Skip to content

Commit 55ceedb

Browse files
committed
Bug#17862905: MYSQLDUMP CREATES USELESS METADATA LOCKS
Problem Description: -------------------- While taking the backup using tool "mysqldump" with option "--single-transaction", MDL lock is acquired on each table dumped. But these locks are not released until the backup operation is completed. Any concurrent DDL operation on those tables will be blocked until backup operation is completed. Moreover such blocked DDL operations will also block other concurrent DML operations (Since DDL has priority over DML) creating pile-up. Note that officially mysqldump --single-transaction is documented as not working/not supported in presence of concurrent DDL statements. But it might work for some people in some scenarios and before 5.5, due to absence of MDL, combination of "mysqldump --single-transaction" and concurrent DDL didn't create pile-up of DML statements. Analysis: -------------------- "mysqldump" start transaction with consistent snapshot and sets isolation level to "repeatable read" when it is used with option "--single-transaction". Data of all the tables is dumped using SELECT statement as part of this transaction. MDL lock SR is taken on all these tables and held till the transaction is committed or rolled back. Any other incompatible MDL lock request on these tables will wait until timeout or "mysqldump" operation is completed. As result concurrent DDL operations on the tables which were dumped will be blocked till the end of dump or timeout. Note that once table is dumped it won't be used again by "mysqldump". This fact and the fact that "mysqldump --single-transactions" produces backup with validity point at the start of transaction (and also retrieves binlog position for backup at the start of transaction) means that "mysqldump --single-transaction" can release MDL on tables which it has already dumped without introducing more problems with consistency. Fix: -------------------- To make "mysqldump --single-transaction" to release locks once dumping of the table is done, modified mysqldump client code to dump table data after setting a savepoint. Once dumping of table data is over, added code to rollback to the savepoint set. Rolling back to savepoint will release MDL lock acquired for the table. But as of now, on rollback to savepoint, MDL locks are not released if binlog is on. This logic is added to avoid dropping of tables before rollback to savepoint event is written to binlog. But when binlog handlerton can clear cache and can safely rollback to savepoint without writing an event for rollback to savepoint then also we are not releasing the MDL locks. This is fixed by introducing a new handlerton function call savepoint_rollback_can_release_mdl. We call this function to check with each storage engine participating in transaction whether it is safe to release MDL after rollback to savepoint. Metadata locks are released only if all the storage engines agreed that it is a safe thing to do. 1) For InnoDB storage engine this handlerton function can allow release of MDL locks if transaction has not acquired any InnoDB locks. 2) For Binlog this handlerton function can allow release of MDL locks if rollback to savepoint will completely remove any traces of transaction from cache. 3) Absence of this method for any storage engine means it is not safe to release MDL locks. Note that this patch doesn't make "mysqldump --single-transaction" safe in general case in presence of concurrent DDL. Nor makes it officially supported in this case. It just allows to avoid problem with unnecessary concurrent DDL blocking and associated DML query pile-up in some specific cases when it might work.
1 parent 01471b7 commit 55ceedb

7 files changed

Lines changed: 211 additions & 8 deletions

File tree

client/mysqldump.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4421,6 +4421,12 @@ static int dump_all_tables_in_db(char *database)
44214421
else
44224422
verbose_msg("-- dump_all_tables_in_db : logs flushed successfully!\n");
44234423
}
4424+
if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500)
4425+
{
4426+
verbose_msg("-- Setting savepoint...\n");
4427+
if (mysql_query_with_error_report(mysql, 0, "SAVEPOINT sp"))
4428+
DBUG_RETURN(1);
4429+
}
44244430
while ((table= getTableName(0)))
44254431
{
44264432
char *end= strmov(afterdot, table);
@@ -4438,6 +4444,23 @@ static int dump_all_tables_in_db(char *database)
44384444
maybe_exit(EX_MYSQLERR);
44394445
}
44404446
}
4447+
4448+
/**
4449+
ROLLBACK TO SAVEPOINT in --single-transaction mode to release metadata
4450+
lock on table which was already dumped. This allows to avoid blocking
4451+
concurrent DDL on this table without sacrificing correctness, as we
4452+
won't access table second time and dumps created by --single-transaction
4453+
mode have validity point at the start of transaction anyway.
4454+
Note that this doesn't make --single-transaction mode with concurrent
4455+
DDL safe in general case. It just improves situation for people for whom
4456+
it might be working.
4457+
*/
4458+
if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500)
4459+
{
4460+
verbose_msg("-- Rolling back to savepoint sp...\n");
4461+
if (mysql_query_with_error_report(mysql, 0, "ROLLBACK TO SAVEPOINT sp"))
4462+
maybe_exit(EX_MYSQLERR);
4463+
}
44414464
}
44424465
else
44434466
{
@@ -4460,6 +4483,14 @@ static int dump_all_tables_in_db(char *database)
44604483
}
44614484
}
44624485
}
4486+
4487+
if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500)
4488+
{
4489+
verbose_msg("-- Releasing savepoint...\n");
4490+
if (mysql_query_with_error_report(mysql, 0, "RELEASE SAVEPOINT sp"))
4491+
DBUG_RETURN(1);
4492+
}
4493+
44634494
if (opt_events && mysql_get_server_version(mysql) >= 50106)
44644495
{
44654496
DBUG_PRINT("info", ("Dumping events for database %s", database));
@@ -4702,6 +4733,13 @@ static int dump_selected_tables(char *db, char **table_names, int tables)
47024733
if (opt_xml)
47034734
print_xml_tag(md_result_file, "", "\n", "database", "name=", db, NullS);
47044735

4736+
if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500)
4737+
{
4738+
verbose_msg("-- Setting savepoint...\n");
4739+
if (mysql_query_with_error_report(mysql, 0, "SAVEPOINT sp"))
4740+
DBUG_RETURN(1);
4741+
}
4742+
47054743
/* Dump each selected table */
47064744
for (pos= dump_tables; pos < end; pos++)
47074745
{
@@ -4717,6 +4755,31 @@ static int dump_selected_tables(char *db, char **table_names, int tables)
47174755
maybe_exit(EX_MYSQLERR);
47184756
}
47194757
}
4758+
4759+
/**
4760+
ROLLBACK TO SAVEPOINT in --single-transaction mode to release metadata
4761+
lock on table which was already dumped. This allows to avoid blocking
4762+
concurrent DDL on this table without sacrificing correctness, as we
4763+
won't access table second time and dumps created by --single-transaction
4764+
mode have validity point at the start of transaction anyway.
4765+
Note that this doesn't make --single-transaction mode with concurrent
4766+
DDL safe in general case. It just improves situation for people for whom
4767+
it might be working.
4768+
*/
4769+
if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500)
4770+
{
4771+
verbose_msg("-- Rolling back to savepoint sp...\n");
4772+
if (mysql_query_with_error_report(mysql, 0, "ROLLBACK TO SAVEPOINT sp"))
4773+
maybe_exit(EX_MYSQLERR);
4774+
}
4775+
}
4776+
4777+
if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500)
4778+
{
4779+
verbose_msg("-- Releasing savepoint...\n");
4780+
if (mysql_query_with_error_report(mysql, 0, "RELEASE SAVEPOINT sp"))
4781+
DBUG_RETURN(1);
4782+
47204783
}
47214784

47224785
/* Dump each selected view */

mysql-test/r/mysqldump.result

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5315,12 +5315,16 @@ UNLOCK TABLES;
53155315
-- Connecting to localhost...
53165316
-- main : logs flushed successfully!
53175317
-- Starting transaction...
5318+
-- Setting savepoint...
53185319
-- Retrieving table structure for table t1...
53195320
-- Sending SELECT query...
53205321
-- Retrieving rows...
5322+
-- Rolling back to savepoint sp...
53215323
-- Retrieving table structure for table t2...
53225324
-- Sending SELECT query...
53235325
-- Retrieving rows...
5326+
-- Rolling back to savepoint sp...
5327+
-- Releasing savepoint...
53245328
-- Disconnecting from localhost...
53255329

53265330
#### Dump ends here ####

sql/binlog.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ static int binlog_start_trans_and_stmt(THD *thd, Log_event *start_event);
7373
static int binlog_close_connection(handlerton *hton, THD *thd);
7474
static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv);
7575
static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv);
76+
static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton,
77+
THD *thd);
7678
static int binlog_commit(handlerton *hton, THD *thd, bool all);
7779
static int binlog_rollback(handlerton *hton, THD *thd, bool all);
7880
static int binlog_prepare(handlerton *hton, THD *thd, bool all);
@@ -881,6 +883,8 @@ static int binlog_init(void *p)
881883
binlog_hton->close_connection= binlog_close_connection;
882884
binlog_hton->savepoint_set= binlog_savepoint_set;
883885
binlog_hton->savepoint_rollback= binlog_savepoint_rollback;
886+
binlog_hton->savepoint_rollback_can_release_mdl=
887+
binlog_savepoint_rollback_can_release_mdl;
884888
binlog_hton->commit= binlog_commit;
885889
binlog_hton->rollback= binlog_rollback;
886890
binlog_hton->prepare= binlog_prepare;
@@ -1760,6 +1764,29 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
17601764
DBUG_RETURN(0);
17611765
}
17621766

1767+
/**
1768+
Check whether binlog state allows to safely release MDL locks after
1769+
rollback to savepoint.
1770+
1771+
@param hton The binlog handlerton.
1772+
@param thd The client thread that executes the transaction.
1773+
1774+
@return true - It is safe to release MDL locks.
1775+
false - If it is not.
1776+
*/
1777+
static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton,
1778+
THD *thd)
1779+
{
1780+
DBUG_ENTER("binlog_savepoint_rollback_can_release_mdl");
1781+
/*
1782+
If we have not updated any non-transactional tables rollback
1783+
to savepoint will simply truncate binlog cache starting from
1784+
SAVEPOINT command. So it should be safe to release MDL acquired
1785+
after SAVEPOINT command in this case.
1786+
*/
1787+
DBUG_RETURN(!trans_cannot_safely_rollback(thd));
1788+
}
1789+
17631790
#ifdef HAVE_REPLICATION
17641791

17651792
/*

sql/handler.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,41 @@ int ha_release_temporary_latches(THD *thd)
19931993
return 0;
19941994
}
19951995

1996+
/**
1997+
Check if all storage engines used in transaction agree that after
1998+
rollback to savepoint it is safe to release MDL locks acquired after
1999+
savepoint creation.
2000+
2001+
@param thd The client thread that executes the transaction.
2002+
2003+
@return true - It is safe to release MDL locks.
2004+
false - If it is not.
2005+
*/
2006+
bool ha_rollback_to_savepoint_can_release_mdl(THD *thd)
2007+
{
2008+
Ha_trx_info *ha_info;
2009+
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
2010+
&thd->transaction.all);
2011+
2012+
DBUG_ENTER("ha_rollback_to_savepoint_can_release_mdl");
2013+
2014+
/**
2015+
Checking whether it is safe to release metadata locks after rollback to
2016+
savepoint in all the storage engines that are part of the transaction.
2017+
*/
2018+
for (ha_info= trans->ha_list; ha_info; ha_info= ha_info->next())
2019+
{
2020+
handlerton *ht= ha_info->ht();
2021+
DBUG_ASSERT(ht);
2022+
2023+
if (ht->savepoint_rollback_can_release_mdl == 0 ||
2024+
ht->savepoint_rollback_can_release_mdl(ht, thd) == false)
2025+
DBUG_RETURN(false);
2026+
}
2027+
2028+
DBUG_RETURN(true);
2029+
}
2030+
19962031
int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
19972032
{
19982033
int error=0;

sql/handler.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,13 @@ struct handlerton
850850
to the savepoint_set call
851851
*/
852852
int (*savepoint_rollback)(handlerton *hton, THD *thd, void *sv);
853+
/**
854+
Check if storage engine allows to release metadata locks which were
855+
acquired after the savepoint if rollback to savepoint is done.
856+
@return true - If it is safe to release MDL locks.
857+
false - If it is not.
858+
*/
859+
bool (*savepoint_rollback_can_release_mdl)(handlerton *hton, THD *thd);
853860
int (*savepoint_release)(handlerton *hton, THD *thd, void *sv);
854861
/*
855862
'all' is true if it's a real commit, that makes persistent changes
@@ -3423,6 +3430,7 @@ int ha_enable_transaction(THD *thd, bool on);
34233430

34243431
/* savepoints */
34253432
int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv);
3433+
bool ha_rollback_to_savepoint_can_release_mdl(THD *thd);
34263434
int ha_savepoint(THD *thd, SAVEPOINT *sv);
34273435
int ha_release_savepoint(THD *thd, SAVEPOINT *sv);
34283436

sql/transaction.cc

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -630,21 +630,40 @@ bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name)
630630
DBUG_RETURN(TRUE);
631631
}
632632

633+
/**
634+
Checking whether it is safe to release metadata locks acquired after
635+
savepoint, if rollback to savepoint is successful.
636+
637+
Whether it is safe to release MDL after rollback to savepoint depends
638+
on storage engines participating in transaction:
639+
640+
- InnoDB doesn't release any row-locks on rollback to savepoint so it
641+
is probably a bad idea to release MDL as well.
642+
- Binary log implementation in some cases (e.g when non-transactional
643+
tables involved) may choose not to remove events added after savepoint
644+
from transactional cache, but instead will write them to binary
645+
log accompanied with ROLLBACK TO SAVEPOINT statement. Since the real
646+
write happens at the end of transaction releasing MDL on tables
647+
mentioned in these events (i.e. acquired after savepoint and before
648+
rollback ot it) can break replication, as concurrent DROP TABLES
649+
statements will be able to drop these tables before events will get
650+
into binary log,
651+
652+
For backward-compatibility reasons we always release MDL if binary
653+
logging is off.
654+
*/
655+
bool mdl_can_safely_rollback_to_savepoint=
656+
(!(mysql_bin_log.is_open() && thd->variables.sql_log_bin) ||
657+
ha_rollback_to_savepoint_can_release_mdl(thd));
658+
633659
if (ha_rollback_to_savepoint(thd, sv))
634660
res= TRUE;
635661
else if (thd->transaction.all.cannot_safely_rollback() && !thd->slave_thread)
636662
thd->transaction.push_unsafe_rollback_warnings(thd);
637663

638664
thd->transaction.savepoints= sv;
639665

640-
/*
641-
Release metadata locks that were acquired during this savepoint unit
642-
unless binlogging is on. Releasing locks with binlogging on can break
643-
replication as it allows other connections to drop these tables before
644-
rollback to savepoint is written to the binlog.
645-
*/
646-
bool binlog_on= mysql_bin_log.is_open() && thd->variables.sql_log_bin;
647-
if (!res && !binlog_on)
666+
if (!res && mdl_can_safely_rollback_to_savepoint)
648667
thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint);
649668

650669
DBUG_RETURN(MY_TEST(res));

storage/innobase/handler/ha_innodb.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,19 @@ innobase_rollback_to_savepoint(
733733
be rolled back to savepoint */
734734
void* savepoint); /*!< in: savepoint data */
735735

736+
/*****************************************************************//**
737+
Check whether innodb state allows to safely release MDL locks after
738+
rollback to savepoint.
739+
@return true if it is safe, false if its not safe. */
740+
static
741+
bool
742+
innobase_rollback_to_savepoint_can_release_mdl(
743+
/*===========================================*/
744+
handlerton* hton, /*!< in/out: InnoDB handlerton */
745+
THD* thd); /*!< in: handle to the MySQL thread of
746+
the user whose XA transaction should
747+
be rolled back to savepoint */
748+
736749
/*****************************************************************//**
737750
Sets a transaction savepoint.
738751
@return always 0, that is, always succeeds */
@@ -2778,6 +2791,8 @@ innobase_init(
27782791
innobase_hton->close_connection = innobase_close_connection;
27792792
innobase_hton->savepoint_set = innobase_savepoint;
27802793
innobase_hton->savepoint_rollback = innobase_rollback_to_savepoint;
2794+
innobase_hton->savepoint_rollback_can_release_mdl =
2795+
innobase_rollback_to_savepoint_can_release_mdl;
27812796
innobase_hton->savepoint_release = innobase_release_savepoint;
27822797
innobase_hton->commit = innobase_commit;
27832798
innobase_hton->rollback = innobase_rollback;
@@ -3695,6 +3710,38 @@ innobase_rollback_to_savepoint(
36953710
DBUG_RETURN(convert_error_code_to_mysql(error, 0, NULL));
36963711
}
36973712

3713+
/*****************************************************************//**
3714+
Check whether innodb state allows to safely release MDL locks after
3715+
rollback to savepoint.
3716+
When binlog is on, MDL locks acquired after savepoint unit are not
3717+
released if there are any locks held in InnoDB.
3718+
@return true if it is safe, false if its not safe. */
3719+
static
3720+
bool
3721+
innobase_rollback_to_savepoint_can_release_mdl(
3722+
/*===========================================*/
3723+
handlerton* hton, /*!< in: InnoDB handlerton */
3724+
THD* thd) /*!< in: handle to the MySQL thread
3725+
of the user whose transaction should
3726+
be rolled back to savepoint */
3727+
{
3728+
trx_t* trx;
3729+
3730+
DBUG_ENTER("innobase_rollback_to_savepoint_can_release_mdl");
3731+
DBUG_ASSERT(hton == innodb_hton_ptr);
3732+
3733+
trx = check_trx_exists(thd);
3734+
ut_ad(trx);
3735+
3736+
/* If transaction has not acquired any locks then it is safe
3737+
to release MDL after rollback to savepoint */
3738+
if (!(UT_LIST_GET_LEN(trx->lock.trx_locks))) {
3739+
DBUG_RETURN(true);
3740+
}
3741+
3742+
DBUG_RETURN(false);
3743+
}
3744+
36983745
/*****************************************************************//**
36993746
Release transaction savepoint name.
37003747
@return 0 if success, HA_ERR_NO_SAVEPOINT if no savepoint with the

0 commit comments

Comments
 (0)