From af57c658090cc52aa20e8cde7d24a563d73ff886 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 20 Jul 2020 18:28:07 +0300 Subject: [PATCH 1/4] MDEV-22061 InnoDB: Assertion of missing row in sec index row_start upon REPLACE on a system-versioned table make_versioned_helper() appended new update field unconditionally while it should check if this field already exists in update vector. Misc renames to conform versioning prefix. vers_update_fields() name conforms with sql layer TABLE::vers_update_fields(). --- mysql-test/suite/versioning/r/update.result | 16 ++++++++ mysql-test/suite/versioning/t/update.test | 20 ++++++++++ storage/innobase/include/row0upd.h | 18 ++++----- storage/innobase/row/row0mysql.cc | 16 ++++---- storage/innobase/row/row0upd.cc | 41 ++++++++++++++------- 5 files changed, 80 insertions(+), 31 deletions(-) diff --git a/mysql-test/suite/versioning/r/update.result b/mysql-test/suite/versioning/r/update.result index f7901d11d2a..512e39355e2 100644 --- a/mysql-test/suite/versioning/r/update.result +++ b/mysql-test/suite/versioning/r/update.result @@ -319,3 +319,19 @@ create or replace table t1 (f point, key(f)) with system versioning engine=myisa update t1 set f = null where f = 'foo'; ERROR 22003: Cannot get geometry object from data you send to the GEOMETRY field drop table t1; +# +# MDEV-22061 InnoDB: Assertion of missing row in sec index row_start upon REPLACE on a system-versioned table +# +create or replace table t1 ( +a int, +b int, +row_start bigint(20) unsigned generated always as row start, +row_end bigint(20) unsigned generated always as row end, +unique key (b,row_end), +key (row_start), +period for system_time (row_start,row_end) +) engine=innodb with system versioning; +insert into t1 (a, b) values (1, 2); +replace into t1 (a, b) values (3, 2); +replace into t1 (a, b) values (4, 2); +drop table t1; diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test index 5b0a9eb5c42..d2af2cac80c 100644 --- a/mysql-test/suite/versioning/t/update.test +++ b/mysql-test/suite/versioning/t/update.test @@ -245,4 +245,24 @@ update t1 set f = null where f = 'foo'; # cleanup drop table t1; +--echo # +--echo # MDEV-22061 InnoDB: Assertion of missing row in sec index row_start upon REPLACE on a system-versioned table +--echo # +create or replace table t1 ( + a int, + b int, + row_start bigint(20) unsigned generated always as row start, + row_end bigint(20) unsigned generated always as row end, + unique key (b,row_end), + key (row_start), + period for system_time (row_start,row_end) +) engine=innodb with system versioning; + +insert into t1 (a, b) values (1, 2); +replace into t1 (a, b) values (3, 2); +replace into t1 (a, b) values (4, 2); + +# cleanup +drop table t1; + source suite/versioning/common_finish.inc; diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index b34acfd8dc1..de97b379d9e 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -587,25 +587,25 @@ private: make_versioned_delete(). @param[in] trx transaction @param[in] vers_sys_idx table->row_start or table->row_end */ - void make_versioned_helper(const trx_t* trx, ulint idx); + void vers_update_fields(const trx_t *trx, ulint idx); public: /** Also set row_start = CURRENT_TIMESTAMP/trx->id @param[in] trx transaction */ - void make_versioned_update(const trx_t* trx) - { - make_versioned_helper(trx, table->vers_start); - } + void vers_make_update(const trx_t *trx) + { + vers_update_fields(trx, table->vers_start); + } /** Only set row_end = CURRENT_TIMESTAMP/trx->id. Do not touch other fields at all. @param[in] trx transaction */ - void make_versioned_delete(const trx_t* trx) - { + void vers_make_delete(const trx_t *trx) + { update->n_fields = 0; is_delete = VERSIONED_DELETE; - make_versioned_helper(trx, table->vers_end); - } + vers_update_fields(trx, table->vers_end); + } }; #define UPD_NODE_MAGIC_N 1579975 diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index b2256e9905d..d2fc63a5f38 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -1871,10 +1871,10 @@ row_update_for_mysql(row_prebuilt_t* prebuilt) if (prebuilt->versioned_write) { if (node->is_delete == VERSIONED_DELETE) { - node->make_versioned_delete(trx); - } else if (node->update->affects_versioned()) { - node->make_versioned_update(trx); - } + node->vers_make_delete(trx); + } else if (node->update->affects_versioned()) { + node->vers_make_update(trx); + } } for (;;) { @@ -2239,14 +2239,14 @@ row_update_cascade_for_mysql( if (table->versioned()) { if (node->is_delete == PLAIN_DELETE) { - node->make_versioned_delete(trx); - } else if (node->update->affects_versioned()) { + node->vers_make_delete(trx); + } else if (node->update->affects_versioned()) { dberr_t err = row_update_vers_insert(thr, node); if (err != DB_SUCCESS) { return err; } - node->make_versioned_update(trx); - } + node->vers_make_update(trx); + } } for (;;) { diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index 0f700c77c36..8ce47e74a1c 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -3486,32 +3486,45 @@ Supposed to be called only by make_versioned_update() and make_versioned_delete(). @param[in] trx transaction @param[in] vers_sys_idx table->row_start or table->row_end */ -void upd_node_t::make_versioned_helper(const trx_t* trx, ulint idx) +void upd_node_t::vers_update_fields(const trx_t *trx, ulint idx) { ut_ad(in_mysql_interface); // otherwise needs to recalculate // node->cmpl_info ut_ad(idx == table->vers_start || idx == table->vers_end); dict_index_t* clust_index = dict_table_get_first_index(table); + const dict_col_t *col= dict_table_get_nth_col(table, idx); + ulint field_no= dict_col_get_clust_pos(col, clust_index); + upd_field_t *ufield; - /* row_create_update_node_for_mysql() pre-allocated this much. + for (ulint i= 0; i < update->n_fields; ++i) + { + if (update->fields[i].field_no == field_no) + { + ufield= &update->fields[i]; + goto skip_append; + } + } + + /* row_create_update_node_for_mysql() pre-allocated this much. At least one PK column always remains unchanged. */ ut_ad(update->n_fields < ulint(table->n_cols + table->n_v_cols)); update->n_fields++; - upd_field_t* ufield = upd_get_nth_field(update, update->n_fields - 1); - const dict_col_t* col = dict_table_get_nth_col(table, idx); + ufield= upd_get_nth_field(update, update->n_fields - 1); + upd_field_set_field_no(ufield, field_no, clust_index); - upd_field_set_field_no(ufield, dict_col_get_clust_pos(col, clust_index), - clust_index); +skip_append: + char *where= reinterpret_cast(update->vers_sys_value); + if (col->vers_native()) + { + mach_write_to_8(where, trx->id); + } + else + { + thd_get_query_start_data(trx->mysql_thd, where); + } - char* where = reinterpret_cast(update->vers_sys_value); - if (col->vers_native()) { - mach_write_to_8(where, trx->id); - } else { - thd_get_query_start_data(trx->mysql_thd, where); - } - - dfield_set_data(&ufield->new_val, update->vers_sys_value, col->len); + dfield_set_data(&ufield->new_val, update->vers_sys_value, col->len); } From af83ed9f0ed10496c11bea6b054f4d86562e2349 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 20 Jul 2020 18:28:08 +0300 Subject: [PATCH 2/4] MDEV-20661 Virtual fields are not recalculated on system fields value assignment Fix stale virtual field value in 4 cases: when virtual field depends on row_start/row_end in timestamp/trx_id versioned table. row_start dep is recalculated in vers_update_fields() (SQL and InnoDB layer). row_end dep is recalculated on history row insert. --- mysql-test/suite/versioning/r/update.result | 15 +++++ mysql-test/suite/versioning/t/update.test | 21 +++++++ sql/sql_insert.cc | 4 ++ sql/table.cc | 25 +++----- storage/innobase/handler/ha_innodb.cc | 67 +++++++++++++++++++++ storage/innobase/include/row0ins.h | 3 + storage/innobase/include/row0upd.h | 27 ++++++++- storage/innobase/row/row0mysql.cc | 42 +------------ storage/innobase/row/row0upd.cc | 22 ++++++- 9 files changed, 169 insertions(+), 57 deletions(-) diff --git a/mysql-test/suite/versioning/r/update.result b/mysql-test/suite/versioning/r/update.result index 512e39355e2..cd26c341113 100644 --- a/mysql-test/suite/versioning/r/update.result +++ b/mysql-test/suite/versioning/r/update.result @@ -335,3 +335,18 @@ insert into t1 (a, b) values (1, 2); replace into t1 (a, b) values (3, 2); replace into t1 (a, b) values (4, 2); drop table t1; +# +# MDEV-20661 Virtual fields are not recalculated on system fields value assignment +# +create table t1 ( +a int, +row_start SYS_DATATYPE as row start invisible, +row_end SYS_DATATYPE as row end invisible, +period for system_time (row_start, row_end), +v1 bigint unsigned as (a ^ row_start) unique, +v2 bigint unsigned as (a ^ row_end) unique +) engine=innodb with system versioning; +insert into t1 (a) values (1), (2); +update ignore t1 set a= 3; +delete history from t1; +drop table t1; diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test index d2af2cac80c..06f81ea9064 100644 --- a/mysql-test/suite/versioning/t/update.test +++ b/mysql-test/suite/versioning/t/update.test @@ -265,4 +265,25 @@ replace into t1 (a, b) values (4, 2); # cleanup drop table t1; +--echo # +--echo # MDEV-20661 Virtual fields are not recalculated on system fields value assignment +--echo # + +replace_result $sys_datatype_expl SYS_DATATYPE; +eval create table t1 ( + a int, + row_start $sys_datatype_expl as row start invisible, + row_end $sys_datatype_expl as row end invisible, + period for system_time (row_start, row_end), + v1 bigint unsigned as (a ^ row_start) unique, + v2 bigint unsigned as (a ^ row_end) unique +) engine=innodb with system versioning; + +insert into t1 (a) values (1), (2); +update ignore t1 set a= 3; +delete history from t1; + +# cleanup +drop table t1; + source suite/versioning/common_finish.inc; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index bbd639b91d3..a24cb61449b 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1658,6 +1658,10 @@ int vers_insert_history_row(TABLE *table) if (row_start->cmp(row_start->ptr, row_end->ptr) >= 0) return 0; + if (table->vfield && + table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_READ)) + return HA_ERR_GENERIC; + return table->file->ha_write_row(table->record[0]); } diff --git a/sql/table.cc b/sql/table.cc index 7aa7abfa006..84c827ec70b 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -8078,29 +8078,24 @@ void TABLE::vers_update_fields() bitmap_set_bit(write_set, vers_start_field()->field_index); bitmap_set_bit(write_set, vers_end_field()->field_index); - if (versioned(VERS_TIMESTAMP)) + if (!vers_write) { - if (!vers_write) - { - file->column_bitmaps_signal(); - return; - } - if (vers_start_field()->store_timestamp(in_use->query_start(), - in_use->query_start_sec_part())) - DBUG_ASSERT(0); + file->column_bitmaps_signal(); + return; } - else + + if (versioned(VERS_TIMESTAMP) && + vers_start_field()->store_timestamp(in_use->query_start(), + in_use->query_start_sec_part())) { - if (!vers_write) - { - file->column_bitmaps_signal(); - return; - } + DBUG_ASSERT(0); } vers_end_field()->set_max(); bitmap_set_bit(read_set, vers_end_field()->field_index); file->column_bitmaps_signal(); + if (vfield) + update_virtual_fields(file, VCOL_UPDATE_FOR_READ); } diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 7234e73f28c..055d311a119 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -21814,3 +21814,70 @@ ib_push_frm_error( break; } } + +/** Writes 8 bytes to nth tuple field +@param[in] tuple where to write +@param[in] nth index in tuple +@param[in] data what to write +@param[in] buf field data buffer */ +static void set_tuple_col_8(dtuple_t *tuple, int col, uint64_t data, byte *buf) +{ + dfield_t *dfield= dtuple_get_nth_field(tuple, col); + ut_ad(dfield->type.len == 8); + if (dfield->len == UNIV_SQL_NULL) + { + dfield_set_data(dfield, buf, 8); + } + ut_ad(dfield->len == dfield->type.len && dfield->data); + mach_write_to_8(dfield->data, data); +} + +void ins_node_t::vers_update_end(row_prebuilt_t *prebuilt, bool history_row) +{ + ut_ad(prebuilt->ins_node == this); + trx_t *trx= prebuilt->trx; +#ifndef DBUG_OFF + ut_ad(table->vers_start != table->vers_end); + const mysql_row_templ_t *t= prebuilt->get_template_by_col(table->vers_end); + ut_ad(t); + ut_ad(t->mysql_col_len == 8); +#endif + + if (history_row) + { + set_tuple_col_8(row, table->vers_end, trx->id, vers_end_buf); + } + else /* ROW_INS_VERSIONED */ + { + set_tuple_col_8(row, table->vers_end, TRX_ID_MAX, vers_end_buf); +#ifndef DBUG_OFF + t= prebuilt->get_template_by_col(table->vers_start); + ut_ad(t); + ut_ad(t->mysql_col_len == 8); +#endif + set_tuple_col_8(row, table->vers_start, trx->id, vers_start_buf); + } + dict_index_t *clust_index= dict_table_get_first_index(table); + THD *thd= trx->mysql_thd; + TABLE *mysql_table= prebuilt->m_mysql_table; + mem_heap_t *local_heap= NULL; + for (ulint col_no= 0; col_no < dict_table_get_n_v_cols(table); col_no++) + { + + const dict_v_col_t *v_col= dict_table_get_nth_v_col(table, col_no); + for (ulint i= 0; i < unsigned(v_col->num_base); i++) + { + dict_col_t *base_col= v_col->base_col[i]; + if (base_col->ind == table->vers_end) + { + innobase_get_computed_value(row, v_col, clust_index, &local_heap, + table->heap, NULL, thd, mysql_table, + mysql_table->record[0], NULL, NULL, NULL); + } + } + } + if (local_heap) + { + mem_heap_free(local_heap); + } +} diff --git a/storage/innobase/include/row0ins.h b/storage/innobase/include/row0ins.h index 95f4388902d..34427dc6dc7 100644 --- a/storage/innobase/include/row0ins.h +++ b/storage/innobase/include/row0ins.h @@ -163,6 +163,8 @@ row_ins_step( #define INS_NODE_INSERT_ENTRIES 3 /* index entries should be built and inserted */ +struct row_prebuilt_t; + /** Insert node structure */ struct ins_node_t { @@ -203,6 +205,7 @@ struct ins_node_t entry_list and sys fields are stored here; if this is NULL, entry list should be created and buffers for sys fields in row allocated */ + void vers_update_end(row_prebuilt_t *prebuilt, bool history_row); }; /** Create an insert object. diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index de97b379d9e..ea9c2db9de0 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -454,7 +454,32 @@ struct upd_t{ fields[n_fields++] = field; } - /** Determine if the given field_no is modified. + void remove_element(ulint i) + { + ut_ad(n_fields > 0); + ut_ad(i < n_fields); + while (i < n_fields - 1) + { + fields[i]= fields[i + 1]; + i++; + } + n_fields--; + } + + bool remove(const ulint field_no) + { + for (ulint i= 0; i < n_fields; ++i) + { + if (field_no == fields[i].field_no) + { + remove_element(i); + return true; + } + } + return false; + } + + /** Determine if the given field_no is modified. @return true if modified, false otherwise. */ bool is_modified(const ulint field_no) const { diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index d2fc63a5f38..15538ff9922 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -1331,23 +1331,6 @@ row_mysql_get_table_status( return(err); } -/** Writes 8 bytes to nth tuple field -@param[in] tuple where to write -@param[in] nth index in tuple -@param[in] data what to write -@param[in] buf field data buffer */ -static -void -set_tuple_col_8(dtuple_t* tuple, int col, uint64_t data, byte* buf) { - dfield_t* dfield = dtuple_get_nth_field(tuple, col); - ut_ad(dfield->type.len == 8); - if (dfield->len == UNIV_SQL_NULL) { - dfield_set_data(dfield, buf, 8); - } - ut_ad(dfield->len == dfield->type.len && dfield->data); - mach_write_to_8(dfield->data, data); -} - /** Does an insert for MySQL. @param[in] mysql_rec row in the MySQL format @param[in,out] prebuilt prebuilt struct in MySQL handle @@ -1415,29 +1398,8 @@ row_insert_for_mysql( &blob_heap); if (ins_mode != ROW_INS_NORMAL) { -#ifndef DBUG_OFF - ut_ad(table->vers_start != table->vers_end); - const mysql_row_templ_t* t - = prebuilt->get_template_by_col(table->vers_end); - ut_ad(t); - ut_ad(t->mysql_col_len == 8); -#endif - - if (ins_mode == ROW_INS_HISTORICAL) { - set_tuple_col_8(node->row, table->vers_end, trx->id, - node->vers_end_buf); - } else /* ROW_INS_VERSIONED */ { - set_tuple_col_8(node->row, table->vers_end, TRX_ID_MAX, - node->vers_end_buf); -#ifndef DBUG_OFF - t = prebuilt->get_template_by_col(table->vers_start); - ut_ad(t); - ut_ad(t->mysql_col_len == 8); -#endif - set_tuple_col_8(node->row, table->vers_start, trx->id, - node->vers_start_buf); - } - } + node->vers_update_end(prebuilt, ins_mode == ROW_INS_HISTORICAL); + } savept = trx_savept_take(trx); diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index 8ce47e74a1c..f2eaef1fd0a 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -3526,5 +3526,25 @@ skip_append: } dfield_set_data(&ufield->new_val, update->vers_sys_value, col->len); -} + for (ulint col_no= 0; col_no < dict_table_get_n_v_cols(table); col_no++) + { + + const dict_v_col_t *v_col= dict_table_get_nth_v_col(table, col_no); + if (!v_col->m_col.ord_part) + continue; + for (ulint i= 0; i < unsigned(v_col->num_base); i++) + { + dict_col_t *base_col= v_col->base_col[i]; + if (base_col->ind == col->ind) + { + /* Virtual column depends on system field value + which we updated above. Remove it from update + vector, so it is recalculated in + row_upd_store_v_row() (see !update branch). */ + update->remove(v_col->v_pos); + break; + } + } + } +} From e26c822aa0a73e62d7a3e4a0915c7bf6cd734978 Mon Sep 17 00:00:00 2001 From: Monty Date: Tue, 21 Jul 2020 15:12:53 +0300 Subject: [PATCH 3/4] MDEV-16929 Assertion ... in close_thread_tables upon killing connection Problem was that the code didn't handle a transaction created in innodb as part of a failed mysql_lock_tables() --- mysql-test/suite/sql_sequence/kill.result | 12 ++++++++++++ mysql-test/suite/sql_sequence/kill.test | 20 ++++++++++++++++++++ sql/sql_sequence.cc | 4 ++++ 3 files changed, 36 insertions(+) create mode 100644 mysql-test/suite/sql_sequence/kill.result create mode 100644 mysql-test/suite/sql_sequence/kill.test diff --git a/mysql-test/suite/sql_sequence/kill.result b/mysql-test/suite/sql_sequence/kill.result new file mode 100644 index 00000000000..6d966254de3 --- /dev/null +++ b/mysql-test/suite/sql_sequence/kill.result @@ -0,0 +1,12 @@ +# +# MDEV-16929 Assertion ... in close_thread_tables upon killing connection +# running SHOW on sequence +# +CREATE SEQUENCE s ENGINE=InnoDB; +RENAME TABLE s TO s1; +connect con1,localhost,root,,test; +SHOW CREATE SEQUENCE s1; +connection default; +KILL thread_id; +connection default; +drop sequence s1; diff --git a/mysql-test/suite/sql_sequence/kill.test b/mysql-test/suite/sql_sequence/kill.test new file mode 100644 index 00000000000..9caebc57f12 --- /dev/null +++ b/mysql-test/suite/sql_sequence/kill.test @@ -0,0 +1,20 @@ +--source include/have_innodb.inc + +--echo # +--echo # MDEV-16929 Assertion ... in close_thread_tables upon killing connection +--echo # running SHOW on sequence +--echo # + +CREATE SEQUENCE s ENGINE=InnoDB; +RENAME TABLE s TO s1; +--connect (con1,localhost,root,,test) +--let $conid= `SELECT CONNECTION_ID()` +--send + SHOW CREATE SEQUENCE s1; +--connection default +--replace_result $conid thread_id +--eval KILL $conid + +# Cleanup +--connection default +drop sequence s1; diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc index 939963de682..ffdb4b54c16 100644 --- a/sql/sql_sequence.cc +++ b/sql/sql_sequence.cc @@ -485,6 +485,10 @@ int SEQUENCE::read_initial_values(TABLE *table) if (mdl_lock_used) thd->mdl_context.release_lock(mdl_request.ticket); write_unlock(table); + + if (!has_active_transaction && !thd->transaction.stmt.is_empty() && + !thd->in_sub_stmt) + trans_commit_stmt(thd); DBUG_RETURN(HA_ERR_LOCK_WAIT_TIMEOUT); } DBUG_ASSERT(table->reginfo.lock_type == TL_READ); From b75563cdfd161e373480949ffa498bd4a8087b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 21 Jul 2020 15:59:45 +0300 Subject: [PATCH 4/4] MDEV-15880: ASAN heap-use-after-free with innodb_evict_tables_on_commit_debug trx_update_mod_tables_timestamp(): When implementing innodb_evict_tables_on_commit_debug, do not evict tables on which transactional locks exist. This debug variable was broken since its introduction in commit 947b0b5722117350c83656ee0b23502be59b7d2b. --- storage/innobase/trx/trx0trx.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index e0d8d44a86e..1d4202b3033 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -1295,7 +1295,8 @@ trx_update_mod_tables_timestamp( dict_table_t* table = it->first; table->update_time = now; #ifdef UNIV_DEBUG - if (preserve_tables || table->get_ref_count()) { + if (preserve_tables || table->get_ref_count() + || UT_LIST_GET_LEN(table->locks)) { /* do not evict when committing DDL operations or if some other transaction is holding the table handle */ @@ -1304,7 +1305,11 @@ trx_update_mod_tables_timestamp( /* recheck while holding the mutex that blocks table->acquire() */ mutex_enter(&dict_sys_mutex); - if (!table->get_ref_count()) { + mutex_enter(&lock_sys.mutex); + const bool do_evict = !table->get_ref_count() + && !UT_LIST_GET_LEN(table->locks); + mutex_exit(&lock_sys.mutex); + if (do_evict) { # if MYSQL_VERSION_ID >= 100405 dict_sys.remove(table, true); # else