Skip to content

Commit a6c8c62

Browse files
Dmitry LenevDmitry Lenev
authored andcommitted
Fix for bug #12641342 - "61401: UPDATE PERFORMANCE DEGRADES
GRADUALLY IF A TRIGGER EXISTS". This bug manifested itself in two ways: - Firstly execution of any data-changing statement which required prelocking (i.e. involved stored function or trigger) as part of transaction slowed down a bit all subsequent statements in this transaction. So performance in transaction which periodically involved such statements gradually degraded over time. - Secondly execution of any data-changing statement which required prelocking as part of transaction prevented concurrent FLUSH TABLES WITH READ LOCK from proceeding until the end of transaction instead of end of particular statement. The problem was caused by incorrect handling of metadata lock used in FTWRL implementation for statements requiring prelocked mode. Each statement which changes data acquires global IX lock with STATEMENT duration. This lock is supposed to block concurrent FTWRL from proceeding until the statement ends. When entering prelocked mode, durations of all metadata locks acquired so far were changed to EXPLICIT, to prevent substatements from releasing these locks. When prelocked mode was left, durations of metadata locks were changed to TRANSACTIONAL (with a few exceptions) so they can be properly released at the end of transaction. Unfortunately, this meant that the global IX lock blocking FTWRL with STATEMENT duration was moved to TRANSACTIONAL duration after execution of statement requiring prelocking. Since each subsequent statement that required prelocking and tried to acquire global IX lock with STATEMENT duration got a new instance of MDL_ticket, which was later moved to TRANSACTIONAL duration, this led to unwarranted growth of number of tickets with TRANSACITONAL duration in this connection's MDL_context. As result searching for other tickets in it became slow and acquisition of other metadata locks by this transaction started to hog CPU. Moreover, this also meant that after execution of statement requiring prelocking concurrent FTWRL was blocked until the end of transaction instead of end of statement. This patch solves this problem by not moving locks to EXPLICIT duration when thread enters prelocked mode (unless it is a real LOCK TABLES mode). This step turned out to be not really necessary as substatements don't try to release metadata locks. Consequently, the global IX lock blocking FTWRL keeps its STATEMENT duration and is properly released at the end of statement and the above issue goes away. --BZR-- revision-id: [email protected] property-branch-nick: mysql-5.5-12641342 property-file-info: ld7:file_id65:sp1f-flush.result-20010323030458-fknoht4a4rohnwlkdtjcyx653fk3jumx7:message98:Added test for bug #12641342 - "61401: UPDATE PERFORMANCE property-file-info: DEGRADES GRADUALLY IF A TRIGGER EXISTS".4:path25:mysql-test/r/flush.resulted7:file_id63:sp1f-flush.test-20010323030458-7rtnbsox2xqacdijdaxl3yeinfs233eo7:message98:Added test for bug #12641342 - "61401: UPDATE PERFORMANCE property-file-info: DEGRADES GRADUALLY IF A TRIGGER EXISTS".4:path23:mysql-test/t/flush.tested7:file_id39:mdl.h-20080523121748-o4y2wcq3maotb9do-17:message65:Added comment describing various types of metadata lock property-file-info: duration.4:path9:sql/mdl.hed7:file_id65:sp1f-sql_class.cc-19700101030959-rpotnweaff2pikkozh3butrf7mv3oero7:message213:Since we no longer change duration of metadata locks to EXPLICIT property-file-info: when entering prelocked mode (unless it is a real LOCK TABLES) property-file-info: there is no need to restore proper duration of the locks when property-file-info: leaving prelocked mode.4:path16:sql/sql_class.cced7:file_id64:sp1f-sql_class.h-19700101030959-jnqnbrjyqsvgncsibnumsmg3lyi7pa5s7:message288:Do not change duration of metadata locks to EXPLICIT when property-file-info: entering prelocking mode (unless it is a real LOCK TABLES). property-file-info: This allows to avoid problems with restoring correct duration property-file-info: when leaving this mode. It is possible to do this as property-file-info: substatements won't release metadata locks in any case.4:path15:sql/sql_class.hed7:file_id65:sp1f-sql_parse.cc-19700101030959-ehcre3rwhv5l3mlxqhaxg36ujenxnrcd7:message80:Added assert checking that we won't release metadata locks property-file-info: when in substatement.4:path16:sql/sql_parse.ccee testament3-sha1: 042cd28529c1e28a122f0f5beedb81504530f31d
1 parent 8bc5d2a commit a6c8c62

File tree

6 files changed

+109
-15
lines changed

6 files changed

+109
-15
lines changed

mysql-test/r/flush.result

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,26 @@ ALTER TABLE t1 COMMENT 'test';
466466
ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
467467
UNLOCK TABLES;
468468
DROP TABLE t1;
469+
#
470+
# Test for bug #12641342 - "61401: UPDATE PERFORMANCE DEGRADES
471+
# GRADUALLY IF A TRIGGER EXISTS".
472+
#
473+
# One of side-effects of this bug was that a transaction which
474+
# involved DML statements requiring prelocking blocked concurrent
475+
# FLUSH TABLES WITH READ LOCK for the whole its duration, while
476+
# correct behavior in this case is to block FTWRL only for duration
477+
# of individual DML statements.
478+
DROP TABLE IF EXISTS t1;
479+
CREATE TABLE t1 (id INT PRIMARY KEY, value INT);
480+
INSERT INTO t1 VALUES (1, 1);
481+
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW SET @var = "a";
482+
BEGIN;
483+
UPDATE t1 SET value= value + 1 WHERE id = 1;
484+
# Switching to connection 'con1'.
485+
# The below FLUSH TABLES WITH READ LOCK should succeed and
486+
# should not be blocked by the transaction in default connection.
487+
FLUSH TABLES WITH READ LOCK;
488+
UNLOCK TABLES;
489+
# Switching to connection 'default'.
490+
COMMIT;
491+
DROP TABLE t1;

mysql-test/t/flush.test

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,36 @@ ALTER TABLE t1 COMMENT 'test';
668668

669669
UNLOCK TABLES;
670670
DROP TABLE t1;
671+
672+
673+
--echo #
674+
--echo # Test for bug #12641342 - "61401: UPDATE PERFORMANCE DEGRADES
675+
--echo # GRADUALLY IF A TRIGGER EXISTS".
676+
--echo #
677+
--echo # One of side-effects of this bug was that a transaction which
678+
--echo # involved DML statements requiring prelocking blocked concurrent
679+
--echo # FLUSH TABLES WITH READ LOCK for the whole its duration, while
680+
--echo # correct behavior in this case is to block FTWRL only for duration
681+
--echo # of individual DML statements.
682+
--disable_warnings
683+
DROP TABLE IF EXISTS t1;
684+
--enable_warnings
685+
CREATE TABLE t1 (id INT PRIMARY KEY, value INT);
686+
INSERT INTO t1 VALUES (1, 1);
687+
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW SET @var = "a";
688+
BEGIN;
689+
UPDATE t1 SET value= value + 1 WHERE id = 1;
690+
691+
--echo # Switching to connection 'con1'.
692+
connect(con1, localhost, root);
693+
--echo # The below FLUSH TABLES WITH READ LOCK should succeed and
694+
--echo # should not be blocked by the transaction in default connection.
695+
FLUSH TABLES WITH READ LOCK;
696+
UNLOCK TABLES;
697+
disconnect con1;
698+
--source include/wait_until_disconnected.inc
699+
700+
--echo # Switching to connection 'default'.
701+
connection default;
702+
COMMIT;
703+
DROP TABLE t1;

sql/mdl.h

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,24 @@ enum enum_mdl_type {
152152

153153
/** Duration of metadata lock. */
154154

155-
enum enum_mdl_duration { MDL_STATEMENT= 0,
156-
MDL_TRANSACTION,
157-
MDL_EXPLICIT,
158-
/* This should be the last ! */
159-
MDL_DURATION_END };
155+
enum enum_mdl_duration {
156+
/**
157+
Locks with statement duration are automatically released at the end
158+
of statement or transaction.
159+
*/
160+
MDL_STATEMENT= 0,
161+
/**
162+
Locks with transaction duration are automatically released at the end
163+
of transaction.
164+
*/
165+
MDL_TRANSACTION,
166+
/**
167+
Locks with explicit duration survive the end of statement and transaction.
168+
They have to be released explicitly by calling MDL_context::release_lock().
169+
*/
170+
MDL_EXPLICIT,
171+
/* This should be the last ! */
172+
MDL_DURATION_END };
160173

161174

162175
/** Maximal length of key for metadata locking subsystem. */

sql/sql_class.cc

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3790,16 +3790,24 @@ void THD::set_mysys_var(struct st_my_thread_var *new_mysys_var)
37903790

37913791
void THD::leave_locked_tables_mode()
37923792
{
3793+
if (locked_tables_mode == LTM_LOCK_TABLES)
3794+
{
3795+
/*
3796+
When leaving LOCK TABLES mode we have to change the duration of most
3797+
of the metadata locks being held, except for HANDLER and GRL locks,
3798+
to transactional for them to be properly released at UNLOCK TABLES.
3799+
*/
3800+
mdl_context.set_transaction_duration_for_all_locks();
3801+
/*
3802+
Make sure we don't release the global read lock and commit blocker
3803+
when leaving LTM.
3804+
*/
3805+
global_read_lock.set_explicit_lock_duration(this);
3806+
/* Also ensure that we don't release metadata locks for open HANDLERs. */
3807+
if (handler_tables_hash.records)
3808+
mysql_ha_set_explicit_lock_duration(this);
3809+
}
37933810
locked_tables_mode= LTM_NONE;
3794-
mdl_context.set_transaction_duration_for_all_locks();
3795-
/*
3796-
Make sure we don't release the global read lock and commit blocker
3797-
when leaving LTM.
3798-
*/
3799-
global_read_lock.set_explicit_lock_duration(this);
3800-
/* Also ensure that we don't release metadata locks for open HANDLERs. */
3801-
if (handler_tables_hash.records)
3802-
mysql_ha_set_explicit_lock_duration(this);
38033811
}
38043812

38053813
void THD::get_definer(LEX_USER *definer)

sql/sql_class.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2795,7 +2795,19 @@ class THD :public Statement,
27952795
{
27962796
DBUG_ASSERT(locked_tables_mode == LTM_NONE);
27972797

2798-
mdl_context.set_explicit_duration_for_all_locks();
2798+
if (mode_arg == LTM_LOCK_TABLES)
2799+
{
2800+
/*
2801+
When entering LOCK TABLES mode we should set explicit duration
2802+
for all metadata locks acquired so far in order to avoid releasing
2803+
them till UNLOCK TABLES statement.
2804+
We don't do this when entering prelocked mode since sub-statements
2805+
don't release metadata locks and restoring status-quo after leaving
2806+
prelocking mode gets complicated.
2807+
*/
2808+
mdl_context.set_explicit_duration_for_all_locks();
2809+
}
2810+
27992811
locked_tables_mode= mode_arg;
28002812
}
28012813
void leave_locked_tables_mode();

sql/sql_parse.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,11 @@ mysql_execute_command(THD *thd)
20272027
*/
20282028
if (stmt_causes_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN))
20292029
{
2030+
/*
2031+
Note that this should never happen inside of stored functions
2032+
or triggers as all such statements prohibited there.
2033+
*/
2034+
DBUG_ASSERT(! thd->in_sub_stmt);
20302035
/* Commit or rollback the statement transaction. */
20312036
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
20322037
/* Commit the normal transaction if one is active. */

0 commit comments

Comments
 (0)