Skip to content

Commit 92d233a

Browse files
committed
MDEV-15061 TRUNCATE must honor InnoDB table locks
Traditionally, DROP TABLE and TRUNCATE TABLE discarded any locks that may have been held on the table. This feels like an ACID violation. Probably most occurrences of it were prevented by meta-data locks (MDL) which were introduced in MySQL 5.5. dict_table_t::n_foreign_key_checks_running: Reduce the number of non-debug checks. lock_remove_all_on_table(), lock_remove_all_on_table_for_trx(): Remove. ha_innobase::truncate(): Acquire an exclusive InnoDB table lock before proceeding. DROP TABLE and DISCARD/IMPORT were already doing this. row_truncate_table_for_mysql(): Convert the already started transaction into a dictionary operation, and do not invoke lock_remove_all_on_table(). row_mysql_table_id_reassign(): Do not call lock_remove_all_on_table(). This function is only used in ALTER TABLE...DISCARD/IMPORT TABLESPACE, which is already holding an exclusive InnoDB table lock. TODO: Make n_foreign_key_checks running a debug-only variable. This would require two fixes: (1) DROP TABLE: Exclusively lock the table beforehand, to prevent the possibility of concurrently running foreign key checks (which would acquire a table IS lock and then record S locks). (2) RENAME TABLE: Find out if n_foreign_key_checks_running>0 actually constitutes a potential problem.
1 parent f1ff69c commit 92d233a

File tree

8 files changed

+62
-226
lines changed

8 files changed

+62
-226
lines changed

sql/share/errmsg-utf8.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6899,8 +6899,8 @@ ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2
68996899
ER_SLAVE_SILENT_RETRY_TRANSACTION
69006900
eng "Slave must silently retry current transaction"
69016901

6902-
ER_DISCARD_FK_CHECKS_RUNNING
6903-
eng "There is a foreign key check running on table '%-.192s'. Cannot discard the table"
6902+
ER_UNUSED_22
6903+
eng "You should never see it"
69046904

69056905
ER_TABLE_SCHEMA_MISMATCH
69066906
eng "Schema mismatch (%s)"

storage/innobase/handler/ha_innodb.cc

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13348,18 +13348,26 @@ ha_innobase::truncate()
1334813348

1334913349
TrxInInnoDB trx_in_innodb(m_prebuilt->trx);
1335013350

13351-
if (!trx_is_started(m_prebuilt->trx)) {
13352-
++m_prebuilt->trx->will_lock;
13353-
}
13354-
13355-
dberr_t err;
13356-
13357-
/* Truncate the table in InnoDB */
13358-
err = row_truncate_table_for_mysql(m_prebuilt->table, m_prebuilt->trx);
13351+
m_prebuilt->trx->ddl = true;
13352+
trx_start_if_not_started(m_prebuilt->trx, true);
1335913353

13360-
int error;
13354+
dberr_t err = row_mysql_lock_table(m_prebuilt->trx, m_prebuilt->table,
13355+
LOCK_X, "truncate table");
13356+
if (err == DB_SUCCESS) {
13357+
err = row_truncate_table_for_mysql(m_prebuilt->table,
13358+
m_prebuilt->trx);
13359+
}
1336113360

1336213361
switch (err) {
13362+
case DB_FORCED_ABORT:
13363+
case DB_DEADLOCK:
13364+
thd_mark_transaction_to_rollback(m_user_thd, 1);
13365+
DBUG_RETURN(HA_ERR_LOCK_DEADLOCK);
13366+
case DB_LOCK_TABLE_FULL:
13367+
thd_mark_transaction_to_rollback(m_user_thd, 1);
13368+
DBUG_RETURN(HA_ERR_LOCK_TABLE_FULL);
13369+
case DB_LOCK_WAIT_TIMEOUT:
13370+
DBUG_RETURN(HA_ERR_LOCK_WAIT_TIMEOUT);
1336313371
case DB_TABLESPACE_DELETED:
1336413372
case DB_TABLESPACE_NOT_FOUND:
1336513373
ib_senderrf(
@@ -13368,19 +13376,14 @@ ha_innobase::truncate()
1336813376
ER_TABLESPACE_DISCARDED : ER_TABLESPACE_MISSING),
1336913377
table->s->table_name.str);
1337013378
table->status = STATUS_NOT_FOUND;
13371-
error = HA_ERR_TABLESPACE_MISSING;
13372-
break;
13373-
13379+
DBUG_RETURN(HA_ERR_TABLESPACE_MISSING);
1337413380
default:
13375-
error = convert_error_code_to_mysql(
13376-
err, m_prebuilt->table->flags,
13377-
m_prebuilt->trx->mysql_thd);
13378-
1337913381
table->status = STATUS_NOT_FOUND;
13382+
DBUG_RETURN(convert_error_code_to_mysql(
13383+
err, m_prebuilt->table->flags,
13384+
m_user_thd));
1338013385
break;
1338113386
}
13382-
13383-
DBUG_RETURN(error);
1338413387
}
1338513388

1338613389
/*****************************************************************//**

storage/innobase/include/dict0mem.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved.
44
Copyright (c) 2012, Facebook Inc.
5-
Copyright (c) 2013, 2017, MariaDB Corporation.
5+
Copyright (c) 2013, 2018, MariaDB Corporation.
66
77
This program is free software; you can redistribute it and/or modify it under
88
the terms of the GNU General Public License as published by the Free Software
@@ -1544,6 +1544,23 @@ struct dict_table_t {
15441544

15451545
bool versioned() const { return vers_start || vers_end; }
15461546

1547+
void inc_fk_checks()
1548+
{
1549+
#ifdef UNIV_DEBUG
1550+
lint fk_checks=
1551+
#endif
1552+
my_atomic_addlint(&n_foreign_key_checks_running, 1);
1553+
ut_ad(fk_checks >= 0);
1554+
}
1555+
void dec_fk_checks()
1556+
{
1557+
#ifdef UNIV_DEBUG
1558+
lint fk_checks=
1559+
#endif
1560+
my_atomic_addlint(&n_foreign_key_checks_running, -1);
1561+
ut_ad(fk_checks > 0);
1562+
}
1563+
15471564
/** Id of the table. */
15481565
table_id_t id;
15491566

storage/innobase/include/lock0lock.h

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*****************************************************************************
22
33
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
4-
Copyright (c) 2017, MariaDB Corporation.
4+
Copyright (c) 2017, 2018, MariaDB Corporation.
55
66
This program is free software; you can redistribute it and/or modify it under
77
the terms of the GNU General Public License as published by the Free Software
@@ -511,18 +511,6 @@ void
511511
lock_trx_release_locks(
512512
/*===================*/
513513
trx_t* trx); /*!< in/out: transaction */
514-
/*********************************************************************//**
515-
Removes locks on a table to be dropped or truncated.
516-
If remove_also_table_sx_locks is TRUE then table-level S and X locks are
517-
also removed in addition to other table-level and record-level locks.
518-
No lock, that is going to be removed, is allowed to be a wait lock. */
519-
void
520-
lock_remove_all_on_table(
521-
/*=====================*/
522-
dict_table_t* table, /*!< in: table to be dropped
523-
or truncated */
524-
ibool remove_also_table_sx_locks);/*!< in: also removes
525-
table S and X locks */
526514

527515
/*********************************************************************//**
528516
Calculates the fold value of a page file address: used in inserting or

storage/innobase/lock/lock0lock.cc

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5300,113 +5300,6 @@ lock_trx_table_locks_remove(
53005300
ut_error;
53015301
}
53025302

5303-
/*********************************************************************//**
5304-
Removes locks of a transaction on a table to be dropped.
5305-
If remove_also_table_sx_locks is TRUE then table-level S and X locks are
5306-
also removed in addition to other table-level and record-level locks.
5307-
No lock that is going to be removed is allowed to be a wait lock. */
5308-
static
5309-
void
5310-
lock_remove_all_on_table_for_trx(
5311-
/*=============================*/
5312-
dict_table_t* table, /*!< in: table to be dropped */
5313-
trx_t* trx, /*!< in: a transaction */
5314-
ibool remove_also_table_sx_locks)/*!< in: also removes
5315-
table S and X locks */
5316-
{
5317-
lock_t* lock;
5318-
lock_t* prev_lock;
5319-
5320-
ut_ad(lock_mutex_own());
5321-
5322-
for (lock = UT_LIST_GET_LAST(trx->lock.trx_locks);
5323-
lock != NULL;
5324-
lock = prev_lock) {
5325-
5326-
prev_lock = UT_LIST_GET_PREV(trx_locks, lock);
5327-
5328-
if (lock_get_type_low(lock) == LOCK_REC
5329-
&& lock->index->table == table) {
5330-
ut_a(!lock_get_wait(lock));
5331-
5332-
lock_rec_discard(lock);
5333-
} else if (lock_get_type_low(lock) & LOCK_TABLE
5334-
&& lock->un_member.tab_lock.table == table
5335-
&& (remove_also_table_sx_locks
5336-
|| !IS_LOCK_S_OR_X(lock))) {
5337-
5338-
ut_a(!lock_get_wait(lock));
5339-
5340-
lock_trx_table_locks_remove(lock);
5341-
lock_table_remove_low(lock);
5342-
}
5343-
}
5344-
}
5345-
5346-
/*********************************************************************//**
5347-
Removes locks on a table to be dropped or truncated.
5348-
If remove_also_table_sx_locks is TRUE then table-level S and X locks are
5349-
also removed in addition to other table-level and record-level locks.
5350-
No lock, that is going to be removed, is allowed to be a wait lock. */
5351-
void
5352-
lock_remove_all_on_table(
5353-
/*=====================*/
5354-
dict_table_t* table, /*!< in: table to be dropped
5355-
or truncated */
5356-
ibool remove_also_table_sx_locks)/*!< in: also removes
5357-
table S and X locks */
5358-
{
5359-
lock_t* lock;
5360-
5361-
lock_mutex_enter();
5362-
5363-
for (lock = UT_LIST_GET_FIRST(table->locks);
5364-
lock != NULL;
5365-
/* No op */) {
5366-
5367-
lock_t* prev_lock;
5368-
5369-
prev_lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock);
5370-
5371-
/* If we should remove all locks (remove_also_table_sx_locks
5372-
is TRUE), or if the lock is not table-level S or X lock,
5373-
then check we are not going to remove a wait lock. */
5374-
if (remove_also_table_sx_locks
5375-
|| !(lock_get_type(lock) == LOCK_TABLE
5376-
&& IS_LOCK_S_OR_X(lock))) {
5377-
5378-
ut_a(!lock_get_wait(lock));
5379-
}
5380-
5381-
lock_remove_all_on_table_for_trx(
5382-
table, lock->trx, remove_also_table_sx_locks);
5383-
5384-
if (prev_lock == NULL) {
5385-
if (lock == UT_LIST_GET_FIRST(table->locks)) {
5386-
/* lock was not removed, pick its successor */
5387-
lock = UT_LIST_GET_NEXT(
5388-
un_member.tab_lock.locks, lock);
5389-
} else {
5390-
/* lock was removed, pick the first one */
5391-
lock = UT_LIST_GET_FIRST(table->locks);
5392-
}
5393-
} else if (UT_LIST_GET_NEXT(un_member.tab_lock.locks,
5394-
prev_lock) != lock) {
5395-
/* If lock was removed by
5396-
lock_remove_all_on_table_for_trx() then pick the
5397-
successor of prev_lock ... */
5398-
lock = UT_LIST_GET_NEXT(
5399-
un_member.tab_lock.locks, prev_lock);
5400-
} else {
5401-
/* ... otherwise pick the successor of lock. */
5402-
lock = UT_LIST_GET_NEXT(
5403-
un_member.tab_lock.locks, lock);
5404-
}
5405-
}
5406-
5407-
lock_mutex_exit();
5408-
}
5409-
54105303
/*===================== VALIDATION AND DEBUGGING ====================*/
54115304

54125305
/** Print info of a table lock.

storage/innobase/row/row0ins.cc

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,15 +1446,11 @@ row_ins_foreign_check_on_constraint(
14461446
#endif /* WITH_WSREP */
14471447
node->new_upd_nodes->push_back(cascade);
14481448

1449-
my_atomic_addlint(&table->n_foreign_key_checks_running, 1);
1450-
1451-
ut_ad(foreign->foreign_table->n_foreign_key_checks_running > 0);
1449+
table->inc_fk_checks();
14521450

14531451
/* Release the data dictionary latch for a while, so that we do not
14541452
starve other threads from doing CREATE TABLE etc. if we have a huge
1455-
cascaded operation running. The counter n_foreign_key_checks_running
1456-
will prevent other users from dropping or ALTERing the table when we
1457-
release the latch. */
1453+
cascaded operation running. */
14581454

14591455
row_mysql_unfreeze_data_dictionary(thr_get_trx(thr));
14601456

@@ -1553,17 +1549,6 @@ row_ins_set_exclusive_rec_lock(
15531549
return(err);
15541550
}
15551551

1556-
/* Decrement a counter in the destructor. */
1557-
class ib_dec_in_dtor {
1558-
public:
1559-
ib_dec_in_dtor(ulint& c): counter(c) {}
1560-
~ib_dec_in_dtor() {
1561-
my_atomic_addlint(&counter, -1);
1562-
}
1563-
private:
1564-
ulint& counter;
1565-
};
1566-
15671552
/***************************************************************//**
15681553
Checks if foreign key constraint fails for an index entry. Sets shared locks
15691554
which lock either the success or the failure of the constraint. NOTE that
@@ -1911,19 +1896,13 @@ row_ins_check_foreign_constraint(
19111896

19121897
do_possible_lock_wait:
19131898
if (err == DB_LOCK_WAIT) {
1914-
/* An object that will correctly decrement the FK check counter
1915-
when it goes out of this scope. */
1916-
ib_dec_in_dtor dec(check_table->n_foreign_key_checks_running);
1917-
19181899
trx->error_state = err;
19191900

19201901
que_thr_stop_for_mysql(thr);
19211902

19221903
thr->lock_state = QUE_THR_LOCK_ROW;
19231904

1924-
/* To avoid check_table being dropped, increment counter */
1925-
my_atomic_addlint(
1926-
&check_table->n_foreign_key_checks_running, 1);
1905+
check_table->inc_fk_checks();
19271906

19281907
trx_kill_blocking(trx);
19291908

@@ -1934,6 +1913,8 @@ row_ins_check_foreign_constraint(
19341913
err = check_table->to_be_dropped
19351914
? DB_LOCK_WAIT_TIMEOUT
19361915
: trx->error_state;
1916+
1917+
check_table->dec_fk_checks();
19371918
}
19381919

19391920
exit_func:

0 commit comments

Comments
 (0)