diff options
-rw-r--r-- | mysql-test/r/innodb-semi-consistent.result | 7 | ||||
-rw-r--r-- | mysql-test/t/innodb-semi-consistent.test | 13 | ||||
-rw-r--r-- | mysql-test/t/innodb.test | 14 | ||||
-rw-r--r-- | storage/innobase/include/row0mysql.h | 15 | ||||
-rw-r--r-- | storage/innobase/include/trx0trx.h | 42 | ||||
-rw-r--r-- | storage/innobase/include/trx0trx.ic | 58 | ||||
-rw-r--r-- | storage/innobase/lock/lock0lock.c | 21 | ||||
-rw-r--r-- | storage/innobase/row/row0mysql.c | 73 | ||||
-rw-r--r-- | storage/innobase/row/row0sel.c | 43 | ||||
-rw-r--r-- | storage/innobase/trx/trx0trx.c | 2 |
10 files changed, 119 insertions, 169 deletions
diff --git a/mysql-test/r/innodb-semi-consistent.result b/mysql-test/r/innodb-semi-consistent.result index 55e3cb5c7b4..ca0e362ef80 100644 --- a/mysql-test/r/innodb-semi-consistent.result +++ b/mysql-test/r/innodb-semi-consistent.result @@ -38,3 +38,10 @@ a 11 7 drop table t1; +create table t1 (a int, b int) engine=myisam; +create table t2 (c int, d int, key (c)) engine=innodb; +insert into t1 values (1,1); +insert into t2 values (1,2); +set session transaction isolation level read committed; +delete from t1 using t1 join t2 on t1.a = t2.c where t2.d in (1); +drop table t1, t2; diff --git a/mysql-test/t/innodb-semi-consistent.test b/mysql-test/t/innodb-semi-consistent.test index 6d3020bb560..61ad7815ca9 100644 --- a/mysql-test/t/innodb-semi-consistent.test +++ b/mysql-test/t/innodb-semi-consistent.test @@ -53,3 +53,16 @@ drop table t1; connection default; disconnect a; disconnect b; + +# Bug 39320 +create table t1 (a int, b int) engine=myisam; +create table t2 (c int, d int, key (c)) engine=innodb; +insert into t1 values (1,1); +insert into t2 values (1,2); +connect (a,localhost,root,,); +connection a; +set session transaction isolation level read committed; +delete from t1 using t1 join t2 on t1.a = t2.c where t2.d in (1); +connection default; +disconnect a; +drop table t1, t2; diff --git a/mysql-test/t/innodb.test b/mysql-test/t/innodb.test index b0353ed5268..9221e389f97 100644 --- a/mysql-test/t/innodb.test +++ b/mysql-test/t/innodb.test @@ -6,7 +6,9 @@ # Use innodb_mysql.[test|result] files instead. # # # # If nevertheless you need to make some changes here, please, forward # -# your commit message To: dev@innodb.com Cc: dev-innodb@mysql.com # +# your commit message # +# To: innodb_dev_ww@oracle.com # +# Cc: dev-innodb@mysql.com # # (otherwise your changes may be erased). # # # ####################################################################### @@ -17,6 +19,10 @@ # Small basic test with ignore # +-- disable_query_log +SET @innodb_thread_concurrency_orig = @@innodb_thread_concurrency; +-- enable_query_log + --disable_warnings drop table if exists t1,t2,t3,t4; drop database if exists mysqltest; @@ -2524,6 +2530,8 @@ DROP TABLE bug35537; DISCONNECT c1; CONNECTION default; +SET GLOBAL innodb_thread_concurrency = @innodb_thread_concurrency_orig; + ####################################################################### # # # Please, DO NOT TOUCH this file as well as the innodb.result file. # @@ -2532,7 +2540,9 @@ CONNECTION default; # Use innodb_mysql.[test|result] files instead. # # # # If nevertheless you need to make some changes here, please, forward # -# your commit message To: dev@innodb.com Cc: dev-innodb@mysql.com # +# your commit message # +# To: innodb_dev_ww@oracle.com # +# Cc: dev-innodb@mysql.com # # (otherwise your changes may be erased). # # # ####################################################################### diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 9b2f3250486..5430190fa51 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -656,6 +656,21 @@ struct row_prebuilt_struct { This eliminates lock waits in some cases; note that this breaks serializability. */ + ulint new_rec_locks; /* normally 0; if + srv_locks_unsafe_for_binlog is + TRUE or session is using READ + COMMITTED isolation level, in a + cursor search, if we set a new + record lock on an index, this is + incremented; this is used in + releasing the locks under the + cursors if we are performing an + UPDATE and we determine after + retrieving the row that it does + not need to be locked; thus, + these can be used to implement a + 'mini-rollback' that releases + the latest record locks */ ulint mysql_prefix_len;/* byte offset of the end of the last requested column */ ulint mysql_row_len; /* length in bytes of a row in the diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 5017c15aaf0..f0833bc6f21 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -21,34 +21,6 @@ Created 3/26/1996 Heikki Tuuri extern ulint trx_n_mysql_transactions; -/***************************************************************** -Resets the new record lock info in a transaction struct. */ -UNIV_INLINE -void -trx_reset_new_rec_lock_info( -/*========================*/ - trx_t* trx); /* in: transaction struct */ -/***************************************************************** -Registers that we have set a new record lock on an index. We only have space -to store 2 indexes! If this is called to store more than 2 indexes after -trx_reset_new_rec_lock_info(), then this function does nothing. */ -UNIV_INLINE -void -trx_register_new_rec_lock( -/*======================*/ - trx_t* trx, /* in: transaction struct */ - dict_index_t* index); /* in: trx sets a new record lock on this - index */ -/***************************************************************** -Checks if trx has set a new record lock on an index. */ -UNIV_INLINE -ibool -trx_new_rec_locks_contain( -/*======================*/ - /* out: TRUE if trx has set a new record lock - on index */ - trx_t* trx, /* in: transaction struct */ - dict_index_t* index); /* in: index */ /************************************************************************ Releases the search latch if trx has reserved it. */ @@ -527,20 +499,6 @@ struct trx_struct{ lock_t* auto_inc_lock; /* possible auto-inc lock reserved by the transaction; note that it is also in the lock list trx_locks */ - dict_index_t* new_rec_locks[2];/* these are normally NULL; if - srv_locks_unsafe_for_binlog is TRUE - or session is using READ COMMITTED - isolation level, - in a cursor search, if we set a new - record lock on an index, this is set - to point to the index; this is - used in releasing the locks under the - cursors if we are performing an UPDATE - and we determine after retrieving - the row that it does not need to be - locked; thus, these can be used to - implement a 'mini-rollback' that - releases the latest record locks */ UT_LIST_NODE_T(trx_t) trx_list; /* list of transactions */ UT_LIST_NODE_T(trx_t) diff --git a/storage/innobase/include/trx0trx.ic b/storage/innobase/include/trx0trx.ic index d562db233e9..09b2f822ff7 100644 --- a/storage/innobase/include/trx0trx.ic +++ b/storage/innobase/include/trx0trx.ic @@ -38,61 +38,3 @@ trx_start_if_not_started_low( trx_start_low(trx, ULINT_UNDEFINED); } } - -/***************************************************************** -Resets the new record lock info in a transaction struct. */ -UNIV_INLINE -void -trx_reset_new_rec_lock_info( -/*========================*/ - trx_t* trx) /* in: transaction struct */ -{ - trx->new_rec_locks[0] = NULL; - trx->new_rec_locks[1] = NULL; -} - -/***************************************************************** -Registers that we have set a new record lock on an index. We only have space -to store 2 indexes! If this is called to store more than 2 indexes after -trx_reset_new_rec_lock_info(), then this function does nothing. */ -UNIV_INLINE -void -trx_register_new_rec_lock( -/*======================*/ - trx_t* trx, /* in: transaction struct */ - dict_index_t* index) /* in: trx sets a new record lock on this - index */ -{ - if (trx->new_rec_locks[0] == NULL) { - trx->new_rec_locks[0] = index; - - return; - } - - if (trx->new_rec_locks[0] == index) { - - return; - } - - if (trx->new_rec_locks[1] != NULL) { - - return; - } - - trx->new_rec_locks[1] = index; -} - -/***************************************************************** -Checks if trx has set a new record lock on an index. */ -UNIV_INLINE -ibool -trx_new_rec_locks_contain( -/*======================*/ - /* out: TRUE if trx has set a new record lock - on index */ - trx_t* trx, /* in: transaction struct */ - dict_index_t* index) /* in: index */ -{ - return(trx->new_rec_locks[0] == index - || trx->new_rec_locks[1] == index); -} diff --git a/storage/innobase/lock/lock0lock.c b/storage/innobase/lock/lock0lock.c index d9bc8ba09e4..5afd19aa7e7 100644 --- a/storage/innobase/lock/lock0lock.c +++ b/storage/innobase/lock/lock0lock.c @@ -1970,12 +1970,6 @@ lock_rec_lock_fast( if (lock == NULL) { if (!impl) { lock_rec_create(mode, rec, index, trx); - - if (srv_locks_unsafe_for_binlog - || trx->isolation_level - == TRX_ISO_READ_COMMITTED) { - trx_register_new_rec_lock(trx, index); - } } return(TRUE); @@ -1999,11 +1993,6 @@ lock_rec_lock_fast( if (!lock_rec_get_nth_bit(lock, heap_no)) { lock_rec_set_nth_bit(lock, heap_no); - if (srv_locks_unsafe_for_binlog - || trx->isolation_level - == TRX_ISO_READ_COMMITTED) { - trx_register_new_rec_lock(trx, index); - } } } @@ -2058,22 +2047,12 @@ lock_rec_lock_slow( enough already granted on the record, we have to wait. */ err = lock_rec_enqueue_waiting(mode, rec, index, thr); - - if (srv_locks_unsafe_for_binlog - || trx->isolation_level == TRX_ISO_READ_COMMITTED) { - trx_register_new_rec_lock(trx, index); - } } else { if (!impl) { /* Set the requested lock on the record */ lock_rec_add_to_queue(LOCK_REC | mode, rec, index, trx); - if (srv_locks_unsafe_for_binlog - || trx->isolation_level - == TRX_ISO_READ_COMMITTED) { - trx_register_new_rec_lock(trx, index); - } } err = DB_SUCCESS; diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c index 088d944cb91..2d9ed4fc944 100644 --- a/storage/innobase/row/row0mysql.c +++ b/storage/innobase/row/row0mysql.c @@ -1476,12 +1476,9 @@ row_unlock_for_mysql( and clust_pcur, and we do not need to reposition the cursors. */ { - dict_index_t* index; btr_pcur_t* pcur = prebuilt->pcur; btr_pcur_t* clust_pcur = prebuilt->clust_pcur; trx_t* trx = prebuilt->trx; - rec_t* rec; - mtr_t mtr; ut_ad(prebuilt && trx); ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); @@ -1501,9 +1498,12 @@ row_unlock_for_mysql( trx->op_info = "unlock_row"; - index = btr_pcur_get_btr_cur(pcur)->index; + if (prebuilt->new_rec_locks >= 1) { - if (index != NULL && trx_new_rec_locks_contain(trx, index)) { + rec_t* rec; + dict_index_t* index; + dulint rec_trx_id; + mtr_t mtr; mtr_start(&mtr); @@ -1514,43 +1514,64 @@ row_unlock_for_mysql( } rec = btr_pcur_get_rec(pcur); + index = btr_pcur_get_btr_cur(pcur)->index; - lock_rec_unlock(trx, rec, prebuilt->select_lock_type); - - mtr_commit(&mtr); + if (prebuilt->new_rec_locks >= 2) { + /* Restore the cursor position and find the record + in the clustered index. */ - /* If the search was done through the clustered index, then - we have not used clust_pcur at all, and we must NOT try to - reset locks on clust_pcur. The values in clust_pcur may be - garbage! */ - - if (index->type & DICT_CLUSTERED) { + if (!has_latches_on_recs) { + btr_pcur_restore_position(BTR_SEARCH_LEAF, + clust_pcur, &mtr); + } - goto func_exit; + rec = btr_pcur_get_rec(clust_pcur); + index = btr_pcur_get_btr_cur(clust_pcur)->index; } - } - index = btr_pcur_get_btr_cur(clust_pcur)->index; + /* If the record has been modified by this + transaction, do not unlock it. */ + ut_a(index->type & DICT_CLUSTERED); - if (index != NULL && trx_new_rec_locks_contain(trx, index)) { + if (index->trx_id_offset) { + rec_trx_id = trx_read_trx_id(rec + + index->trx_id_offset); + } else { + mem_heap_t* heap = NULL; + ulint offsets_[REC_OFFS_NORMAL_SIZE]; + ulint* offsets = offsets_; - mtr_start(&mtr); + *offsets_ = (sizeof offsets_) / sizeof *offsets_; + offsets = rec_get_offsets(rec, index, offsets, + ULINT_UNDEFINED, &heap); - /* Restore the cursor position and find the record */ + rec_trx_id = row_get_rec_trx_id(rec, index, offsets); - if (!has_latches_on_recs) { - btr_pcur_restore_position(BTR_SEARCH_LEAF, clust_pcur, - &mtr); + if (UNIV_LIKELY_NULL(heap)) { + mem_heap_free(heap); + } } - rec = btr_pcur_get_rec(clust_pcur); + if (ut_dulint_cmp(rec_trx_id, trx->id) != 0) { + /* We did not update the record: unlock it */ + + rec = btr_pcur_get_rec(pcur); + index = btr_pcur_get_btr_cur(pcur)->index; - lock_rec_unlock(trx, rec, prebuilt->select_lock_type); + lock_rec_unlock(trx, rec, prebuilt->select_lock_type); + + if (prebuilt->new_rec_locks >= 2) { + rec = btr_pcur_get_rec(clust_pcur); + index = btr_pcur_get_btr_cur(clust_pcur)->index; + + lock_rec_unlock(trx, rec, + prebuilt->select_lock_type); + } + } mtr_commit(&mtr); } -func_exit: trx->op_info = ""; return(DB_SUCCESS); diff --git a/storage/innobase/row/row0sel.c b/storage/innobase/row/row0sel.c index f53dfe8a686..1746fb39f43 100644 --- a/storage/innobase/row/row0sel.c +++ b/storage/innobase/row/row0sel.c @@ -2901,8 +2901,9 @@ row_sel_get_clust_rec_for_mysql( func_exit: *out_rec = clust_rec; - if (prebuilt->select_lock_type == LOCK_X) { - /* We may use the cursor in update: store its position */ + if (prebuilt->select_lock_type != LOCK_NONE) { + /* We may use the cursor in update or in unlock_row(): + store its position */ btr_pcur_store_position(prebuilt->clust_pcur, mtr); } @@ -3303,13 +3304,7 @@ row_search_for_mysql( is set or session is using a READ COMMITED isolation level. Then we are able to remove the record locks set here on an individual row. */ - - if ((srv_locks_unsafe_for_binlog - || trx->isolation_level == TRX_ISO_READ_COMMITTED) - && prebuilt->select_lock_type != LOCK_NONE) { - - trx_reset_new_rec_lock_info(trx); - } + prebuilt->new_rec_locks = 0; /*-------------------------------------------------------------*/ /* PHASE 1: Try to pop the row from the prefetch cache */ @@ -3951,6 +3946,12 @@ no_gap_lock: switch (err) { rec_t* old_vers; case DB_SUCCESS: + if (srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) { + /* Note that a record of + prebuilt->index was locked. */ + prebuilt->new_rec_locks = 1; + } break; case DB_LOCK_WAIT: if (UNIV_LIKELY(prebuilt->row_read_type @@ -3981,7 +3982,7 @@ no_gap_lock: if (UNIV_LIKELY(trx->wait_lock != NULL)) { lock_cancel_waiting_and_release( trx->wait_lock); - trx_reset_new_rec_lock_info(trx); + prebuilt->new_rec_locks = 0; } else { mutex_exit(&kernel_mutex); @@ -3993,6 +3994,9 @@ no_gap_lock: ULINT_UNDEFINED, &heap); err = DB_SUCCESS; + /* Note that a record of + prebuilt->index was locked. */ + prebuilt->new_rec_locks = 1; break; } mutex_exit(&kernel_mutex); @@ -4142,6 +4146,15 @@ requires_clust_rec: goto next_rec; } + if ((srv_locks_unsafe_for_binlog + || trx->isolation_level == TRX_ISO_READ_COMMITTED) + && prebuilt->select_lock_type != LOCK_NONE) { + /* Note that both the secondary index record + and the clustered index record were locked. */ + ut_ad(prebuilt->new_rec_locks == 1); + prebuilt->new_rec_locks = 2; + } + if (UNIV_UNLIKELY(rec_get_deleted_flag(clust_rec, comp))) { /* The record is delete marked: we can skip it */ @@ -4267,13 +4280,7 @@ next_rec: prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; } did_semi_consistent_read = FALSE; - - if (UNIV_UNLIKELY(srv_locks_unsafe_for_binlog - || trx->isolation_level == TRX_ISO_READ_COMMITTED) - && prebuilt->select_lock_type != LOCK_NONE) { - - trx_reset_new_rec_lock_info(trx); - } + prebuilt->new_rec_locks = 0; /*-------------------------------------------------------------*/ /* PHASE 5: Move the cursor to the next index record */ @@ -4379,7 +4386,7 @@ lock_wait_or_error: rec_loop we will again try to set a lock, and new_rec_lock_info in trx will be right at the end. */ - trx_reset_new_rec_lock_info(trx); + prebuilt->new_rec_locks = 0; } mode = pcur->search_mode; diff --git a/storage/innobase/trx/trx0trx.c b/storage/innobase/trx/trx0trx.c index 43456865903..8ada38845c5 100644 --- a/storage/innobase/trx/trx0trx.c +++ b/storage/innobase/trx/trx0trx.c @@ -192,8 +192,6 @@ trx_create( trx->n_autoinc_rows = 0; - trx_reset_new_rec_lock_info(trx); - return(trx); } |