diff --git a/mysql-test/suite/innodb/r/innodb_bug84958.result b/mysql-test/suite/innodb/r/innodb_bug84958.result new file mode 100644 index 00000000000..604aa888b54 --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_bug84958.result @@ -0,0 +1,33 @@ +create procedure insert_n(n int) +begin +declare i int default 0; +while i < n do +insert into t1 values (1, 2, 3) on duplicate key update c= c + 1; +set i = i + 1; +end while; +end~~ +create function bprrs() +returns int +begin +declare ret int; +select variable_value +from information_schema.global_status +where +variable_name = 'innodb_buffer_pool_read_requests' + into ret; +return ret; +end~~ +create table t1 (a int, b int, c int, primary key(a,b), key (b,c)) engine=InnoDB; +begin; +select * from t1; +a b c +call insert_n(100); +set @rr1= bprrs(); +select * from t1 force index (b); +a b c +set @rr2= bprrs(); +select @rr2 - @rr1 < 1000; +@rr2 - @rr1 < 1000 +1 +drop database test; +create database test; diff --git a/mysql-test/suite/innodb/t/innodb_bug84958.test b/mysql-test/suite/innodb/t/innodb_bug84958.test new file mode 100644 index 00000000000..7425baa7198 --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_bug84958.test @@ -0,0 +1,50 @@ +# +# Bug #84958 InnoDB's MVCC has O(N^2) behaviors +# https://bugs.mysql.com/bug.php?id=84958 +# + +-- source include/have_innodb.inc + +delimiter ~~; +create procedure insert_n(n int) +begin + declare i int default 0; + while i < n do + insert into t1 values (1, 2, 3) on duplicate key update c= c + 1; + set i = i + 1; + end while; +end~~ + +create function bprrs() +returns int +begin + declare ret int; + select variable_value + from information_schema.global_status + where + variable_name = 'innodb_buffer_pool_read_requests' + into ret; + return ret; +end~~ +delimiter ;~~ + +create table t1 (a int, b int, c int, primary key(a,b), key (b,c)) engine=InnoDB; + +connect (con2, localhost, root,,); + +begin; +select * from t1; + +connection con2; +call insert_n(100); + +connection default; +set @rr1= bprrs(); + +select * from t1 force index (b); +set @rr2= bprrs(); +select @rr2 - @rr1 < 1000; + +disconnect con2; +drop database test; +create database test; diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index e87587973f7..b0cd1ae90c3 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -3022,13 +3022,48 @@ row_sel_build_prev_vers_for_mysql( } /*********************************************************************//** +Helper class to cache clust_rec and old_ver bug 84958 + */ +class +Row_sel_get_clust_rec_for_mysql +{ + const rec_t* cached_clust_rec; + rec_t* cached_old_vers; +public: + Row_sel_get_clust_rec_for_mysql() : + cached_clust_rec(NULL), + cached_old_vers(NULL) {} + dberr_t operator()( + row_prebuilt_t* prebuilt,/*!< in: prebuilt struct in the handle */ + dict_index_t* sec_index,/*!< in: secondary index where rec resides */ + const rec_t* rec, /*!< in: record in a non-clustered index; if + this is a locking read, then rec is not + allowed to be delete-marked, and that would + not make sense either */ + que_thr_t* thr, /*!< in: query thread */ + const rec_t** out_rec,/*!< out: clustered record or an old version of + it, NULL if the old version did not exist + in the read view, i.e., it was a fresh + inserted version */ + ulint** offsets,/*!< in: offsets returned by + rec_get_offsets(rec, sec_index); + out: offsets returned by + rec_get_offsets(out_rec, clust_index) */ + mem_heap_t** offset_heap,/*!< in/out: memory heap from which + the offsets are allocated */ + mtr_t* mtr); /*!< in: mtr used to get access to the + non-clustered record; the same mtr is used to + access the clustered index */ +}; + +/*********************************************************************//** Retrieves the clustered index record corresponding to a record in a non-clustered index. Does the necessary locking. Used in the MySQL interface. @return DB_SUCCESS, DB_SUCCESS_LOCKED_REC, or error code */ -static MY_ATTRIBUTE((nonnull, warn_unused_result)) +MY_ATTRIBUTE((nonnull, warn_unused_result)) dberr_t -row_sel_get_clust_rec_for_mysql( +Row_sel_get_clust_rec_for_mysql::operator()( /*============================*/ row_prebuilt_t* prebuilt,/*!< in: prebuilt struct in the handle */ dict_index_t* sec_index,/*!< in: secondary index where rec resides */ @@ -3157,15 +3192,25 @@ row_sel_get_clust_rec_for_mysql( clust_rec, clust_index, *offsets, trx->read_view)) { - /* The following call returns 'offsets' associated with - 'old_vers' */ - err = row_sel_build_prev_vers_for_mysql( - trx->read_view, clust_index, prebuilt, - clust_rec, offsets, offset_heap, &old_vers, - mtr); + if (clust_rec != cached_clust_rec) { + /* The following call returns 'offsets' associated with + 'old_vers' */ + err = row_sel_build_prev_vers_for_mysql( + trx->read_view, clust_index, prebuilt, + clust_rec, offsets, offset_heap, &old_vers, + mtr); - if (err != DB_SUCCESS || old_vers == NULL) { + if (err != DB_SUCCESS) { + goto err_exit; + } + cached_clust_rec = clust_rec; + cached_old_vers = old_vers; + } else { + err = DB_SUCCESS; + old_vers = cached_old_vers; + } + if (old_vers == NULL) { goto err_exit; } @@ -4025,6 +4070,7 @@ row_search_for_mysql( const rec_t* rec; const rec_t* result_rec = NULL; const rec_t* clust_rec; + Row_sel_get_clust_rec_for_mysql row_sel_get_clust_rec_for_mysql; dberr_t err = DB_SUCCESS; ibool unique_search = FALSE; ibool mtr_has_extra_clust_latch = FALSE;