diff --git a/mysql-test/suite/innodb/r/index_lock_upgrade.result b/mysql-test/suite/innodb/r/index_lock_upgrade.result new file mode 100644 index 0000000000000..f60d4abcae718 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_lock_upgrade.result @@ -0,0 +1,90 @@ +# +# MDEV-38814: High rate of index_lock_upgrades due to +# btr_cur_need_opposite_intention() mostly returning true +# +# Test with simple table structure: +# Single INT primary key +# DATETIME column dt that starts NULL and gets updated to a +# distinct timestamp per row +# +# The NULL-to-DATETIME growth makes btr_cur_optimistic_update() +# fall through to the pessimistic path (BTR_MODIFY_TREE). +CREATE TABLE t1 ( +id INT NOT NULL, +dt DATETIME NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB; +INSERT INTO t1 (id, dt) SELECT seq, NULL FROM seq_1_to_1000; +# Rows inserted with NULL DATETIME +SELECT COUNT(*) AS rows_inserted FROM t1; +rows_inserted +1000 +SELECT COUNT(DISTINCT dt) AS distinct_timestamps FROM t1; +distinct_timestamps +0 +# Index lock upgrades during inserts +SELECT @lu01 AS index_lock_upgrades_insert; +index_lock_upgrades_insert +5 +SELECT @pu01 AS pessimistic_update_calls_insert; +pessimistic_update_calls_insert +0 +SELECT @ou01 AS optim_err_underflows_insert; +optim_err_underflows_insert +0 +SELECT @oo01 AS optim_err_overflows_insert; +optim_err_overflows_insert +0 +SELECT @ps01 AS index_page_splits_insert; +index_page_splits_insert +6 +SELECT @ma01 AS index_page_merge_attempts_insert; +index_page_merge_attempts_insert +0 +SELECT @ms01 AS index_page_merge_successful_insert; +index_page_merge_successful_insert +0 +SELECT @rs01 AS index_page_reorg_successful_insert; +index_page_reorg_successful_insert +0 +SELECT @pd01 AS index_page_discards_insert; +index_page_discards_insert +0 +# Now update all rows (batch update) +# This changes DATETIME from NULL to a distinct timestamp per row, +# triggering the pessimistic path due to record size change. +UPDATE t1 JOIN seq_1_to_1000 s ON t1.id = s.seq +SET t1.dt= '2025-01-01' + INTERVAL s.seq SECOND; +# Rows updated +SELECT COUNT(DISTINCT dt) AS distinct_timestamps FROM t1; +distinct_timestamps +1000 +# Index lock upgrades during updates +SELECT @lu12 AS index_lock_upgrades_update; +index_lock_upgrades_update +6 +SELECT @pu12 AS pessimistic_update_calls_update; +pessimistic_update_calls_update +6 +SELECT @ou12 AS optim_err_underflows_update; +optim_err_underflows_update +0 +SELECT @oo12 AS optim_err_overflows_update; +optim_err_overflows_update +6 +SELECT @ps12 AS index_page_splits_update; +index_page_splits_update +6 +SELECT @ma12 AS index_page_merge_attempts_update; +index_page_merge_attempts_update +0 +SELECT @ms12 AS index_page_merge_successful_update; +index_page_merge_successful_update +0 +SELECT @rs12 AS index_page_reorg_successful_update; +index_page_reorg_successful_update +40 +SELECT @pd12 AS index_page_discards_update; +index_page_discards_update +0 +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/innodb_status_variables.result b/mysql-test/suite/innodb/r/innodb_status_variables.result index c6f4d4f27c45a..95fac1b50829c 100644 --- a/mysql-test/suite/innodb/r/innodb_status_variables.result +++ b/mysql-test/suite/innodb/r/innodb_status_variables.result @@ -3,7 +3,11 @@ WHERE variable_name LIKE 'INNODB_%' AND variable_name NOT IN ('INNODB_ADAPTIVE_HASH_HASH_SEARCHES','INNODB_ADAPTIVE_HASH_NON_HASH_SEARCHES', 'INNODB_MEM_ADAPTIVE_HASH', -'INNODB_BUFFERED_AIO_SUBMITTED','INNODB_BUFFER_POOL_PAGES_LATCHED'); +'INNODB_BUFFERED_AIO_SUBMITTED','INNODB_BUFFER_POOL_PAGES_LATCHED', +'INNODB_BTR_CUR_N_INDEX_LOCK_UPGRADES', +'INNODB_BTR_CUR_PESSIMISTIC_UPDATE_CALLS', +'INNODB_BTR_CUR_PESSIMISTIC_UPDATE_OPTIM_ERR_UNDERFLOWS', +'INNODB_BTR_CUR_PESSIMISTIC_UPDATE_OPTIM_ERR_OVERFLOWS'); variable_name INNODB_ASYNC_READS_PENDING INNODB_ASYNC_READS_TASKS_RUNNING diff --git a/mysql-test/suite/innodb/t/index_lock_upgrade.test b/mysql-test/suite/innodb/t/index_lock_upgrade.test new file mode 100644 index 0000000000000..354212174c169 --- /dev/null +++ b/mysql-test/suite/innodb/t/index_lock_upgrade.test @@ -0,0 +1,155 @@ +--source include/have_innodb.inc +--source include/have_innodb_4k.inc +--source include/have_sequence.inc +--source include/have_debug.inc + +--echo # +--echo # MDEV-38814: High rate of index_lock_upgrades due to +--echo # btr_cur_need_opposite_intention() mostly returning true +--echo # + +--echo # Test with simple table structure: +--echo # Single INT primary key +--echo # DATETIME column dt that starts NULL and gets updated to a +--echo # distinct timestamp per row +--echo # +--echo # The NULL-to-DATETIME growth makes btr_cur_optimistic_update() +--echo # fall through to the pessimistic path (BTR_MODIFY_TREE). +CREATE TABLE t1 ( + id INT NOT NULL, + dt DATETIME NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB; + +--disable_query_log +SET GLOBAL innodb_monitor_enable= 'module_index'; +--enable_query_log + +let $lu0= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_n_index_lock_upgrades'`; +let $pu0= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_calls'`; +let $ou0= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_optim_err_underflows'`; +let $oo0= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_optim_err_overflows'`; +let $ps0= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_splits'`; +let $ma0= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_merge_attempts'`; +let $ms0= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_merge_successful'`; +let $rs0= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_reorg_successful'`; +let $pd0= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_discards'`; + +INSERT INTO t1 (id, dt) SELECT seq, NULL FROM seq_1_to_1000; + +--echo # Rows inserted with NULL DATETIME +SELECT COUNT(*) AS rows_inserted FROM t1; +SELECT COUNT(DISTINCT dt) AS distinct_timestamps FROM t1; + +let $lu1= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_n_index_lock_upgrades'`; +let $pu1= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_calls'`; +let $ou1= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_optim_err_underflows'`; +let $oo1= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_optim_err_overflows'`; +let $ps1= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_splits'`; +let $ma1= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_merge_attempts'`; +let $ms1= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_merge_successful'`; +let $rs1= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_reorg_successful'`; +let $pd1= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_discards'`; + +--disable_query_log +eval SET @lu01= $lu1 - $lu0; +eval SET @pu01= $pu1 - $pu0; +eval SET @ou01= $ou1 - $ou0; +eval SET @oo01= $oo1 - $oo0; +eval SET @ps01= $ps1 - $ps0; +eval SET @ma01= $ma1 - $ma0; +eval SET @ms01= $ms1 - $ms0; +eval SET @rs01= $rs1 - $rs0; +eval SET @pd01= $pd1 - $pd0; +--enable_query_log + +--echo # Index lock upgrades during inserts +SELECT @lu01 AS index_lock_upgrades_insert; +SELECT @pu01 AS pessimistic_update_calls_insert; +SELECT @ou01 AS optim_err_underflows_insert; +SELECT @oo01 AS optim_err_overflows_insert; +SELECT @ps01 AS index_page_splits_insert; +SELECT @ma01 AS index_page_merge_attempts_insert; +SELECT @ms01 AS index_page_merge_successful_insert; +SELECT @rs01 AS index_page_reorg_successful_insert; +SELECT @pd01 AS index_page_discards_insert; + +--echo # Now update all rows (batch update) +--echo # This changes DATETIME from NULL to a distinct timestamp per row, +--echo # triggering the pessimistic path due to record size change. + +UPDATE t1 JOIN seq_1_to_1000 s ON t1.id = s.seq +SET t1.dt= '2025-01-01' + INTERVAL s.seq SECOND; + +--echo # Rows updated +SELECT COUNT(DISTINCT dt) AS distinct_timestamps FROM t1; + +let $lu2= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_n_index_lock_upgrades'`; +let $pu2= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_calls'`; +let $ou2= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_optim_err_underflows'`; +let $oo2= `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'Innodb_btr_cur_pessimistic_update_optim_err_overflows'`; +let $ps2= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_splits'`; +let $ma2= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_merge_attempts'`; +let $ms2= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_merge_successful'`; +let $rs2= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_reorg_successful'`; +let $pd2= `SELECT COUNT_RESET FROM INFORMATION_SCHEMA.INNODB_METRICS + WHERE NAME = 'index_page_discards'`; + +--disable_query_log +eval SET @lu12= $lu2 - $lu1; +eval SET @pu12= $pu2 - $pu1; +eval SET @ou12= $ou2 - $ou1; +eval SET @oo12= $oo2 - $oo1; +eval SET @ps12= $ps2 - $ps1; +eval SET @ma12= $ma2 - $ma1; +eval SET @ms12= $ms2 - $ms1; +eval SET @rs12= $rs2 - $rs1; +eval SET @pd12= $pd2 - $pd1; +--enable_query_log + +--echo # Index lock upgrades during updates +SELECT @lu12 AS index_lock_upgrades_update; +SELECT @pu12 AS pessimistic_update_calls_update; +SELECT @ou12 AS optim_err_underflows_update; +SELECT @oo12 AS optim_err_overflows_update; +SELECT @ps12 AS index_page_splits_update; +SELECT @ma12 AS index_page_merge_attempts_update; +SELECT @ms12 AS index_page_merge_successful_update; +SELECT @rs12 AS index_page_reorg_successful_update; +SELECT @pd12 AS index_page_discards_update; + +--disable_query_log +SET GLOBAL innodb_monitor_disable= 'module_index'; +--disable_warnings +SET GLOBAL innodb_monitor_disable= default; +SET GLOBAL innodb_monitor_enable= default; +--enable_warnings +--enable_query_log + +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb_status_variables.test b/mysql-test/suite/innodb/t/innodb_status_variables.test index 6746a94530fcc..d4bbca5f08ef7 100644 --- a/mysql-test/suite/innodb/t/innodb_status_variables.test +++ b/mysql-test/suite/innodb/t/innodb_status_variables.test @@ -4,4 +4,8 @@ WHERE variable_name LIKE 'INNODB_%' AND variable_name NOT IN ('INNODB_ADAPTIVE_HASH_HASH_SEARCHES','INNODB_ADAPTIVE_HASH_NON_HASH_SEARCHES', 'INNODB_MEM_ADAPTIVE_HASH', - 'INNODB_BUFFERED_AIO_SUBMITTED','INNODB_BUFFER_POOL_PAGES_LATCHED'); + 'INNODB_BUFFERED_AIO_SUBMITTED','INNODB_BUFFER_POOL_PAGES_LATCHED', + 'INNODB_BTR_CUR_N_INDEX_LOCK_UPGRADES', + 'INNODB_BTR_CUR_PESSIMISTIC_UPDATE_CALLS', + 'INNODB_BTR_CUR_PESSIMISTIC_UPDATE_OPTIM_ERR_UNDERFLOWS', + 'INNODB_BTR_CUR_PESSIMISTIC_UPDATE_OPTIM_ERR_OVERFLOWS'); diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index 40a7305062ad5..53651f11185d7 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -105,6 +105,14 @@ ulint btr_cur_n_sea_old; #ifdef UNIV_DEBUG /* Flag to limit optimistic insert records */ uint btr_cur_limit_optimistic_insert_debug; +/** Number of times index lock was upgraded from SX to X */ +Atomic_counter btr_cur_n_index_lock_upgrades; +/** Number of times btr_cur_pessimistic_update() was called */ +Atomic_counter btr_cur_pessimistic_update_calls; +/** Number of times DB_UNDERFLOW was returned as optimistic update error in btr_cur_pessimistic_update() */ +Atomic_counter btr_cur_pessimistic_update_optim_err_underflows; +/** Number of times DB_OVERFLOW was returned as optimistic update error in btr_cur_pessimistic_update() */ +Atomic_counter btr_cur_pessimistic_update_optim_err_overflows; #endif /* UNIV_DEBUG */ /** In the optimistic insert, if the insert does not fit, but this much space @@ -1599,6 +1607,7 @@ ATTRIBUTE_COLD void mtr_t::index_lock_upgrade() index_lock *lock= static_cast(slot.object); lock->u_x_upgrade(SRW_LOCK_CALL); slot.type= MTR_MEMO_X_LOCK; + ut_d(++btr_cur_n_index_lock_upgrades); } /** Mark a non-leaf page "least recently used", but avoid invoking @@ -3602,10 +3611,10 @@ btr_cur_optimistic_update( goto func_exit; } - if (UNIV_UNLIKELY(page_get_data_size(page) + if (UNIV_UNLIKELY((new_rec_size < old_rec_size) && page_get_data_size(page) - old_rec_size + new_rec_size < BTR_CUR_PAGE_COMPRESS_LIMIT(index))) { - /* The page would become too empty */ + /* The page would become too empty due to record shrinkage */ err = DB_UNDERFLOW; goto func_exit; } @@ -3797,6 +3806,8 @@ btr_cur_pessimistic_update( block = btr_cur_get_block(cursor); index = cursor->index(); + ut_d(++btr_cur_pessimistic_update_calls); + ut_ad(mtr->memo_contains_flagged(&index->lock, MTR_MEMO_X_LOCK | MTR_MEMO_SX_LOCK)); ut_ad(mtr->memo_contains_flagged(block, MTR_MEMO_PAGE_X_FIX)); @@ -3822,6 +3833,9 @@ btr_cur_pessimistic_update( cursor, offsets, offsets_heap, update, cmpl_info, thr, trx_id, mtr); + ut_d(btr_cur_pessimistic_update_optim_err_underflows += (err == DB_UNDERFLOW)); + ut_d(btr_cur_pessimistic_update_optim_err_overflows += (err == DB_OVERFLOW)); + switch (err) { case DB_ZIP_OVERFLOW: case DB_UNDERFLOW: @@ -3992,8 +4006,17 @@ btr_cur_pessimistic_update( goto return_after_reservations; } - rec = btr_cur_insert_if_possible(cursor, new_entry, - offsets, offsets_heap, n_ext, mtr); + if (optim_err == DB_OVERFLOW + && !buf_block_get_page_zip(block) + && page_get_max_insert_size_after_reorganize( + block->page.frame, 1) < BTR_CUR_PAGE_REORGANIZE_LIMIT) { + /* The page is too full: force a split instead of + reinserting on the same page. */ + rec = NULL; + } else { + rec = btr_cur_insert_if_possible(cursor, new_entry, + offsets, offsets_heap, n_ext, mtr); + } if (rec) { page_cursor->rec = rec; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 2e905e8500f8f..bca9fa36b39c0 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -1071,6 +1071,17 @@ static SHOW_VAR innodb_status_variables[]= { /* InnoDB bulk operations */ {"bulk_operations", &export_vars.innodb_bulk_operations, SHOW_SIZE_T}, +#ifdef UNIV_DEBUG + {"btr_cur_n_index_lock_upgrades", + &btr_cur_n_index_lock_upgrades, SHOW_SIZE_T}, + {"btr_cur_pessimistic_update_calls", + &btr_cur_pessimistic_update_calls, SHOW_SIZE_T}, + {"btr_cur_pessimistic_update_optim_err_underflows", + &btr_cur_pessimistic_update_optim_err_underflows, SHOW_SIZE_T}, + {"btr_cur_pessimistic_update_optim_err_overflows", + &btr_cur_pessimistic_update_optim_err_overflows, SHOW_SIZE_T}, +#endif /* UNIV_DEBUG */ + {NullS, NullS, SHOW_LONG} }; diff --git a/storage/innobase/include/btr0cur.h b/storage/innobase/include/btr0cur.h index 53f88cc8ca1f5..24282330a35da 100644 --- a/storage/innobase/include/btr0cur.h +++ b/storage/innobase/include/btr0cur.h @@ -833,6 +833,14 @@ extern ulint btr_cur_n_sea_old; #ifdef UNIV_DEBUG /* Flag to limit optimistic insert records */ extern uint btr_cur_limit_optimistic_insert_debug; +/** Number of times index lock was upgraded from SX to X */ +extern Atomic_counter btr_cur_n_index_lock_upgrades; +/** Number of times btr_cur_pessimistic_update() was called */ +extern Atomic_counter btr_cur_pessimistic_update_calls; +/** Number of times DB_UNDERFLOW was returned as optimistic update error in btr_cur_pessimistic_update() */ +extern Atomic_counter btr_cur_pessimistic_update_optim_err_underflows; +/** Number of times DB_OVERFLOW was returned as optimistic update error in btr_cur_pessimistic_update() */ +extern Atomic_counter btr_cur_pessimistic_update_optim_err_overflows; #endif /* UNIV_DEBUG */ #include "btr0cur.inl"