commit fad17855775486f99a4716e79ba177c3efbf2d66 Author: zhongbei.yk <zhongbei.yk@alibaba-inc.com> Date: Tue Apr 2 11:05:51 2024 +0800 Bug#114154 Parallel read degrades to single-thread read Description =========== After a partition table is read in parallel for many times, it becomes very slow to read a common innodb table or a partition table in parallel. See bug#114154. Analysis ======== In the function ha_innopart::records(ha_rows *num_rows), if the local variable n_threads has a return value of 1, the parallel read will not be executed, but the s_active_threads variable will be increased by one and will bot be released, resulting in leakage of the s_active_threads variable. When the s_active_threads reaches 256, other sessions cannot obtain parallel read threads. Thus causing parallel read is invalid. Subsequent count(*)\chekck table\parallel DDL statements can only be executed single-threaded, thus slowing down. Fix === When the s_active_threads variable has already been increased but parallel read partition table is not executed, the s_active_threads variable needs to be released. diff --git a/mysql-test/suite/innodb/r/innodb_bug114154.result b/mysql-test/suite/innodb/r/innodb_bug114154.result new file mode 100644 index 00000000000..8b18ee267b0 --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_bug114154.result @@ -0,0 +1,68 @@ +##################################################################### +# 1. Prepare +##################################################################### +# Create a normal table +CREATE TABLE t1( +class INT, +id INT, +title VARCHAR(100), +PRIMARY KEY(class) +) ENGINE=InnoDB; +# Create a partition table +CREATE TABLE t2( +class INT, +id INT, +title VARCHAR(100), +PRIMARY KEY(class) +) ENGINE=InnoDB +PARTITION BY RANGE(class) ( +PARTITION p0 VALUES LESS THAN (5000), +PARTITION p1 VALUES LESS THAN (10000), +PARTITION p2 VALUES LESS THAN (15000)); +# Create a procedure to insert data +CREATE PROCEDURE populate_t1_t2() +BEGIN +DECLARE i int DEFAULT 1; +START TRANSACTION; +WHILE (i <= 10000) DO +INSERT INTO t1 VALUES (i, i, CONCAT('a', i)); +INSERT INTO t2 VALUES (i, i, CONCAT('a', i)); +SET i = i + 1; +END WHILE; +COMMIT; +END | +# Create a procedure to repeat execute 'select count(*) from partition table' +CREATE PROCEDURE select_count_partition_table() +BEGIN +DECLARE i int DEFAULT 1; +WHILE (i <= 256) DO +select count(*) from t2; +SET i = i + 1; +END WHILE; +END | +# Load data +CALL populate_t1_t2(); +##################################################################### +# 2. Repeat sql 'select count(*) from t2;' for 256 times. +##################################################################### +set innodb_parallel_read_threads = 1; +call select_count_partition_table(); +####################################################################### +# 3. Parallel read will degrade to single thread read before this patch +####################################################################### +set innodb_parallel_read_threads = 16; +set global DEBUG="+d,die_if_not_parallel_read"; +select count(*) from t1; +count(*) +10000 +select count(*) from t2; +count(*) +10000 +set global DEBUG="-d,die_if_not_parallel_read"; +##################################################################### +# 4. Cleanup +##################################################################### +DROP PROCEDURE populate_t1_t2; +DROP PROCEDURE select_count_partition_table; +DROP table t1; +DROP table t2; diff --git a/mysql-test/suite/innodb/t/innodb_bug114154.test b/mysql-test/suite/innodb/t/innodb_bug114154.test new file mode 100644 index 00000000000..d3fc1767f45 --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_bug114154.test @@ -0,0 +1,100 @@ +# +# Bug#114154 parallel read degrades to single thread read +# +# After a partition table is read in parallel for many times, the parallel +# read will degrade to single thread read. This test repeat the bug. +# +# ==== Implementation ==== +# 1. Prepare. +# 2. Do multiple parallel read on a partitioned table +# under innodb_parallel_read_threads = 1 to cause parallel threads leak. +# 3. Do parallel read under innodb_parallel_read_threads = 16, it +# will degrade to single thread read. +# 4. cleanup +# + +--source include/have_debug.inc + +--echo ##################################################################### +--echo # 1. Prepare +--echo ##################################################################### +--echo # Create a normal table +CREATE TABLE t1( + class INT, + id INT, + title VARCHAR(100), + PRIMARY KEY(class) +) ENGINE=InnoDB; + +--echo # Create a partition table +CREATE TABLE t2( + class INT, + id INT, + title VARCHAR(100), + PRIMARY KEY(class) +) ENGINE=InnoDB +PARTITION BY RANGE(class) ( +PARTITION p0 VALUES LESS THAN (5000), +PARTITION p1 VALUES LESS THAN (10000), +PARTITION p2 VALUES LESS THAN (15000)); + +--echo # Create a procedure to insert data +DELIMITER |; +CREATE PROCEDURE populate_t1_t2() +BEGIN + DECLARE i int DEFAULT 1; + + START TRANSACTION; + WHILE (i <= 10000) DO + INSERT INTO t1 VALUES (i, i, CONCAT('a', i)); + INSERT INTO t2 VALUES (i, i, CONCAT('a', i)); + SET i = i + 1; + END WHILE; + COMMIT; +END | + +--echo # Create a procedure to repeat execute 'select count(*) from partition table' +CREATE PROCEDURE select_count_partition_table() +BEGIN + DECLARE i int DEFAULT 1; + WHILE (i <= 256) DO + select count(*) from t2; + SET i = i + 1; + END WHILE; +END | +DELIMITER ;| + +-- disable_result_log +--echo # Load data +CALL populate_t1_t2(); +-- enable_result_log + +--echo ##################################################################### +--echo # 2. Repeat sql 'select count(*) from t2;' for 256 times. +--echo ##################################################################### +set innodb_parallel_read_threads = 1; + +-- disable_result_log +call select_count_partition_table(); +-- enable_result_log + +--echo ####################################################################### +--echo # 3. Parallel read will degrade to single thread read before this patch +--echo ####################################################################### +set innodb_parallel_read_threads = 16; + +set global DEBUG="+d,die_if_not_parallel_read"; + +select count(*) from t1; + +select count(*) from t2; + +set global DEBUG="-d,die_if_not_parallel_read"; + +--echo ##################################################################### +--echo # 4. Cleanup +--echo ##################################################################### +DROP PROCEDURE populate_t1_t2; +DROP PROCEDURE select_count_partition_table; +DROP table t1; +DROP table t2; \ No newline at end of file diff --git a/storage/innobase/handler/ha_innopart.cc b/storage/innobase/handler/ha_innopart.cc index abcc2965144..355bc0ac101 100644 --- a/storage/innobase/handler/ha_innopart.cc +++ b/storage/innobase/handler/ha_innopart.cc @@ -3200,6 +3200,9 @@ int ha_innopart::records(ha_rows *num_rows) { if (dict_table_is_discarded(m_prebuilt->table)) { ib_senderrf(ha_thd(), IB_LOG_LEVEL_ERROR, ER_TABLESPACE_DISCARDED, m_prebuilt->table->name.m_name); + /* When parallel read is not executed, it needs to be released. */ + Parallel_reader::release_threads(n_threads); + *num_rows = HA_POS_ERROR; return (HA_ERR_NO_SUCH_TABLE); } @@ -3230,6 +3233,9 @@ int ha_innopart::records(ha_rows *num_rows) { of the rest of the function is neglectable for each partition. So no current reason for optimizing this further. */ + /* When parallel read is not executed, it needs to be released. */ + Parallel_reader::release_threads(n_threads); + for (uint i = m_part_info->get_first_used_partition(); i < m_tot_parts; i = m_part_info->get_next_used_partition(i)) { set_partition(i); diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index 9902acd3243..0d0a776daff 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -4602,6 +4602,8 @@ dberr_t row_scan_index_for_mysql(row_prebuilt_t *prebuilt, dict_index_t *index, skip_parallel_read: #endif /* UNIV_DEBUG */ + DBUG_EXECUTE_IF("die_if_not_parallel_read", ut_error;); + bool contains_null; rec_t *rec = nullptr; ulint matched_fields;