Skip to content

Commit be2b6ef

Browse files
committed
Bug#18913551 : LOCK TABLES USES INCORRECT LOCK FOR IMPLICITLY
USED TABLES. Analysis: ---------- Issue here is, LOCK table operation sometime acquired too weaker lock on the tables locked implicitly. LOCK TABLE on a view which uses stored function which uses some table acquires lock TL_READ on that table. As result, such LOCK TABLE operation allows concurrent insert on the table used by function. And this can break statement based replication/ cause wrong binlog order. While opening tables used in a statement, tables used in stored functions(if statement uses any) are also opened and locked implicitly. When statement based replication is on, if function does not modify any data then the lock TL_READ acquired on such user tables by ignoring prelocking_placeholder set for them in function "read_lock_type_for_table". This helps in taking weaker locks on such tables and allows concurrent insert operations on them. But when opening tables for "LOCK TABLE" operations, because of the above mentioned logic, lock type "TL_READ" is set instead of "TL_READ_NO_INSERT" for tables of stored function which does not modify any data. Hence issue mentioned in the report is seen. Fix: ---------- In function "read_lock_type_for_table", when statement based replication is on, for tables of stored functions lock_type TL_READ_NO_INSERT is set for LOCK TABLE statement. Flag used to indicate stored function modifies data or not is ignored in this case.
1 parent c11767e commit be2b6ef

File tree

3 files changed

+100
-10
lines changed

3 files changed

+100
-10
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Bug#18913551 - LOCK TABLES USES INCORRECT LOCK FOR IMPLICITLY USED
3+
# TABLES.
4+
#
5+
SET @org_concurrent_insert= @@global.concurrent_insert;
6+
SET @@global.concurrent_insert=1;
7+
CREATE TABLE t1(a INT) ENGINE=MyISAM;
8+
CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MIN(a) FROM t1);
9+
CREATE VIEW v1 AS (SELECT 1 FROM dual WHERE f1() = 1);
10+
LOCK TABLE v1 READ;
11+
connect con1, localhost, root;
12+
SET lock_wait_timeout=1;
13+
# With fix, con1 does not get lock on table "t1" so following insert
14+
# operation fails after waiting for "lock_wait_timeout" duration.
15+
INSERT INTO t1 VALUES (1);
16+
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
17+
connection default;
18+
UNLOCK TABLES;
19+
# V1 should be empty here.
20+
SELECT * FROM v1;
21+
1
22+
disconnect con1;
23+
SET @@global.concurrent_insert= @org_concurrent_insert;
24+
DROP TABLE t1;
25+
DROP VIEW v1;
26+
DROP FUNCTION f1;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
--source include/have_binlog_format_mixed_or_statement.inc
3+
# Save the initial number of concurrent sessions
4+
--source include/count_sessions.inc
5+
6+
--echo #
7+
--echo # Bug#18913551 - LOCK TABLES USES INCORRECT LOCK FOR IMPLICITLY USED
8+
--echo # TABLES.
9+
--echo #
10+
11+
--enable_connect_log
12+
13+
SET @org_concurrent_insert= @@global.concurrent_insert;
14+
SET @@global.concurrent_insert=1;
15+
16+
CREATE TABLE t1(a INT) ENGINE=MyISAM;
17+
CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MIN(a) FROM t1);
18+
CREATE VIEW v1 AS (SELECT 1 FROM dual WHERE f1() = 1);
19+
20+
LOCK TABLE v1 READ;
21+
22+
connect (con1, localhost, root);
23+
SET lock_wait_timeout=1;
24+
--echo # With fix, con1 does not get lock on table "t1" so following insert
25+
--echo # operation fails after waiting for "lock_wait_timeout" duration.
26+
--error ER_LOCK_WAIT_TIMEOUT
27+
INSERT INTO t1 VALUES (1);
28+
29+
connection default;
30+
UNLOCK TABLES;
31+
32+
--echo # V1 should be empty here.
33+
SELECT * FROM v1;
34+
35+
# Cleanup
36+
disconnect con1;
37+
SET @@global.concurrent_insert= @org_concurrent_insert;
38+
DROP TABLE t1;
39+
DROP VIEW v1;
40+
DROP FUNCTION f1;
41+
--disable_connect_log
42+
43+
#Wait till all disconnects are completed
44+
--source include/wait_until_count_sessions.inc

sql/sql_base.cc

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4239,8 +4239,8 @@ recover_from_failed_open()
42394239
do not modify data but only read it, since in this case nothing is
42404240
written to the binary log. Argument routine_modifies_data
42414241
denotes the same. So effectively, if the statement is not a
4242-
update query and routine_modifies_data is false, then
4243-
prelocking_placeholder does not take importance.
4242+
LOCK TABLE, not a update query and routine_modifies_data is false
4243+
then prelocking_placeholder does not take importance.
42444244
42454245
Furthermore, this does not apply to I_S and log tables as it's
42464246
always unsafe to replicate such tables under statement-based
@@ -4267,17 +4267,37 @@ thr_lock_type read_lock_type_for_table(THD *thd,
42674267
at THD::variables::sql_log_bin member.
42684268
*/
42694269
bool log_on= mysql_bin_log.is_open() && thd->variables.sql_log_bin;
4270-
ulong binlog_format= thd->variables.binlog_format;
4271-
if ((log_on == FALSE) || (binlog_format == BINLOG_FORMAT_ROW) ||
4272-
(table_list->table->s->table_category == TABLE_CATEGORY_LOG) ||
4270+
4271+
/*
4272+
When we do not write to binlog or when we use row based replication,
4273+
it is safe to use a weaker lock.
4274+
*/
4275+
if (log_on == false ||
4276+
thd->variables.binlog_format == BINLOG_FORMAT_ROW)
4277+
return TL_READ;
4278+
4279+
if ((table_list->table->s->table_category == TABLE_CATEGORY_LOG) ||
42734280
(table_list->table->s->table_category == TABLE_CATEGORY_RPL_INFO) ||
4274-
(table_list->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) ||
4275-
!(is_update_query(prelocking_ctx->sql_command) ||
4276-
(routine_modifies_data && table_list->prelocking_placeholder) ||
4277-
(thd->locked_tables_mode > LTM_LOCK_TABLES)))
4281+
(table_list->table->s->table_category == TABLE_CATEGORY_PERFORMANCE))
42784282
return TL_READ;
4279-
else
4283+
4284+
// SQL queries which updates data need a stronger lock.
4285+
if (is_update_query(prelocking_ctx->sql_command))
42804286
return TL_READ_NO_INSERT;
4287+
4288+
/*
4289+
table_list is placeholder for prelocking.
4290+
Ignore prelocking_placeholder status for non "LOCK TABLE" statement's
4291+
table_list objects when routine_modifies_data is false.
4292+
*/
4293+
if (table_list->prelocking_placeholder &&
4294+
(routine_modifies_data || thd->in_lock_tables))
4295+
return TL_READ_NO_INSERT;
4296+
4297+
if (thd->locked_tables_mode > LTM_LOCK_TABLES)
4298+
return TL_READ_NO_INSERT;
4299+
4300+
return TL_READ;
42814301
}
42824302

42834303

0 commit comments

Comments
 (0)