diff --git a/include/my_base.h b/include/my_base.h index 758f11ed21214..248b1df59c004 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -231,7 +231,12 @@ enum ha_extra_function { /** Finish HA_EXTRA_BEGIN_COPY or HA_EXTRA_BEGIN_ALTER_IGNORE_COPY */ HA_EXTRA_END_COPY, /** Abort HA_EXTRA_BEGIN_COPY or HA_EXTRA_BEGIN_ALTER_IGNORE_COPY */ - HA_EXTRA_ABORT_COPY + HA_EXTRA_ABORT_COPY, + /* + Advise the engine that the next operation will be a full table or + full index scan + */ + HA_EXTRA_FULL_SCAN }; /* Compatible option, to be deleted in 6.0 */ diff --git a/mysql-test/include/innodb_table_lock_on_full_scan.combinations b/mysql-test/include/innodb_table_lock_on_full_scan.combinations new file mode 100644 index 0000000000000..88a3880dcac20 --- /dev/null +++ b/mysql-test/include/innodb_table_lock_on_full_scan.combinations @@ -0,0 +1,6 @@ +[row_lock] +--disable_innodb_table_lock_on_full_scan + +[table_lock] +--innodb_table_lock_on_full_scan + diff --git a/mysql-test/include/innodb_table_lock_on_full_scan.inc b/mysql-test/include/innodb_table_lock_on_full_scan.inc new file mode 100644 index 0000000000000..418d1036d6f2c --- /dev/null +++ b/mysql-test/include/innodb_table_lock_on_full_scan.inc @@ -0,0 +1,4 @@ +# The goal of including this file is to enable innodb_table_lock_on_full_scan combinations +# (see include/innodb_table_lock_on_full_scan.combinations) + +--source include/have_innodb.inc diff --git a/mysql-test/include/innodb_trx_weight.inc b/mysql-test/include/innodb_trx_weight.inc index a1e6427651139..91131ce7deaf5 100644 --- a/mysql-test/include/innodb_trx_weight.inc +++ b/mysql-test/include/innodb_trx_weight.inc @@ -25,12 +25,23 @@ SELECT * FROM t2 FOR UPDATE; INSERT INTO t2 VALUES (0); -- connection con2 - INSERT INTO t1 VALUES (0); + if (!$insert_deadlock) { + INSERT INTO t1 VALUES (0); + } + if ($insert_deadlock) { + --error ER_LOCK_DEADLOCK + INSERT INTO t1 VALUES (0); + } ROLLBACK; -- connection con1 - -- error ER_LOCK_DEADLOCK - -- reap + if (!$insert_deadlock) { + -- error ER_LOCK_DEADLOCK + -- reap + } + if ($insert_deadlock) { + -- reap + } -- } # else -- if (!$con1_should_be_rolledback) { diff --git a/mysql-test/main/innodb_full_scan,table_lock.rdiff b/mysql-test/main/innodb_full_scan,table_lock.rdiff new file mode 100644 index 0000000000000..5bf2581eb6042 --- /dev/null +++ b/mysql-test/main/innodb_full_scan,table_lock.rdiff @@ -0,0 +1,121 @@ +--- innodb_full_scan.result ++++ innodb_full_scan,table_lock.result +@@ -10,14 +10,14 @@ + 42 + COMMIT; + # lock_rec_created +-1 ++0 + BEGIN; + SELECT * FROM t for update; + a + 42 + COMMIT; + # lock_rec_created +-1 ++0 + ## SELECT without explicit transaction + SELECT * FROM t; + a +@@ -55,13 +55,13 @@ + UNION + select * from t1; + # lock_rec_created +-3 ++1 + insert into t2 + select * from t1 + UNION + select * from t1 where t1.a = 10; + # lock_rec_created +-3 ++1 + insert into t2 + select * from t1 where t1.a > 10 + UNION +@@ -73,7 +73,7 @@ + UNION + select * from t1 where t1.a > 10; + # lock_rec_created +-1 ++0 + drop table t1, t2; + # CREATE ... SELECT with UNION + create table t1 (a int primary key, b int) engine=innodb; +@@ -83,13 +83,13 @@ + UNION + select * from t1; + # lock_rec_created +-3 ++1 + create or replace table t2 (a int, b int) engine=innodb + select * from t1 + UNION + select * from t1 where t1.a = 10; + # lock_rec_created +-3 ++1 + create or replace table t2 (a int, b int) engine=innodb + select * from t1 where t1.a > 10 + UNION +@@ -101,14 +101,14 @@ + UNION + select * from t1 where t1.a > 10; + # lock_rec_created +-1 ++0 + drop table t1, t2; + # UPDATE + create table t1 (a int primary key, b int) engine=innodb; + insert into t1 select seq, seq from seq_1_to_100; + UPDATE t1 SET b = b + 3; + # lock_rec_created +-1 ++0 + UPDATE t1 SET b = b + 3 WHERE a = 10; + # lock_rec_created + 1 +@@ -118,7 +118,7 @@ + # DELETE + DELETE FROM t1; + # lock_rec_created +-1 ++0 + DELETE FROM t1 WHERE a = 10; + # lock_rec_created + 1 +@@ -135,7 +135,7 @@ + select * from t1 join t2 on t1.a = t2.a LOCK IN SHARE MODE; + commit; + # lock_rec_created +-2 ++1 + begin; + select * from t1 join t2 on t1.a = t2.b LOCK IN SHARE MODE; + commit; +@@ -145,7 +145,7 @@ + select * from t1 join t2 on t1.b = t2.b LOCK IN SHARE MODE; + commit; + # lock_rec_created +-2 ++1 + drop table t1, t2; + # Full index scan + create table t10 (a int, b varchar(100), index(a)) engine=innodb; +@@ -153,14 +153,14 @@ + create table t11(a int) engine=innodb; + insert into t11 select a from t10; + # lock_rec_created +-11 ++0 + drop table t10, t11; + create table t10 (a int, b varchar(100), index(a)) engine=innodb; + insert into t10 select seq, uuid() from seq_1_to_10000; + create table t11(a int) engine=innodb; + insert into t11 select a from t10 order by a desc; + # lock_rec_created +-11 ++0 + drop table t10, t11; + # Range access + create table t10 (a int, b varchar(100), index(a)) engine=innodb; diff --git a/mysql-test/main/innodb_full_scan.opt b/mysql-test/main/innodb_full_scan.opt new file mode 100644 index 0000000000000..dfce39955c89d --- /dev/null +++ b/mysql-test/main/innodb_full_scan.opt @@ -0,0 +1,2 @@ +--innodb_monitor_enable=lock_rec_lock_created + diff --git a/mysql-test/main/innodb_full_scan.result b/mysql-test/main/innodb_full_scan.result new file mode 100644 index 0000000000000..d8eddbdfda22a --- /dev/null +++ b/mysql-test/main/innodb_full_scan.result @@ -0,0 +1,176 @@ +# +# MDEV-24813 Locking full table scan fails to use table-level locking +# +CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB; +insert into t values (42); +# SELECT +BEGIN; +SELECT * FROM t LOCK IN SHARE MODE; +a +42 +COMMIT; +# lock_rec_created +1 +BEGIN; +SELECT * FROM t for update; +a +42 +COMMIT; +# lock_rec_created +1 +## SELECT without explicit transaction +SELECT * FROM t; +a +42 +# lock_rec_created +0 +# SELECT with a condition +BEGIN; +select * from t where a > 10 lock in share mode; +a +42 +COMMIT; +# lock_rec_created +1 +BEGIN; +SELECT * FROM t where a > 10 for update; +a +42 +COMMIT; +# lock_rec_created +1 +## SELECT with a condition without explicit transaction +SELECT * FROM t where a > 10; +a +42 +# lock_rec_created +0 +DROP TABLE t; +# INSERT INTO ... SELECT with UNION +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +create table t2 (a int, b int) engine=innodb; +insert into t2 +select * from t1 where t1.a = 10 +UNION +select * from t1; +# lock_rec_created +3 +insert into t2 +select * from t1 +UNION +select * from t1 where t1.a = 10; +# lock_rec_created +3 +insert into t2 +select * from t1 where t1.a > 10 +UNION +select * from t1; +# lock_rec_created +1 +insert into t2 +select * from t1 +UNION +select * from t1 where t1.a > 10; +# lock_rec_created +1 +drop table t1, t2; +# CREATE ... SELECT with UNION +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +create table t2 (a int, b int) engine=innodb +select * from t1 where t1.a = 10 +UNION +select * from t1; +# lock_rec_created +3 +create or replace table t2 (a int, b int) engine=innodb +select * from t1 +UNION +select * from t1 where t1.a = 10; +# lock_rec_created +3 +create or replace table t2 (a int, b int) engine=innodb +select * from t1 where t1.a > 10 +UNION +select * from t1; +# lock_rec_created +1 +create or replace table t2 (a int, b int) engine=innodb +select * from t1 +UNION +select * from t1 where t1.a > 10; +# lock_rec_created +1 +drop table t1, t2; +# UPDATE +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +UPDATE t1 SET b = b + 3; +# lock_rec_created +1 +UPDATE t1 SET b = b + 3 WHERE a = 10; +# lock_rec_created +1 +UPDATE t1 SET b = b + 3 WHERE a > 10; +# lock_rec_created +1 +# DELETE +DELETE FROM t1; +# lock_rec_created +1 +DELETE FROM t1 WHERE a = 10; +# lock_rec_created +1 +DELETE FROM t1 WHERE a > 10; +# lock_rec_created +1 +drop table t1; +# JOIN +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +create table t2 (a int primary key, b int) engine=innodb; +insert into t2 select seq, seq from seq_1_to_100; +begin; +select * from t1 join t2 on t1.a = t2.a LOCK IN SHARE MODE; +commit; +# lock_rec_created +2 +begin; +select * from t1 join t2 on t1.a = t2.b LOCK IN SHARE MODE; +commit; +# lock_rec_created +2 +begin; +select * from t1 join t2 on t1.b = t2.b LOCK IN SHARE MODE; +commit; +# lock_rec_created +2 +drop table t1, t2; +# Full index scan +create table t10 (a int, b varchar(100), index(a)) engine=innodb; +insert into t10 select seq, uuid() from seq_1_to_10000; +create table t11(a int) engine=innodb; +insert into t11 select a from t10; +# lock_rec_created +11 +drop table t10, t11; +create table t10 (a int, b varchar(100), index(a)) engine=innodb; +insert into t10 select seq, uuid() from seq_1_to_10000; +create table t11(a int) engine=innodb; +insert into t11 select a from t10 order by a desc; +# lock_rec_created +11 +drop table t10, t11; +# Range access +create table t10 (a int, b varchar(100), index(a)) engine=innodb; +insert into t10 select seq, uuid() from seq_1_to_10000; +create table t11(a int) engine=innodb; +explain +insert into t11 select a from t10 where a > 30; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10 range a a 5 NULL # Using where; Using index +insert into t11 select a from t10 where a > 30; +# lock_rec_created +11 +drop table t10, t11; diff --git a/mysql-test/main/innodb_full_scan.test b/mysql-test/main/innodb_full_scan.test new file mode 100644 index 0000000000000..31eaf40d669ec --- /dev/null +++ b/mysql-test/main/innodb_full_scan.test @@ -0,0 +1,291 @@ +--source include/have_sequence.inc +--source include/have_innodb.inc +--source include/no_view_protocol.inc +--source include/innodb_table_lock_on_full_scan.inc +--echo # +--echo # MDEV-24813 Locking full table scan fails to use table-level locking +--echo # + +CREATE TABLE t (a INT PRIMARY KEY) ENGINE=InnoDB; +insert into t values (42); + +--echo # SELECT +# The .opt file sets innodb_monitor_enable=lock_rec_lock_created. +# $rec_lock_created reads the counter; capture it into $start_val before each +# measured statement and into $end_val after, then print ($end_val - $start_val) +# as the per-statement number of record locks created. +let $rec_lock_created= SELECT COUNT FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE NAME = 'lock_rec_lock_created'; + +let $start_val= `$rec_lock_created`; +BEGIN; +SELECT * FROM t LOCK IN SHARE MODE; +COMMIT; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +BEGIN; +SELECT * FROM t for update; +COMMIT; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +--echo ## SELECT without explicit transaction +let $start_val= `$rec_lock_created`; +SELECT * FROM t; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +--echo # SELECT with a condition +let $start_val= `$rec_lock_created`; +BEGIN; +select * from t where a > 10 lock in share mode; +COMMIT; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +BEGIN; +SELECT * FROM t where a > 10 for update; +COMMIT; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +--echo ## SELECT with a condition without explicit transaction +let $start_val= `$rec_lock_created`; +SELECT * FROM t where a > 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +DROP TABLE t; + +--echo # INSERT INTO ... SELECT with UNION +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +create table t2 (a int, b int) engine=innodb; +let $start_val= `$rec_lock_created`; +insert into t2 + select * from t1 where t1.a = 10 + UNION + select * from t1; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +insert into t2 + select * from t1 + UNION + select * from t1 where t1.a = 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +insert into t2 + select * from t1 where t1.a > 10 + UNION + select * from t1; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +insert into t2 + select * from t1 + UNION + select * from t1 where t1.a > 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +drop table t1, t2; + +--echo # CREATE ... SELECT with UNION +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +let $start_val= `$rec_lock_created`; +create table t2 (a int, b int) engine=innodb + select * from t1 where t1.a = 10 + UNION + select * from t1; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +create or replace table t2 (a int, b int) engine=innodb + select * from t1 + UNION + select * from t1 where t1.a = 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +create or replace table t2 (a int, b int) engine=innodb + select * from t1 where t1.a > 10 + UNION + select * from t1; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +create or replace table t2 (a int, b int) engine=innodb + select * from t1 + UNION + select * from t1 where t1.a > 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +drop table t1, t2; + +--echo # UPDATE +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; + +let $start_val= `$rec_lock_created`; +UPDATE t1 SET b = b + 3; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +UPDATE t1 SET b = b + 3 WHERE a = 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +UPDATE t1 SET b = b + 3 WHERE a > 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +--echo # DELETE +let $start_val= `$rec_lock_created`; +DELETE FROM t1; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +DELETE FROM t1 WHERE a = 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +DELETE FROM t1 WHERE a > 10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +drop table t1; + +--echo # JOIN +create table t1 (a int primary key, b int) engine=innodb; +insert into t1 select seq, seq from seq_1_to_100; +create table t2 (a int primary key, b int) engine=innodb; +insert into t2 select seq, seq from seq_1_to_100; + +let $start_val= `$rec_lock_created`; +--disable_result_log +begin; +select * from t1 join t2 on t1.a = t2.a LOCK IN SHARE MODE; +commit; +--enable_result_log +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +--disable_result_log +begin; +select * from t1 join t2 on t1.a = t2.b LOCK IN SHARE MODE; +commit; +--enable_result_log +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +let $start_val= `$rec_lock_created`; +--disable_result_log +begin; +select * from t1 join t2 on t1.b = t2.b LOCK IN SHARE MODE; +commit; +--enable_result_log +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; + +drop table t1, t2; + +--echo # Full index scan +create table t10 (a int, b varchar(100), index(a)) engine=innodb; +insert into t10 select seq, uuid() from seq_1_to_10000; +create table t11(a int) engine=innodb; +let $start_val= `$rec_lock_created`; +insert into t11 select a from t10; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; +drop table t10, t11; + +create table t10 (a int, b varchar(100), index(a)) engine=innodb; +insert into t10 select seq, uuid() from seq_1_to_10000; +create table t11(a int) engine=innodb; +let $start_val= `$rec_lock_created`; +insert into t11 select a from t10 order by a desc; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; +drop table t10, t11; + +--echo # Range access +create table t10 (a int, b varchar(100), index(a)) engine=innodb; +insert into t10 select seq, uuid() from seq_1_to_10000; +create table t11(a int) engine=innodb; +--replace_column 9 # +explain +insert into t11 select a from t10 where a > 30; +let $start_val= `$rec_lock_created`; +insert into t11 select a from t10 where a > 30; +let $end_val= `$rec_lock_created`; +let $result= `SELECT $end_val - $start_val`; +echo # lock_rec_created; +echo $result; +drop table t10, t11; diff --git a/mysql-test/suite/innodb/r/autoinc_debug,table_lock.rdiff b/mysql-test/suite/innodb/r/autoinc_debug,table_lock.rdiff new file mode 100644 index 0000000000000..bb8db277dda69 --- /dev/null +++ b/mysql-test/suite/innodb/r/autoinc_debug,table_lock.rdiff @@ -0,0 +1,20 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/autoinc_debug.result 2025-02-19 14:14:39.056154959 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/autoinc_debug.reject 2026-03-05 16:31:06.934494369 +1100 +@@ -132,7 +132,7 @@ + # T1: Wait for (T3) auto increment lock on t1 causing T1 -> T3 -> T2 -> T1 deadlock + SET debug_dbug = '+d,innodb_deadlock_victim_self'; + INSERT INTO t1(col2) VALUES(200); +-ERROR HY000: Failed to read auto-increment value from storage engine ++ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + # The transaction should have been rolled back + SELECT * FROM t1; + col1 col2 +@@ -152,7 +152,7 @@ + SELECT * FROM t1; + col1 col2 + 1 100 +-2 100 ++3 100 + DROP TABLE t1; + SELECT * FROM t2; + col1 diff --git a/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked,table_lock.rdiff b/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked,table_lock.rdiff new file mode 100644 index 0000000000000..3133c1a29dcb0 --- /dev/null +++ b/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked,table_lock.rdiff @@ -0,0 +1,70 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked.result 2026-03-05 16:40:26.246544601 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked.reject 2026-03-12 11:08:57.116399743 +1100 +@@ -32,8 +32,7 @@ + 1 + COMMIT; + connection con2; +-id +-1 ++ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + COMMIT; + # The scenario when we bypass X<-S pair: + # , +@@ -57,16 +56,12 @@ + connection con1; + SET DEBUG_SYNC = 'now WAIT_FOR con3_will_wait'; + SELECT * FROM t1 FOR UPDATE; +-id +-1 + COMMIT; + connection con2; +-id +-1 ++ERROR HY000: Lock wait timeout exceeded; try restarting transaction + COMMIT; + connection con3; +-id +-1 ++ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + COMMIT; + # A variant of the above scenario: + # , +@@ -87,7 +82,8 @@ + INSERT INTO t1 VALUES (0); + ROLLBACK; + connection con2; +-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction ++id ++1 + COMMIT; + # More complicated scenario: + # , +@@ -155,8 +151,7 @@ + 1 + COMMIT; + connection con3; +-id +-1 ++ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + COMMIT; + # A secenario, where con1 has to bypass two transactions: + # +@@ -181,16 +176,12 @@ + connection con1; + SET DEBUG_SYNC = 'now WAIT_FOR con3_will_wait'; + SELECT * FROM t1 WHERE id=1 FOR UPDATE; +-id +-1 + COMMIT; + connection con2; +-id +-1 ++ERROR HY000: Lock wait timeout exceeded; try restarting transaction + COMMIT; + connection con3; +-id +-1 ++ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + COMMIT; + connection default; + disconnect con1; diff --git a/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked.result b/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked.result index 4e0cca428987a..0cd49e1625424 100644 --- a/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked.result +++ b/mysql-test/suite/innodb/r/avoid_deadlock_with_blocked.result @@ -1,3 +1,5 @@ +set @old_innodb_lock_wait_timeout=@@global.innodb_lock_wait_timeout; +set global innodb_lock_wait_timeout=1; connect stop_purge,localhost,root; START TRANSACTION WITH CONSISTENT SNAPSHOT; connect con1,localhost,root,,; @@ -196,3 +198,4 @@ disconnect con2; disconnect con3; disconnect stop_purge; DROP TABLE t1; +set global innodb_lock_wait_timeout=@old_innodb_lock_wait_timeout; diff --git a/mysql-test/suite/innodb/r/deadlock_on_lock_upgrade,table_lock.rdiff b/mysql-test/suite/innodb/r/deadlock_on_lock_upgrade,table_lock.rdiff new file mode 100644 index 0000000000000..65810af995688 --- /dev/null +++ b/mysql-test/suite/innodb/r/deadlock_on_lock_upgrade,table_lock.rdiff @@ -0,0 +1,12 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/deadlock_on_lock_upgrade.result 2025-02-19 14:14:39.056154959 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/deadlock_on_lock_upgrade.reject 2026-03-05 16:41:41.114018319 +1100 +@@ -69,8 +69,7 @@ + 2 + COMMIT; + connection waiter; +-id +-1 ++ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + connection default; + disconnect holder; + disconnect waiter; diff --git a/mysql-test/suite/innodb/r/innodb_lock_wait_timeout_1.result b/mysql-test/suite/innodb/r/innodb_lock_wait_timeout_1.result index 52b85cf64e272..031741c913a7e 100644 --- a/mysql-test/suite/innodb/r/innodb_lock_wait_timeout_1.result +++ b/mysql-test/suite/innodb/r/innodb_lock_wait_timeout_1.result @@ -258,7 +258,10 @@ connection con1; begin; # Since there is another distinct record in the derived table # the previous matching record in t1 -- (2,null) -- was unlocked. +set @old_innodb_lock_wait_timeout=@@innodb_lock_wait_timeout; +set innodb_lock_wait_timeout=1; delete from t1; +set innodb_lock_wait_timeout=@old_innodb_lock_wait_timeout; # We will need the contents of the table again. rollback; select * from t1; diff --git a/mysql-test/suite/innodb/r/insert_into_empty,32k,table_lock.rdiff b/mysql-test/suite/innodb/r/insert_into_empty,32k,table_lock.rdiff new file mode 100644 index 0000000000000..47c5872858f7f --- /dev/null +++ b/mysql-test/suite/innodb/r/insert_into_empty,32k,table_lock.rdiff @@ -0,0 +1,29 @@ +--- ../src/mysql-test/suite/innodb/r/insert_into_empty.result 2026-03-03 14:40:28.037988631 +1100 ++++ ../src/mysql-test/suite/innodb/r/insert_into_empty,32k.reject 2026-03-04 17:14:55.368845565 +1100 +@@ -162,13 +162,10 @@ + INSERT INTO t1 VALUES (0,1); + ERROR 21S01: Column count doesn't match value count at row 1 + INSERT INTO t1 VALUES (2); +-ERROR 23000: Duplicate entry '2' for key 'PRIMARY' + COMMIT; + SET GLOBAL innodb_limit_optimistic_insert_debug = @save_limit; + SELECT * FROM t1; + a +-0 +-1 + 2 + DROP TABLE t1; + # +@@ -446,12 +443,9 @@ + c09 text, c10 text, c11 text, c12 text) ENGINE=InnoDB; + SET GLOBAL INNODB_DEFAULT_ROW_FORMAT= COMPACT; + ALTER TABLE t1 FORCE; +-Warnings: +-Warning 139 Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. + INSERT IGNORE INTO t1 VALUES + (1, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)), + (2, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)); +-ERROR 42000: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. + CHECK TABLE t1; + Table Op Msg_type Msg_text + test.t1 check status OK diff --git a/mysql-test/suite/innodb/r/insert_into_empty,4k,table_lock.rdiff b/mysql-test/suite/innodb/r/insert_into_empty,4k,table_lock.rdiff new file mode 100644 index 0000000000000..c6770cb8f45d4 --- /dev/null +++ b/mysql-test/suite/innodb/r/insert_into_empty,4k,table_lock.rdiff @@ -0,0 +1,54 @@ +--- ../src/mysql-test/suite/innodb/r/insert_into_empty.result 2026-03-03 14:40:28.037988631 +1100 ++++ ../src/mysql-test/suite/innodb/r/insert_into_empty,4k.reject 2026-03-04 17:15:37.860553099 +1100 +@@ -162,13 +162,10 @@ + INSERT INTO t1 VALUES (0,1); + ERROR 21S01: Column count doesn't match value count at row 1 + INSERT INTO t1 VALUES (2); +-ERROR 23000: Duplicate entry '2' for key 'PRIMARY' + COMMIT; + SET GLOBAL innodb_limit_optimistic_insert_debug = @save_limit; + SELECT * FROM t1; + a +-0 +-1 + 2 + DROP TABLE t1; + # +@@ -451,7 +448,7 @@ + INSERT IGNORE INTO t1 VALUES + (1, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)), + (2, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)); +-ERROR 42000: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. ++ERROR 42000: Row size too large (> 1982). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. + CHECK TABLE t1; + Table Op Msg_type Msg_text + test.t1 check status OK +@@ -542,28 +539,6 @@ + commit; + DROP TABLE t1; + # +-# MDEV-35475 Assertion `!rec_offs_nth_extern(offsets1, n)' +-# failed in cmp_rec_rec_simple_field +-# +-CREATE TABLE t1(a BLOB, b VARCHAR(2048), PRIMARY KEY (b)) ENGINE=InnoDB; +-INSERT INTO t1 VALUES (REPEAT('x',4805),'a'), (REPEAT('x',16111),'b'), +-(REPEAT('x',65535),'c'), (REPEAT('x',11312),'d'), +-(REPEAT('x',35177),'e'), (REPEAT('x',65535),'f'), +-(REPEAT('x',1988),'g'), (NULL,REPEAT('x',2048)), +-(REPEAT('x',2503),'h'), (REPEAT('x',33152),'i'), +-(REPEAT('x',65535),'j'), (REPEAT('x',1988),'k'), +-(REPEAT('x',65535),'l'), (REPEAT('x',65535),'m'), +-(REPEAT('x',65535),'n'), (REPEAT('x',65535),'o'), +-(REPEAT('x',1988),'p'), (REPEAT('x',2503),'q'), +-(REPEAT('x',65535),'r'), (REPEAT('x',65535),'s'), +-(REPEAT('x',65535),'t'), (REPEAT('x',3169),'u'), +-(REPEAT('x',7071),'v'), (REPEAT('x',16111),'w'), +-(REPEAT('x',2325),'x'), (REPEAT('x',33152),'y'), +-(REPEAT('x',65535),'z'), (REPEAT('x',65535),'aa'), +-(REPEAT('x',16111),'bb'), (REPEAT('x',4805),'cc'), +-(REPEAT('x',65535),'dd'); +-DROP TABLE t1; +-# + # Assertion `page_dir_get_n_heap(new_page) == 2U' failed + # in dberr_t PageBulk::init() + # diff --git a/mysql-test/suite/innodb/r/insert_into_empty,64k,table_lock.rdiff b/mysql-test/suite/innodb/r/insert_into_empty,64k,table_lock.rdiff new file mode 100644 index 0000000000000..914d99406d1d8 --- /dev/null +++ b/mysql-test/suite/innodb/r/insert_into_empty,64k,table_lock.rdiff @@ -0,0 +1,29 @@ +--- ../src/mysql-test/suite/innodb/r/insert_into_empty.result 2026-03-03 14:40:28.037988631 +1100 ++++ ../src/mysql-test/suite/innodb/r/insert_into_empty,64k.reject 2026-03-04 17:15:09.304749650 +1100 +@@ -162,13 +162,10 @@ + INSERT INTO t1 VALUES (0,1); + ERROR 21S01: Column count doesn't match value count at row 1 + INSERT INTO t1 VALUES (2); +-ERROR 23000: Duplicate entry '2' for key 'PRIMARY' + COMMIT; + SET GLOBAL innodb_limit_optimistic_insert_debug = @save_limit; + SELECT * FROM t1; + a +-0 +-1 + 2 + DROP TABLE t1; + # +@@ -446,12 +443,9 @@ + c09 text, c10 text, c11 text, c12 text) ENGINE=InnoDB; + SET GLOBAL INNODB_DEFAULT_ROW_FORMAT= COMPACT; + ALTER TABLE t1 FORCE; +-Warnings: +-Warning 139 Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. + INSERT IGNORE INTO t1 VALUES + (1, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)), + (2, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)); +-ERROR 42000: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. + CHECK TABLE t1; + Table Op Msg_type Msg_text + test.t1 check status OK diff --git a/mysql-test/suite/innodb/r/insert_into_empty,8k,table_lock.rdiff b/mysql-test/suite/innodb/r/insert_into_empty,8k,table_lock.rdiff new file mode 100644 index 0000000000000..59a18a80065f7 --- /dev/null +++ b/mysql-test/suite/innodb/r/insert_into_empty,8k,table_lock.rdiff @@ -0,0 +1,54 @@ +--- ../src/mysql-test/suite/innodb/r/insert_into_empty.result 2026-03-03 14:40:28.037988631 +1100 ++++ ../src/mysql-test/suite/innodb/r/insert_into_empty,8k.reject 2026-03-04 17:15:50.648465080 +1100 +@@ -162,13 +162,10 @@ + INSERT INTO t1 VALUES (0,1); + ERROR 21S01: Column count doesn't match value count at row 1 + INSERT INTO t1 VALUES (2); +-ERROR 23000: Duplicate entry '2' for key 'PRIMARY' + COMMIT; + SET GLOBAL innodb_limit_optimistic_insert_debug = @save_limit; + SELECT * FROM t1; + a +-0 +-1 + 2 + DROP TABLE t1; + # +@@ -451,7 +448,7 @@ + INSERT IGNORE INTO t1 VALUES + (1, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)), + (2, REPEAT('x',4805), REPEAT('t',2211), REPEAT('u',974), REPEAT('e',871), REPEAT('z',224), REPEAT('j',978), REPEAT('n',190), REPEAT('t',888), REPEAT('x',32768), REPEAT('e',968), REPEAT('b',913), REPEAT('x',12107)); +-ERROR 42000: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. ++ERROR 42000: Row size too large (> 4030). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline. + CHECK TABLE t1; + Table Op Msg_type Msg_text + test.t1 check status OK +@@ -542,28 +539,6 @@ + commit; + DROP TABLE t1; + # +-# MDEV-35475 Assertion `!rec_offs_nth_extern(offsets1, n)' +-# failed in cmp_rec_rec_simple_field +-# +-CREATE TABLE t1(a BLOB, b VARCHAR(2048), PRIMARY KEY (b)) ENGINE=InnoDB; +-INSERT INTO t1 VALUES (REPEAT('x',4805),'a'), (REPEAT('x',16111),'b'), +-(REPEAT('x',65535),'c'), (REPEAT('x',11312),'d'), +-(REPEAT('x',35177),'e'), (REPEAT('x',65535),'f'), +-(REPEAT('x',1988),'g'), (NULL,REPEAT('x',2048)), +-(REPEAT('x',2503),'h'), (REPEAT('x',33152),'i'), +-(REPEAT('x',65535),'j'), (REPEAT('x',1988),'k'), +-(REPEAT('x',65535),'l'), (REPEAT('x',65535),'m'), +-(REPEAT('x',65535),'n'), (REPEAT('x',65535),'o'), +-(REPEAT('x',1988),'p'), (REPEAT('x',2503),'q'), +-(REPEAT('x',65535),'r'), (REPEAT('x',65535),'s'), +-(REPEAT('x',65535),'t'), (REPEAT('x',3169),'u'), +-(REPEAT('x',7071),'v'), (REPEAT('x',16111),'w'), +-(REPEAT('x',2325),'x'), (REPEAT('x',33152),'y'), +-(REPEAT('x',65535),'z'), (REPEAT('x',65535),'aa'), +-(REPEAT('x',16111),'bb'), (REPEAT('x',4805),'cc'), +-(REPEAT('x',65535),'dd'); +-DROP TABLE t1; +-# + # Assertion `page_dir_get_n_heap(new_page) == 2U' failed + # in dberr_t PageBulk::init() + # diff --git a/mysql-test/suite/innodb/r/insert_into_empty,table_lock.rdiff b/mysql-test/suite/innodb/r/insert_into_empty,table_lock.rdiff new file mode 100644 index 0000000000000..0c7e29831af67 --- /dev/null +++ b/mysql-test/suite/innodb/r/insert_into_empty,table_lock.rdiff @@ -0,0 +1,16 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/insert_into_empty.result 2026-03-03 14:40:28.037988631 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/insert_into_empty.reject 2026-03-04 17:22:46.425602626 +1100 +@@ -162,13 +162,10 @@ + INSERT INTO t1 VALUES (0,1); + ERROR 21S01: Column count doesn't match value count at row 1 + INSERT INTO t1 VALUES (2); +-ERROR 23000: Duplicate entry '2' for key 'PRIMARY' + COMMIT; + SET GLOBAL innodb_limit_optimistic_insert_debug = @save_limit; + SELECT * FROM t1; + a +-0 +-1 + 2 + DROP TABLE t1; + # diff --git a/mysql-test/suite/innodb/r/lock_delete_updated,table_lock.rdiff b/mysql-test/suite/innodb/r/lock_delete_updated,table_lock.rdiff new file mode 100644 index 0000000000000..e0b84fa891542 --- /dev/null +++ b/mysql-test/suite/innodb/r/lock_delete_updated,table_lock.rdiff @@ -0,0 +1,17 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/lock_delete_updated.result 2025-01-22 12:27:31.316598027 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/lock_delete_updated.reject 2026-03-05 14:27:02.066267800 +1100 +@@ -10,13 +10,12 @@ + UPDATE t SET a = 1; + COMMIT; + connection con1; +-ERROR 40001: Deadlock found when trying to get lock; try restarting transaction + disconnect con1; + connection default; + # The above DELETE must delete all the rows in the table, so the + # following SELECT must show 0 rows. + SELECT count(*) FROM t; + count(*) +-1 ++0 + SET DEBUG_SYNC="reset"; + DROP TABLE t; diff --git a/mysql-test/suite/innodb/r/lock_isolation,table_lock.rdiff b/mysql-test/suite/innodb/r/lock_isolation,table_lock.rdiff new file mode 100644 index 0000000000000..f4d02e940b42e --- /dev/null +++ b/mysql-test/suite/innodb/r/lock_isolation,table_lock.rdiff @@ -0,0 +1,11 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/lock_isolation.result 2025-09-05 14:46:23.964928913 +1000 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/lock_isolation.reject 2026-03-05 14:28:40.077672334 +1100 +@@ -110,7 +110,7 @@ + connection default; + SELECT * FROM t; + a b +-10 1 ++10 20 + 10 20 + TRUNCATE TABLE t; + # diff --git a/mysql-test/suite/innodb/r/lock_memory_debug,table_lock.rdiff b/mysql-test/suite/innodb/r/lock_memory_debug,table_lock.rdiff new file mode 100644 index 0000000000000..259f1185e2f30 --- /dev/null +++ b/mysql-test/suite/innodb/r/lock_memory_debug,table_lock.rdiff @@ -0,0 +1,12 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/lock_memory_debug.result 2025-05-07 10:38:15.869311858 +1000 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/lock_memory_debug.reject 2026-03-05 14:31:42.484616778 +1100 +@@ -6,8 +6,7 @@ + INSERT INTO t1 VALUES (1),(2),(3),(4),(5); + SET STATEMENT debug_dbug='+d,innodb_skip_lock_bitmap' FOR + INSERT INTO t1 SELECT a.* FROM t1 a, t1 b, t1 c, t1 d, t1 e, t1 f, t1 g; +-ERROR HY000: The total number of locks exceeds the lock table size + SELECT COUNT(*) FROM t1; + COUNT(*) +-5 ++78130 + DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/mdev-14846,table_lock.rdiff b/mysql-test/suite/innodb/r/mdev-14846,table_lock.rdiff new file mode 100644 index 0000000000000..755c2e4b391ef --- /dev/null +++ b/mysql-test/suite/innodb/r/mdev-14846,table_lock.rdiff @@ -0,0 +1,12 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/mdev-14846.result 2025-10-15 11:15:55.221082220 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/mdev-14846.reject 2026-03-05 16:11:28.967240949 +1100 +@@ -54,8 +54,8 @@ + 2 MATERIALIZED t3 ALL NULL NULL NULL NULL # + 2 MATERIALIZED t2 ALL NULL NULL NULL NULL # + UPDATE v4, t1 SET t1.pk = 76 WHERE t1.f2 IN ( SELECT t2.f FROM t2 INNER JOIN t3 ); +-connection default; + ERROR 40001: Deadlock found when trying to get lock; try restarting transaction ++connection default; + connection con1; + COMMIT; + disconnect con1; diff --git a/mysql-test/suite/innodb/r/row_lock,table_lock.rdiff b/mysql-test/suite/innodb/r/row_lock,table_lock.rdiff new file mode 100644 index 0000000000000..61d64d7ff63a7 --- /dev/null +++ b/mysql-test/suite/innodb/r/row_lock,table_lock.rdiff @@ -0,0 +1,12 @@ +--- /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/row_lock.result 2023-11-29 12:36:54.941275754 +1100 ++++ /home/ycp/source/mariadb-server/11.4/src/mysql-test/suite/innodb/r/row_lock.reject 2026-03-05 16:08:19.816626898 +1100 +@@ -11,8 +11,8 @@ + UPDATE t4 SET d = 1 WHERE d in ( SELECT a FROM t1 ) ORDER BY c LIMIT 6; + connection con11; + UPDATE t4 SET d = 9; +-connection con12; + ERROR 40001: Deadlock found when trying to get lock; try restarting transaction ++connection con12; + connection con11; + commit; + connection default; diff --git a/mysql-test/suite/innodb/t/autoinc_debug.test b/mysql-test/suite/innodb/t/autoinc_debug.test index f62cfd8afce09..c2b6ae855ae79 100644 --- a/mysql-test/suite/innodb/t/autoinc_debug.test +++ b/mysql-test/suite/innodb/t/autoinc_debug.test @@ -2,6 +2,7 @@ --source include/have_debug.inc --source include/have_debug_sync.inc --source include/not_embedded.inc +--source include/innodb_table_lock_on_full_scan.inc --disable_query_log call mtr.add_suppression("InnoDB: Transaction was aborted due to "); @@ -128,8 +129,14 @@ SAVEPOINT s1; SET DEBUG_SYNC='now WAIT_FOR t3_waiting'; --echo # T1: Wait for (T3) auto increment lock on t1 causing T1 -> T3 -> T2 -> T1 deadlock SET debug_dbug = '+d,innodb_deadlock_victim_self'; ---error ER_AUTOINC_READ_FAILED -INSERT INTO t1(col2) VALUES(200); +if ($MTR_COMBINATION_ROW_LOCK) { + --error ER_AUTOINC_READ_FAILED + INSERT INTO t1(col2) VALUES(200); +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK + INSERT INTO t1(col2) VALUES(200); +} --echo # The transaction should have been rolled back SELECT * FROM t1; diff --git a/mysql-test/suite/innodb/t/avoid_deadlock_with_blocked.test b/mysql-test/suite/innodb/t/avoid_deadlock_with_blocked.test index aa55b1ba00850..61c3d43eb5f00 100644 --- a/mysql-test/suite/innodb/t/avoid_deadlock_with_blocked.test +++ b/mysql-test/suite/innodb/t/avoid_deadlock_with_blocked.test @@ -2,11 +2,15 @@ --source include/have_debug.inc --source include/have_debug_sync.inc --source include/count_sessions.inc +--source include/innodb_table_lock_on_full_scan.inc --disable_query_log call mtr.add_suppression("InnoDB: Transaction was aborted due to "); --enable_query_log +set @old_innodb_lock_wait_timeout=@@global.innodb_lock_wait_timeout; +set global innodb_lock_wait_timeout=1; + connect stop_purge,localhost,root; START TRANSACTION WITH CONSISTENT SNAPSHOT; @@ -42,7 +46,13 @@ INSERT INTO t1 (id) VALUES (1); COMMIT; --connection con2 +if ($MTR_COMBINATION_ROW_LOCK) { + --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK --reap +} COMMIT; --echo # The scenario when we bypass X<-S pair: @@ -68,15 +78,35 @@ INSERT INTO t1 (id) VALUES (1); --connection con1 SET DEBUG_SYNC = 'now WAIT_FOR con3_will_wait'; +if ($MTR_COMBINATION_ROW_LOCK) { + SELECT * FROM t1 FOR UPDATE; +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --disable_result_log + --error 0,ER_LOCK_WAIT_TIMEOUT SELECT * FROM t1 FOR UPDATE; + --enable_result_log +} COMMIT; --connection con2 +if ($MTR_COMBINATION_ROW_LOCK) { --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_WAIT_TIMEOUT + --reap +} COMMIT; --connection con3 +if ($MTR_COMBINATION_ROW_LOCK) { + --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK --reap +} COMMIT; # @@ -100,8 +130,13 @@ INSERT INTO t1 (id) VALUES (1); ROLLBACK; --connection con2 +if ($MTR_COMBINATION_ROW_LOCK) { --error ER_LOCK_DEADLOCK --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --reap +} COMMIT; --echo # More complicated scenario: @@ -180,7 +215,13 @@ INSERT INTO t1 (id) VALUES (1); COMMIT; --connection con3 +if ($MTR_COMBINATION_ROW_LOCK) { + --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK --reap +} COMMIT; --echo # A secenario, where con1 has to bypass two transactions: @@ -206,15 +247,35 @@ INSERT INTO t1 (id) VALUES (1); --connection con1 SET DEBUG_SYNC = 'now WAIT_FOR con3_will_wait'; +if ($MTR_COMBINATION_ROW_LOCK) { + SELECT * FROM t1 WHERE id=1 FOR UPDATE; +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --disable_result_log + --error 0,ER_LOCK_WAIT_TIMEOUT SELECT * FROM t1 WHERE id=1 FOR UPDATE; + --enable_result_log +} COMMIT; --connection con2 +if ($MTR_COMBINATION_ROW_LOCK) { --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_WAIT_TIMEOUT + --reap +} COMMIT; --connection con3 +if ($MTR_COMBINATION_ROW_LOCK) { + --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK --reap +} COMMIT; --connection default @@ -225,4 +286,5 @@ INSERT INTO t1 (id) VALUES (1); DROP TABLE t1; +set global innodb_lock_wait_timeout=@old_innodb_lock_wait_timeout; --source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/innodb/t/deadlock_on_lock_upgrade.test b/mysql-test/suite/innodb/t/deadlock_on_lock_upgrade.test index 79adbe22021af..dfa7d3a6666cd 100644 --- a/mysql-test/suite/innodb/t/deadlock_on_lock_upgrade.test +++ b/mysql-test/suite/innodb/t/deadlock_on_lock_upgrade.test @@ -6,6 +6,7 @@ --source include/have_debug.inc --source include/have_debug_sync.inc --source include/count_sessions.inc +--source include/innodb_table_lock_on_full_scan.inc --connection default # There are various scenarious in which a transaction already holds "half" @@ -130,7 +131,13 @@ INSERT INTO t (`id`) VALUES (1), (2); COMMIT; --connection waiter +if ($MTR_COMBINATION_ROW_LOCK) { --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK + --reap +} --connection default diff --git a/mysql-test/suite/innodb/t/innodb_lock_wait_timeout_1.test b/mysql-test/suite/innodb/t/innodb_lock_wait_timeout_1.test index 56a86a2c4d90b..1021e35dd4864 100644 --- a/mysql-test/suite/innodb/t/innodb_lock_wait_timeout_1.test +++ b/mysql-test/suite/innodb/t/innodb_lock_wait_timeout_1.test @@ -1,4 +1,5 @@ --source include/have_innodb.inc +--source include/innodb_table_lock_on_full_scan.inc --echo # --echo # Bug #40113: Embedded SELECT inside UPDATE or DELETE can timeout @@ -199,7 +200,18 @@ connection con1; begin; --echo # Since there is another distinct record in the derived table --echo # the previous matching record in t1 -- (2,null) -- was unlocked. -delete from t1; +set @old_innodb_lock_wait_timeout=@@innodb_lock_wait_timeout; +set innodb_lock_wait_timeout=1; +if ($MTR_COMBINATION_ROW_LOCK) { + delete from t1; +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --disable_result_log + --error ER_LOCK_WAIT_TIMEOUT + delete from t1; + --enable_result_log +} +set innodb_lock_wait_timeout=@old_innodb_lock_wait_timeout; --echo # We will need the contents of the table again. rollback; select * from t1; diff --git a/mysql-test/suite/innodb/t/innodb_trx_weight.test b/mysql-test/suite/innodb/t/innodb_trx_weight.test index a8c29a1a8a847..3e8466df3e267 100644 --- a/mysql-test/suite/innodb/t/innodb_trx_weight.test +++ b/mysql-test/suite/innodb/t/innodb_trx_weight.test @@ -11,6 +11,7 @@ call mtr.add_suppression("InnoDB: Transaction was aborted due to "); --enable_query_log -- source include/have_innodb.inc +--source include/innodb_table_lock_on_full_scan.inc SET default_storage_engine=InnoDB; @@ -60,6 +61,7 @@ INSERT INTO t3 SELECT * FROM t3; # test locking weight +-- let $insert_deadlock = 0 -- let $con1_extra_sql = -- let $con1_extra_sql_present = 0 -- let $con2_extra_sql = SELECT * FROM t3 FOR UPDATE @@ -72,6 +74,9 @@ INSERT INTO t3 SELECT * FROM t3; -- let $con2_extra_sql = SELECT * FROM t3 FOR UPDATE -- let $con2_extra_sql_present = 1 -- let $con1_should_be_rolledback = 1 +if ($MTR_COMBINATION_TABLE_LOCK) { + -- let $insert_deadlock = 1 +} -- source include/innodb_trx_weight.inc -- let $con1_extra_sql = INSERT INTO t4 VALUES (1), (1), (1), (1), (1), (1) @@ -90,6 +95,7 @@ INSERT INTO t3 SELECT * FROM t3; -- let $con1_should_be_rolledback = 0 -- source include/innodb_trx_weight.inc +-- let $insert_deadlock = 0 -- let $con1_extra_sql = INSERT INTO t4 VALUES (1), (1), (1) -- let $con1_extra_sql_present = 1 -- let $con2_extra_sql = INSERT INTO t5_nontrans VALUES (1) diff --git a/mysql-test/suite/innodb/t/insert_into_empty.test b/mysql-test/suite/innodb/t/insert_into_empty.test index 9c544a3532eec..d42abfaa03e0c 100644 --- a/mysql-test/suite/innodb/t/insert_into_empty.test +++ b/mysql-test/suite/innodb/t/insert_into_empty.test @@ -1,5 +1,6 @@ --source include/have_innodb.inc --source include/innodb_page_size.inc +--source include/innodb_table_lock_on_full_scan.inc --source include/have_sequence.inc --source include/maybe_debug.inc --source include/have_partition.inc @@ -171,10 +172,19 @@ SET GLOBAL innodb_limit_optimistic_insert_debug = 2; BEGIN; SELECT * FROM t1 LOCK IN SHARE MODE; INSERT INTO t1 VALUES (0),(1),(2); +--disable_ps_protocol --error ER_WRONG_VALUE_COUNT_ON_ROW INSERT INTO t1 VALUES (0,1); ---error ER_DUP_ENTRY -INSERT INTO t1 VALUES (2); +--enable_ps_protocol +if ($MTR_COMBINATION_ROW_LOCK) { + --error ER_DUP_ENTRY + INSERT INTO t1 VALUES (2); +} +if ($MTR_COMBINATION_TABLE_LOCK) { +# SELECT..LOCK IN SHARE MODE takes a table lock and no record locks, +# so the empty table bulk insert stays enabled + INSERT INTO t1 VALUES (2); +} COMMIT; --error 0,ER_UNKNOWN_SYSTEM_VARIABLE diff --git a/mysql-test/suite/innodb/t/lock_delete_updated.test b/mysql-test/suite/innodb/t/lock_delete_updated.test index 8697ff595ab0b..60ed572e7d6dc 100644 --- a/mysql-test/suite/innodb/t/lock_delete_updated.test +++ b/mysql-test/suite/innodb/t/lock_delete_updated.test @@ -2,6 +2,7 @@ --source include/count_sessions.inc --source include/have_debug.inc --source include/have_debug_sync.inc +--source include/innodb_table_lock_on_full_scan.inc --disable_query_log call mtr.add_suppression("InnoDB: Transaction was aborted due to "); @@ -23,8 +24,13 @@ UPDATE t SET a = 1; COMMIT; connection con1; -error ER_LOCK_DEADLOCK; -reap; +if ($MTR_COMBINATION_ROW_LOCK) { + error ER_LOCK_DEADLOCK; + reap; +} +if ($MTR_COMBINATION_TABLE_LOCK) { + reap; +} disconnect con1; connection default; diff --git a/mysql-test/suite/innodb/t/lock_isolation.test b/mysql-test/suite/innodb/t/lock_isolation.test index 55305fab27e5f..d30b97f2be36e 100644 --- a/mysql-test/suite/innodb/t/lock_isolation.test +++ b/mysql-test/suite/innodb/t/lock_isolation.test @@ -2,6 +2,7 @@ --source include/count_sessions.inc --source include/have_debug.inc --source include/have_debug_sync.inc +--source include/innodb_table_lock_on_full_scan.inc --disable_query_log call mtr.add_suppression("InnoDB: Transaction was aborted due to "); diff --git a/mysql-test/suite/innodb/t/lock_memory_debug.test b/mysql-test/suite/innodb/t/lock_memory_debug.test index 58a76740dcb0b..c67b92ac002ee 100644 --- a/mysql-test/suite/innodb/t/lock_memory_debug.test +++ b/mysql-test/suite/innodb/t/lock_memory_debug.test @@ -1,5 +1,6 @@ --source include/have_innodb.inc --source include/have_debug.inc +--source include/innodb_table_lock_on_full_scan.inc --echo # --echo # MDEV-34166 Server could hang with BP < 80M under stress @@ -13,9 +14,15 @@ call mtr.add_suppression("InnoDB: Transaction was aborted due to "); CREATE TABLE t1 (col1 INT) ENGINE=InnoDB; INSERT INTO t1 VALUES (1),(2),(3),(4),(5); ---error ER_LOCK_TABLE_FULL -SET STATEMENT debug_dbug='+d,innodb_skip_lock_bitmap' FOR -INSERT INTO t1 SELECT a.* FROM t1 a, t1 b, t1 c, t1 d, t1 e, t1 f, t1 g; +if ($MTR_COMBINATION_ROW_LOCK) { + --error ER_LOCK_TABLE_FULL + SET STATEMENT debug_dbug='+d,innodb_skip_lock_bitmap' FOR + INSERT INTO t1 SELECT a.* FROM t1 a, t1 b, t1 c, t1 d, t1 e, t1 f, t1 g; +} +if ($MTR_COMBINATION_TABLE_LOCK) { + SET STATEMENT debug_dbug='+d,innodb_skip_lock_bitmap' FOR + INSERT INTO t1 SELECT a.* FROM t1 a, t1 b, t1 c, t1 d, t1 e, t1 f, t1 g; +} SELECT COUNT(*) FROM t1; diff --git a/mysql-test/suite/innodb/t/mdev-14846.test b/mysql-test/suite/innodb/t/mdev-14846.test index fac010871feda..d2e394fe14b26 100644 --- a/mysql-test/suite/innodb/t/mdev-14846.test +++ b/mysql-test/suite/innodb/t/mdev-14846.test @@ -3,6 +3,7 @@ --source include/have_debug_sync.inc --source include/innodb_stable_estimates.inc +--source include/innodb_table_lock_on_full_scan.inc --disable_query_log call mtr.add_suppression("InnoDB: Transaction was aborted due to "); @@ -63,13 +64,24 @@ disconnect con2; SET DEBUG_SYNC='now WAIT_FOR con1_dml2'; --replace_column 9 # explain UPDATE v4, t1 SET t1.pk = 76 WHERE t1.f2 IN ( SELECT t2.f FROM t2 INNER JOIN t3 ); -UPDATE v4, t1 SET t1.pk = 76 WHERE t1.f2 IN ( SELECT t2.f FROM t2 INNER JOIN t3 ); +if ($MTR_COMBINATION_ROW_LOCK) { + UPDATE v4, t1 SET t1.pk = 76 WHERE t1.f2 IN ( SELECT t2.f FROM t2 INNER JOIN t3 ); +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK + UPDATE v4, t1 SET t1.pk = 76 WHERE t1.f2 IN ( SELECT t2.f FROM t2 INNER JOIN t3 ); +} # It holds the record lock on table t1 and tries to acquire record lock on t3. # leads to deadlock (con1 trx is waiting for default trx and vice versa) --connection default ---error ER_LOCK_DEADLOCK ---reap +if ($MTR_COMBINATION_ROW_LOCK) { + --error ER_LOCK_DEADLOCK + --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --reap +} connection con1; COMMIT; diff --git a/mysql-test/suite/innodb/t/row_lock.test b/mysql-test/suite/innodb/t/row_lock.test index 2598189d0e2d6..df1792b19943e 100644 --- a/mysql-test/suite/innodb/t/row_lock.test +++ b/mysql-test/suite/innodb/t/row_lock.test @@ -9,6 +9,7 @@ call mtr.add_suppression("InnoDB: Transaction was aborted due to "); --enable_query_log --source include/have_innodb.inc +--source include/innodb_table_lock_on_full_scan.inc CREATE TABLE t1 (a INT, b INT) ENGINE=InnoDB; INSERT INTO t1 VALUES (1,1),(2,2); @@ -38,11 +39,22 @@ let $wait_condition= trx_query like "%SELECT a FROM t1%"; --source include/wait_condition.inc -UPDATE t4 SET d = 9; +if ($MTR_COMBINATION_ROW_LOCK) { + UPDATE t4 SET d = 9; +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --error ER_LOCK_DEADLOCK + UPDATE t4 SET d = 9; +} --connection con12 ---error ER_LOCK_DEADLOCK ---reap +if ($MTR_COMBINATION_ROW_LOCK) { + --error ER_LOCK_DEADLOCK + --reap +} +if ($MTR_COMBINATION_TABLE_LOCK) { + --reap +} --connection con11 commit; --connection default diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index cad0893798fc2..11e4b86afc08a 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -1594,6 +1594,18 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL +VARIABLE_NAME INNODB_TABLE_LOCK_ON_FULL_SCAN +SESSION_VALUE OFF +DEFAULT_VALUE OFF +VARIABLE_SCOPE SESSION +VARIABLE_TYPE BOOLEAN +VARIABLE_COMMENT When the SQL layer advises a full scan, acquire a single table lock instead of locking each scanned row individually. This is faster, at the cost of less granular (table-level instead of record-level) locking. +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,ON +READ_ONLY NO +COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME INNODB_TEMP_DATA_FILE_PATH SESSION_VALUE NULL DEFAULT_VALUE ibtmp1:12M:autoextend diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index e1b9e605a246a..7cccc5f751eb5 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -9496,6 +9496,8 @@ int ha_partition::extra(enum ha_extra_function operation) case HA_EXTRA_ABORT_COPY: case HA_EXTRA_BEGIN_ALTER_IGNORE_COPY: DBUG_RETURN(loop_partitions(extra_cb, &operation)); + case HA_EXTRA_FULL_SCAN: + break; default: { /* Temporary crash to discover what is wrong */ @@ -9572,6 +9574,8 @@ int ha_partition::extra_opt(enum ha_extra_function operation, ulong arg) case HA_EXTRA_CACHE: prepare_extra_cache(arg); DBUG_RETURN(0); + case HA_EXTRA_FULL_SCAN: + DBUG_RETURN(0); default: DBUG_ASSERT(0); } diff --git a/sql/handler.cc b/sql/handler.cc index 600a20edfe2cd..4c32fecb594e4 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -8697,6 +8697,13 @@ int get_select_field_pos(Alter_info *alter_info, int select_field_count, return select_field_pos; } +void init_table_full_scan_if_needed(TABLE *table, Item *cond, ha_rows limit) +{ + if (!cond) + table->file->extra_opt(HA_EXTRA_FULL_SCAN, + limit < ULONG_MAX ? (ulong) limit : ULONG_MAX); +} + bool Table_scope_and_contents_source_st::vers_check_system_fields( THD *thd, Alter_info *alter_info, const Lex_table_name &table_name, diff --git a/sql/handler.h b/sql/handler.h index d45e8c2d00b26..9b09c0e5ef0a0 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -5813,4 +5813,5 @@ int get_select_field_pos(Alter_info *alter_info, int select_field_count, #ifndef DBUG_OFF String dbug_format_row(TABLE *table, const uchar *rec, bool print_names= true); #endif /* DBUG_OFF */ +void init_table_full_scan_if_needed(TABLE *table, Item *cond, ha_rows limit); #endif /* HANDLER_INCLUDED */ diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index fed5b4c7f7ec8..15577fbe64e4d 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -774,6 +774,7 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd) if (select && select->quick && select->quick->reset()) goto got_error; + init_table_full_scan_if_needed(table, conds, limit); if (query_plan.index == MAX_KEY || (select && select->quick)) error= init_read_record(&info, thd, table, select, file_sort, 1, 1, FALSE); else diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 633b65499b578..9abb266e2d109 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -25338,6 +25338,7 @@ int test_if_use_dynamic_range_scan(JOIN_TAB *join_tab) int join_init_read_record(JOIN_TAB *tab) { bool need_unpacking= FALSE; + /* TODO: s/tab->join/join/g in this function */ JOIN *join= tab->join; /* Note: the query plan tree for the below operations is constructed in @@ -25387,7 +25388,11 @@ int join_init_read_record(JOIN_TAB *tab) */ save_copy= tab->read_record.copy_field; save_copy_end= tab->read_record.copy_field_end; - + + init_table_full_scan_if_needed(tab->table, + tab->select ? tab->select->cond : NULL, + join->unit->lim.get_select_limit()); + /* JT_NEXT means that we should use an index scan on index 'tab->index' However if filesort is set, the table was already sorted above @@ -25459,6 +25464,9 @@ join_read_first(JOIN_TAB *tab) tab->table->status=0; tab->read_record.read_record_func= join_read_next; tab->read_record.table=table; + init_table_full_scan_if_needed(tab->table, + tab->select ? tab->select->cond : NULL, + tab->join->unit->lim.get_select_limit()); if (!table->file->inited) error= table->file->ha_index_init(tab->index, tab->sorted); if (likely(!error)) @@ -25498,6 +25506,9 @@ join_read_last(JOIN_TAB *tab) tab->table->status=0; tab->read_record.read_record_func= join_read_prev; tab->read_record.table=table; + init_table_full_scan_if_needed(tab->table, + tab->select ? tab->select->cond : NULL, + tab->join->unit->lim.get_select_limit()); if (!table->file->inited) error= table->file->ha_index_init(tab->index, 1); if (likely(!error)) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 76e3b7fb47ca8..0a76d3b8bbf38 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -791,6 +791,8 @@ bool Sql_cmd_update::update_single_table(THD *thd) Full index scan must be started with init_read_record_idx */ + init_table_full_scan_if_needed(table, conds, limit); + if (query_plan.index == MAX_KEY || (select && select->quick)) error= init_read_record(&info, thd, table, select, NULL, 0, 1, FALSE); else @@ -886,6 +888,7 @@ bool Sql_cmd_update::update_single_table(THD *thd) if (select && select->quick && select->quick->reset()) goto err; table->file->try_semi_consistent_read(1); + init_table_full_scan_if_needed(table, conds, limit); if (init_read_record(&info, thd, table, select, file_sort, 0, 1, FALSE)) goto err; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 4a0b72651f8f2..57e528a027cdf 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -864,6 +864,12 @@ static MYSQL_THDVAR_BOOL(ft_enable_stopword, PLUGIN_VAR_OPCMDARG, NULL, NULL, /* default */ TRUE); +static MYSQL_THDVAR_BOOL(table_lock_on_full_scan, PLUGIN_VAR_OPCMDARG, + "When the SQL layer advises a full scan, acquire a single table lock " + "instead of locking each scanned row individually. This is faster, at the " + "cost of less granular (table-level instead of record-level) locking.", + NULL, NULL, FALSE); + static MYSQL_THDVAR_UINT(lock_wait_timeout, PLUGIN_VAR_RQCMDARG, "Timeout in seconds an InnoDB transaction may wait for a lock before being rolled back. The value 100000000 is infinite timeout.", NULL, NULL, 50, 0, 100000000, 0); @@ -15940,6 +15946,33 @@ ha_innobase::extra( return(0); } +/** SQL layer calls this function (via init_table_full_scan_if_needed()) +with HA_EXTRA_FULL_SCAN when a statement reads a whole table or +index with no WHERE condition. When innodb_table_lock_on_full_scan +is enabled, flag the scan as covering the entire table. +row_search_mvcc() takes a single table-level lock (LOCK_S/LOCK_X) +instead of locking each scanned row individually. A bounded +scan (arg < ULONG_MAX) keeps per-row locking, since it may stop early. +Every other operation is forwarded unchanged to extra(). +@param operation hint to act on +@param arg operation argument; for HA_EXTRA_FULL_SCAN the row + limit, or ULONG_MAX when the scan is unbounded +@return 0 on success, otherwise the error code returned by extra() */ +int ha_innobase::extra_opt(enum ha_extra_function operation, ulong arg) +{ + switch (operation) + { + case HA_EXTRA_FULL_SCAN: + if (THDVAR(ha_thd(), table_lock_on_full_scan) && + !m_prebuilt->skip_locked && arg == ULONG_MAX) + m_prebuilt->full_table_scan = true; + return 0; + default:/* Do nothing */ + ; + } + return extra(operation); +} + /** MySQL calls this method at the end of each statement */ int @@ -15960,6 +15993,7 @@ ha_innobase::reset() m_prebuilt->autoinc_last_value = 0; m_prebuilt->skip_locked = false; + m_prebuilt->full_table_scan = false; return(0); } @@ -19926,6 +19960,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= { MYSQL_SYSVAR(ft_num_word_optimize), MYSQL_SYSVAR(ft_sort_pll_degree), MYSQL_SYSVAR(lock_wait_timeout), + MYSQL_SYSVAR(table_lock_on_full_scan), MYSQL_SYSVAR(deadlock_detect), MYSQL_SYSVAR(deadlock_report), MYSQL_SYSVAR(page_size), diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 9ac4eed4f1f52..a499a20ee4261 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -177,6 +177,8 @@ class ha_innobase final : public handler int extra(ha_extra_function operation) override; + int extra_opt(ha_extra_function operation, ulong arg) override; + int reset() override; int external_lock(THD *thd, int lock_type) override; diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 63858f25f023e..b1dcb147b2a22 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -695,6 +695,11 @@ struct row_prebuilt_t { /** The MySQL table object */ TABLE* m_mysql_table; + /** If TRUE, the SQL layer has advised that we're doing full scan. + A locking read will acquire a table-level lock (X or S) instead + of acquiring row-level locks. */ + bool full_table_scan; + /** Get template by dict_table_t::cols[] number */ const mysql_row_templ_t* get_template_by_col(ulint col) const { diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index fcbc9e955599d..64c6c3404c456 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -4718,8 +4718,10 @@ row_search_mvcc( } else { wait_table_again: err = lock_table(prebuilt->table, nullptr, - prebuilt->select_lock_type == LOCK_S - ? LOCK_IS : LOCK_IX, thr); + prebuilt->full_table_scan ? + prebuilt->select_lock_type : + (prebuilt->select_lock_type == LOCK_S + ? LOCK_IS : LOCK_IX), thr); if (err != DB_SUCCESS) { diff --git a/storage/mroonga/ha_mroonga.cpp b/storage/mroonga/ha_mroonga.cpp index 37a690e11880c..933deee7221cd 100644 --- a/storage/mroonga/ha_mroonga.cpp +++ b/storage/mroonga/ha_mroonga.cpp @@ -564,6 +564,9 @@ static const char *mrn_inspect_extra_function(enum ha_extra_function operation) case HA_EXTRA_BEGIN_ALTER_IGNORE_COPY: inspected = "HA_EXTRA_BEGIN_ALTER_IGNORE_COPY"; break; + case HA_EXTRA_FULL_SCAN: + inspected = "HA_EXTRA_FULL_SCAN"; + break; #ifdef MRN_HAVE_HA_EXTRA_EXPORT case HA_EXTRA_EXPORT: inspected = "HA_EXTRA_EXPORT";