summaryrefslogtreecommitdiff
path: root/storage/innobase/row
diff options
context:
space:
mode:
authorMarko Mäkelä <marko.makela@oracle.com>2013-08-15 15:23:23 +0300
committerMarko Mäkelä <marko.makela@oracle.com>2013-08-15 15:23:23 +0300
commit318096074e2e195d7c97d6e08360c3966b49daea (patch)
treeb9127b2f76fe5989fe9383ad5dce04f89094c9d3 /storage/innobase/row
parent300ac936be090e12b118dc2f8d334d39cace30ae (diff)
downloadmariadb-git-318096074e2e195d7c97d6e08360c3966b49daea.tar.gz
Bug#17302896 DOUBLE PURGE ON ROLLBACK OF UPDATING A DELETE-MARKED RECORD
There was a race condition in the rollback of TRX_UNDO_UPD_DEL_REC. Once row_undo_mod_clust() has rolled back the changes by the rolling-back transaction, it attempts to purge the delete-marked record, if possible, in a separate mini-transaction. However, row_undo_mod_remove_clust_low() fails to check if the DB_TRX_ID of the record that it found after repositioning the cursor, is still the same. If it is not, it means that the record was purged and another record was inserted in its place. So, the rollback would have performed an incorrect purge, breaking the locking rules and causing corruption. The problem was found by creating a table that contains a unique secondary index and a primary key, and two threads running REPLACE with only one value for the unique column, so that the uniqueness constraint would be violated all the time, leading to statement rollback. This bug exists in all InnoDB versions (I checked MySQL 3.23.53). It has become easier to repeat in 5.5 and 5.6 thanks to scalability improvements and a dedicated purge thread. rb#3085 approved by Jimmy Yang
Diffstat (limited to 'storage/innobase/row')
-rw-r--r--storage/innobase/row/row0umod.c44
1 files changed, 38 insertions, 6 deletions
diff --git a/storage/innobase/row/row0umod.c b/storage/innobase/row/row0umod.c
index 83288c570cb..37e857a4708 100644
--- a/storage/innobase/row/row0umod.c
+++ b/storage/innobase/row/row0umod.c
@@ -1,7 +1,7 @@
/******************************************************
Undo modify of a row
-(c) 1997 Innobase Oy
+Copyright (c) 1997, 2013, Oracle and/or its affiliates. All Rights Reserved.
Created 2/27/1997 Heikki Tuuri
*******************************************************/
@@ -94,7 +94,10 @@ row_undo_mod_clust_low(
}
/***************************************************************
-Removes a clustered index record after undo if possible. */
+Purges a clustered index record after undo if possible.
+This is attempted when the record was inserted by updating a
+delete-marked record and there no longer exist transactions
+that would see the delete-marked record. */
static
ulint
row_undo_mod_remove_clust_low(
@@ -103,13 +106,16 @@ row_undo_mod_remove_clust_low(
we may run out of file space */
undo_node_t* node, /* in: row undo node */
que_thr_t* thr __attribute__((unused)), /* in: query thread */
- mtr_t* mtr, /* in: mtr */
+ mtr_t* mtr, /* in/out: mini-transaction */
ulint mode) /* in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
{
btr_pcur_t* pcur;
btr_cur_t* btr_cur;
ulint err;
ibool success;
+ byte* db_trx_id;
+
+ ut_ad(node->rec_type == TRX_UNDO_UPD_DEL_REC);
pcur = &(node->pcur);
btr_cur = btr_pcur_get_btr_cur(pcur);
@@ -123,11 +129,37 @@ row_undo_mod_remove_clust_low(
/* Find out if we can remove the whole clustered index record */
- if (node->rec_type == TRX_UNDO_UPD_DEL_REC
- && !row_vers_must_preserve_del_marked(node->new_trx_id, mtr)) {
+ if (row_vers_must_preserve_del_marked(node->new_trx_id, mtr)) {
+ return(DB_SUCCESS);
+ }
+
+ if (!btr_cur_get_index(btr_cur)->trx_id_offset) {
+ mem_heap_t* heap = NULL;
+ ulint trx_id_col;
+ ulint* offsets;
+ ulint len;
- /* Ok, we can remove */
+ trx_id_col = dict_index_get_sys_col_pos(
+ btr_cur_get_index(btr_cur), DATA_TRX_ID);
+ ut_ad(trx_id_col > 0);
+ ut_ad(trx_id_col != ULINT_UNDEFINED);
+
+ offsets = rec_get_offsets(
+ btr_cur_get_rec(btr_cur), btr_cur_get_index(btr_cur),
+ NULL, trx_id_col + 1, &heap);
+
+ db_trx_id = rec_get_nth_field(btr_cur_get_rec(btr_cur),
+ offsets, trx_id_col, &len);
+ ut_ad(len == DATA_TRX_ID_LEN);
+ mem_heap_free(heap);
} else {
+ db_trx_id = btr_cur_get_rec(btr_cur)
+ + btr_cur_get_index(btr_cur)->trx_id_offset;
+ }
+
+ if (ut_dulint_cmp(trx_read_trx_id(db_trx_id), node->new_trx_id)) {
+ /* The record must have been purged and then replaced
+ with a different one. */
return(DB_SUCCESS);
}