Skip to content
Permalink
Browse files

Add option to exclude tables from gap lock check

Summary:
Similar to what was done in D49035, a new sysvar was added called `gap_lock_exceptions` that takes a comma separated list of regexes that specify which tables should skip gap lock detection. If it is skipped, then we do not raise errors, and we do not log to the gaplock log.

In order to do this, I've moved some logic from the collations check to `sql/handler.cc`. I've also made changes the mutex that protected the table list into a rwlock.

Fixes #232

Test Plan: mtr

Reviewers: yoshinorim, hermanlee4, jkedgar

Reviewed By: jkedgar

Subscribers: webscalesql-eng

Differential Revision: https://reviews.facebook.net/D56937
  • Loading branch information...
lth committed Apr 19, 2016
1 parent 292d546 commit 9b439ee4010d0db8f48b206bc16a53c1ea293744
@@ -290,6 +290,9 @@ The following options may be given as the first argument:
Number of best matches to use for query expansion
--ft-stopword-file=name
Use stopwords from this file instead of built-in list
--gap-lock-exceptions[=name]
List of tables (using regex) that are excluded from gap
lock detection.
--gap-lock-log-file=name
Log file path where queries using Gap Lock are written.
gap_lock_write_log needs to be turned on to write logs
@@ -1563,6 +1566,7 @@ ft-max-word-len 84
ft-min-word-len 4
ft-query-expansion-limit 20
ft-stopword-file (No default value)
gap-lock-exceptions (No default value)
gap-lock-raise-error FALSE
gap-lock-write-log FALSE
gdb FALSE
@@ -290,6 +290,9 @@ The following options may be given as the first argument:
Number of best matches to use for query expansion
--ft-stopword-file=name
Use stopwords from this file instead of built-in list
--gap-lock-exceptions[=name]
List of tables (using regex) that are excluded from gap
lock detection.
--gap-lock-log-file=name
Log file path where queries using Gap Lock are written.
gap_lock_write_log needs to be turned on to write logs
@@ -1561,6 +1564,7 @@ ft-max-word-len 84
ft-min-word-len 4
ft-query-expansion-limit 20
ft-stopword-file (No default value)
gap-lock-exceptions (No default value)
gap-lock-raise-error FALSE
gap-lock-write-log FALSE
gdb FALSE
@@ -400,3 +400,77 @@ DROP DATABASE mysqlslap;
SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
flush general logs;
SET @save_gap_lock_exceptions = @@global.gap_lock_exceptions;
SET GLOBAL gap_lock_exceptions="t.*";
drop table if exists gap1,gap2,gap3;
CREATE DATABASE mysqlslap;
CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
PRIMARY KEY (id1, id2, id3),
INDEX i (c1)) ENGINE=innodb;
CREATE TABLE gap2 like gap1;
CREATE TABLE gap3 (id INT, value INT,
PRIMARY KEY (id),
UNIQUE KEY ui(value)) ENGINE=innodb;
insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
create table gap4 (
pk int primary key,
a int,
b int,
key(a)
) ENGINE=innodb;
insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
create table gap5 like gap4;
insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
set session gap_lock_raise_error=1;
set session gap_lock_write_log=1;
set session autocommit=0;
select * from gap1 limit 1 for update;
ERROR HY000: Using Gap Lock without full primary key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all primary key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 limit 1 for update
select * from gap1 where value != 100 limit 1 for update;
ERROR HY000: Using Gap Lock without full primary key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all primary key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where value != 100 limit 1 for update
set global gap_lock_write_log= 0;
set global gap_lock_raise_error= 0;
drop table if exists gap1, gap2, gap3, gap4, gap5;
DROP DATABASE mysqlslap;
0
SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
flush general logs;
SET GLOBAL gap_lock_exceptions="gap.*";
drop table if exists gap1,gap2,gap3;
CREATE DATABASE mysqlslap;
CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
PRIMARY KEY (id1, id2, id3),
INDEX i (c1)) ENGINE=innodb;
CREATE TABLE gap2 like gap1;
CREATE TABLE gap3 (id INT, value INT,
PRIMARY KEY (id),
UNIQUE KEY ui(value)) ENGINE=innodb;
insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
create table gap4 (
pk int primary key,
a int,
b int,
key(a)
) ENGINE=innodb;
insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
create table gap5 like gap4;
insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
set session gap_lock_raise_error=1;
set session gap_lock_write_log=1;
set session autocommit=0;
select * from gap1 limit 1 for update;
id1 id2 id3 c1 value
0 0 1 1 1
select * from gap1 where value != 100 limit 1 for update;
id1 id2 id3 c1 value
0 0 1 1 1
set global gap_lock_write_log= 0;
set global gap_lock_raise_error= 0;
drop table if exists gap1, gap2, gap3, gap4, gap5;
DROP DATABASE mysqlslap;
0
SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
flush general logs;
SET GLOBAL gap_lock_exceptions=@save_gap_lock_exceptions;
@@ -3,4 +3,26 @@
let $engine=innodb;
--source include/gap_lock_raise_error_all.inc

SET @save_gap_lock_exceptions = @@global.gap_lock_exceptions;

SET GLOBAL gap_lock_exceptions="t.*";
--source include/gap_lock_raise_error_init.inc

set session autocommit=0;
--error ER_UNKNOWN_ERROR
select * from gap1 limit 1 for update;
--error ER_UNKNOWN_ERROR
select * from gap1 where value != 100 limit 1 for update;

--source include/gap_lock_raise_error_cleanup.inc

SET GLOBAL gap_lock_exceptions="gap.*";
--source include/gap_lock_raise_error_init.inc

set session autocommit=0;
select * from gap1 limit 1 for update;
select * from gap1 where value != 100 limit 1 for update;

--source include/gap_lock_raise_error_cleanup.inc

SET GLOBAL gap_lock_exceptions=@save_gap_lock_exceptions;
@@ -25,10 +25,10 @@ wait/synch/rwlock/sql/Binlog_storage_delegate::lock YES YES
wait/synch/rwlock/sql/Binlog_transmit_delegate::lock YES YES
wait/synch/rwlock/sql/gtid_commit_rollback YES YES
wait/synch/rwlock/sql/LOCK_dboptions YES YES
wait/synch/rwlock/sql/LOCK_gap_lock_exceptions YES YES
wait/synch/rwlock/sql/LOCK_grant YES YES
wait/synch/rwlock/sql/LOCK_system_variables_hash YES YES
wait/synch/rwlock/sql/LOCK_sys_init_connect YES YES
wait/synch/rwlock/sql/LOCK_sys_init_slave YES YES
select * from performance_schema.setup_instruments
where name like 'Wait/Synch/Cond/sql/%'
and name not in (
@@ -400,3 +400,77 @@ DROP DATABASE mysqlslap;
SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
flush general logs;
SET @save_gap_lock_exceptions = @@global.gap_lock_exceptions;
SET GLOBAL gap_lock_exceptions="t.*";
drop table if exists gap1,gap2,gap3;
CREATE DATABASE mysqlslap;
CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
PRIMARY KEY (id1, id2, id3),
INDEX i (c1)) ENGINE=rocksdb;
CREATE TABLE gap2 like gap1;
CREATE TABLE gap3 (id INT, value INT,
PRIMARY KEY (id),
UNIQUE KEY ui(value)) ENGINE=rocksdb;
insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
create table gap4 (
pk int primary key,
a int,
b int,
key(a)
) ENGINE=rocksdb;
insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
create table gap5 like gap4;
insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
set session gap_lock_raise_error=1;
set session gap_lock_write_log=1;
set session autocommit=0;
select * from gap1 limit 1 for update;
ERROR HY000: Using Gap Lock without full primary key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all primary key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 limit 1 for update
select * from gap1 where value != 100 limit 1 for update;
ERROR HY000: Using Gap Lock without full primary key in multi-table or multi-statement transactions is not allowed. You need either 1: Execute 'SET SESSION gap_lock_raise_error=0' if you are sure that your application does not rely on Gap Lock. 2: Rewrite queries to use all primary key columns in WHERE equal conditions. 3: Rewrite to single-table, single-statement transaction. Query: select * from gap1 where value != 100 limit 1 for update
set global gap_lock_write_log= 0;
set global gap_lock_raise_error= 0;
drop table if exists gap1, gap2, gap3, gap4, gap5;
DROP DATABASE mysqlslap;
0
SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
flush general logs;
SET GLOBAL gap_lock_exceptions="gap.*";
drop table if exists gap1,gap2,gap3;
CREATE DATABASE mysqlslap;
CREATE TABLE gap1 (id1 INT, id2 INT, id3 INT, c1 INT, value INT,
PRIMARY KEY (id1, id2, id3),
INDEX i (c1)) ENGINE=rocksdb;
CREATE TABLE gap2 like gap1;
CREATE TABLE gap3 (id INT, value INT,
PRIMARY KEY (id),
UNIQUE KEY ui(value)) ENGINE=rocksdb;
insert into gap3 values (1,1), (2,2),(3,3),(4,4),(5,5);
create table gap4 (
pk int primary key,
a int,
b int,
key(a)
) ENGINE=rocksdb;
insert into gap4 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
create table gap5 like gap4;
insert into gap5 values (1,1,1), (2,2,2), (3,3,3), (4,4,4);
set session gap_lock_raise_error=1;
set session gap_lock_write_log=1;
set session autocommit=0;
select * from gap1 limit 1 for update;
id1 id2 id3 c1 value
0 0 1 1 1
select * from gap1 where value != 100 limit 1 for update;
id1 id2 id3 c1 value
0 0 1 1 1
set global gap_lock_write_log= 0;
set global gap_lock_raise_error= 0;
drop table if exists gap1, gap2, gap3, gap4, gap5;
DROP DATABASE mysqlslap;
0
SET GLOBAL gap_lock_log_file='<GAP_LOCK_ORIG>';
SET GLOBAL gap_lock_log_file='<GAP_LOCK>';
flush general logs;
SET GLOBAL gap_lock_exceptions=@save_gap_lock_exceptions;
@@ -3,4 +3,26 @@
let $engine=rocksdb;
--source include/gap_lock_raise_error_all.inc

SET @save_gap_lock_exceptions = @@global.gap_lock_exceptions;

SET GLOBAL gap_lock_exceptions="t.*";
--source include/gap_lock_raise_error_init.inc

set session autocommit=0;
--error ER_UNKNOWN_ERROR
select * from gap1 limit 1 for update;
--error ER_UNKNOWN_ERROR
select * from gap1 where value != 100 limit 1 for update;

--source include/gap_lock_raise_error_cleanup.inc

SET GLOBAL gap_lock_exceptions="gap.*";
--source include/gap_lock_raise_error_init.inc

set session autocommit=0;
select * from gap1 limit 1 for update;
select * from gap1 where value != 100 limit 1 for update;

--source include/gap_lock_raise_error_cleanup.inc

SET GLOBAL gap_lock_exceptions=@save_gap_lock_exceptions;
@@ -0,0 +1,36 @@
SET @start_global_value = @@global.GAP_LOCK_EXCEPTIONS;
SELECT @start_global_value;
@start_global_value
NULL
"Trying to set @session.GAP_LOCK_EXCEPTIONS to simple table name."
SET @@global.GAP_LOCK_EXCEPTIONS = mytable;
SELECT @@global.GAP_LOCK_EXCEPTIONS;
@@global.GAP_LOCK_EXCEPTIONS
mytable
"Trying to set @session.GAP_LOCK_EXCEPTIONS to regex table name(s)."
SET @@global.GAP_LOCK_EXCEPTIONS = "t.*";
SELECT @@global.GAP_LOCK_EXCEPTIONS;
@@global.GAP_LOCK_EXCEPTIONS
t.*
"Trying to set @session.GAP_LOCK_EXCEPTIONS to multiple regex table names."
SET @@global.GAP_LOCK_EXCEPTIONS = "s.*,t.*";
SELECT @@global.GAP_LOCK_EXCEPTIONS;
@@global.GAP_LOCK_EXCEPTIONS
s.*,t.*
"Trying to set @session.GAP_LOCK_EXCEPTIONS to empty."
SET @@global.GAP_LOCK_EXCEPTIONS = "";
SELECT @@global.GAP_LOCK_EXCEPTIONS;
@@global.GAP_LOCK_EXCEPTIONS

"Trying to set @session.GAP_LOCK_EXCEPTIONS to default."
SET @@global.GAP_LOCK_EXCEPTIONS = DEFAULT;
SELECT @@global.GAP_LOCK_EXCEPTIONS;
@@global.GAP_LOCK_EXCEPTIONS
NULL
"Trying to set @session.GAP_LOCK_EXCEPTIONS to 444. It should fail because it is not session."
SET @@session.GAP_LOCK_EXCEPTIONS = 444;
ERROR HY000: Variable 'gap_lock_exceptions' is a GLOBAL variable and should be set with SET GLOBAL
SET @@global.GAP_LOCK_EXCEPTIONS = @start_global_value;
SELECT @@global.GAP_LOCK_EXCEPTIONS;
@@global.GAP_LOCK_EXCEPTIONS
NULL
@@ -0,0 +1,33 @@
# We cannot use the rocskdb_sys_var.inc script as some of the strings we set
# need to be quoted and that doesn't work with this script. Run through valid
# options by hand.

SET @start_global_value = @@global.GAP_LOCK_EXCEPTIONS;
SELECT @start_global_value;

--echo "Trying to set @session.GAP_LOCK_EXCEPTIONS to simple table name."
SET @@global.GAP_LOCK_EXCEPTIONS = mytable;
SELECT @@global.GAP_LOCK_EXCEPTIONS;

--echo "Trying to set @session.GAP_LOCK_EXCEPTIONS to regex table name(s)."
SET @@global.GAP_LOCK_EXCEPTIONS = "t.*";
SELECT @@global.GAP_LOCK_EXCEPTIONS;

--echo "Trying to set @session.GAP_LOCK_EXCEPTIONS to multiple regex table names."
SET @@global.GAP_LOCK_EXCEPTIONS = "s.*,t.*";
SELECT @@global.GAP_LOCK_EXCEPTIONS;

--echo "Trying to set @session.GAP_LOCK_EXCEPTIONS to empty."
SET @@global.GAP_LOCK_EXCEPTIONS = "";
SELECT @@global.GAP_LOCK_EXCEPTIONS;

--echo "Trying to set @session.GAP_LOCK_EXCEPTIONS to default."
SET @@global.GAP_LOCK_EXCEPTIONS = DEFAULT;
SELECT @@global.GAP_LOCK_EXCEPTIONS;

--echo "Trying to set @session.GAP_LOCK_EXCEPTIONS to 444. It should fail because it is not session."
--Error ER_GLOBAL_VARIABLE
SET @@session.GAP_LOCK_EXCEPTIONS = 444;

SET @@global.GAP_LOCK_EXCEPTIONS = @start_global_value;
SELECT @@global.GAP_LOCK_EXCEPTIONS;

0 comments on commit 9b439ee

Please sign in to comment.
You can’t perform that action at this time.