diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2019-03-29 19:08:22 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2019-05-09 11:13:44 +0400 |
commit | d0b73fb8d3ae4ef9ec2cde945fa6ada512f762fe (patch) | |
tree | a3a9a1dcef8818f6e8e05ae4a82880cf8ef3187c | |
parent | 3e5526b0dfb3250d71a1eaff319fb842a25dccf5 (diff) | |
download | mariadb-git-d0b73fb8d3ae4ef9ec2cde945fa6ada512f762fe.tar.gz |
MDEV-16060 - InnoDB: Failing assertion: ut_strcmp(index->name, key->name)
A sequel to 9180e86 and 149b754.
ALTER TABLE ... ADD FOREIGN KEY may crash if parent table is updated
concurrently.
Block FK parent table updates even earlier, before intermediate child
table is created.
Use proper charset info for my_casedn_str() and don't update original
identifiers so that lower_cast_table_names == 2 is honoured.
-rw-r--r-- | mysql-test/suite/innodb/r/foreign-keys.result | 24 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/foreign-keys.test | 32 | ||||
-rw-r--r-- | sql/sql_table.cc | 91 | ||||
-rw-r--r-- | storage/innobase/handler/ha_innodb.cc | 4 | ||||
-rw-r--r-- | storage/xtradb/handler/ha_innodb.cc | 4 |
5 files changed, 109 insertions, 46 deletions
diff --git a/mysql-test/suite/innodb/r/foreign-keys.result b/mysql-test/suite/innodb/r/foreign-keys.result index 68528521fb6..10e0b1f3d7b 100644 --- a/mysql-test/suite/innodb/r/foreign-keys.result +++ b/mysql-test/suite/innodb/r/foreign-keys.result @@ -100,3 +100,27 @@ CREATE TABLE t2 (b INT, KEY(b)) ENGINE=InnoDB; INSERT INTO t2 VALUES(2); ALTER TABLE t2 ADD FOREIGN KEY(b) REFERENCES t1(a), LOCK=EXCLUSIVE; DROP TABLE t2, t1; +# +# MDEV-16060 - InnoDB: Failing assertion: ut_strcmp(index->name, key->name) +# +CREATE TABLE t1 (`pk` INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t2 LIKE t1; +FLUSH TABLES; +SET debug_sync='alter_table_intermediate_table_created SIGNAL ready WAIT_FOR go'; +ALTER TABLE t1 ADD FOREIGN KEY(pk) REFERENCES t2(pk) ON UPDATE CASCADE; +connect con1, localhost, root; +SET debug_sync='now WAIT_FOR ready'; +SET lock_wait_timeout=1; +UPDATE t2 SET pk=10 WHERE pk=1; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +PREPARE stmt FROM 'UPDATE t2 SET pk=10 WHERE pk=1'; +DEALLOCATE PREPARE stmt; +FLUSH TABLE t2; +SET debug_sync='now SIGNAL go'; +connection default; +disconnect con1; +connection default; +SET debug_sync='reset'; +SHOW OPEN TABLES FROM test; +Database Table In_use Name_locked +DROP TABLE t1, t2; diff --git a/mysql-test/suite/innodb/t/foreign-keys.test b/mysql-test/suite/innodb/t/foreign-keys.test index ced44a89d7c..a19fe3876f3 100644 --- a/mysql-test/suite/innodb/t/foreign-keys.test +++ b/mysql-test/suite/innodb/t/foreign-keys.test @@ -126,3 +126,35 @@ CREATE TABLE t2 (b INT, KEY(b)) ENGINE=InnoDB; INSERT INTO t2 VALUES(2); ALTER TABLE t2 ADD FOREIGN KEY(b) REFERENCES t1(a), LOCK=EXCLUSIVE; DROP TABLE t2, t1; + + +--echo # +--echo # MDEV-16060 - InnoDB: Failing assertion: ut_strcmp(index->name, key->name) +--echo # +CREATE TABLE t1 (`pk` INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t2 LIKE t1; +FLUSH TABLES; + +SET debug_sync='alter_table_intermediate_table_created SIGNAL ready WAIT_FOR go'; +send ALTER TABLE t1 ADD FOREIGN KEY(pk) REFERENCES t2(pk) ON UPDATE CASCADE; + +connect con1, localhost, root; +SET debug_sync='now WAIT_FOR ready'; +SET lock_wait_timeout=1; # change to 0 in 10.3 +--error ER_LOCK_WAIT_TIMEOUT +UPDATE t2 SET pk=10 WHERE pk=1; +PREPARE stmt FROM 'UPDATE t2 SET pk=10 WHERE pk=1'; +DEALLOCATE PREPARE stmt; +FLUSH TABLE t2; +SET debug_sync='now SIGNAL go'; + +connection default; +reap; + +# Cleanup +disconnect con1; + +connection default; +SET debug_sync='reset'; +SHOW OPEN TABLES FROM test; +DROP TABLE t1, t2; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 57333e7dc30..463aab9f589 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -8242,6 +8242,50 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, } } + /* + Normally, an attempt to modify an FK parent table will cause + FK children to be prelocked, so the table-being-altered cannot + be modified by a cascade FK action, because ALTER holds a lock + and prelocking will wait. + + But if a new FK is being added by this very ALTER, then the target + table is not locked yet (it's a temporary table). So, we have to + lock FK parents explicitly. + */ + if (alter_info->flags & Alter_info::ADD_FOREIGN_KEY) + { + List_iterator<Key> fk_list_it(alter_info->key_list); + + while (Key *key= fk_list_it++) + { + if (key->type != Key::FOREIGN_KEY) + continue; + + Foreign_key *fk= static_cast<Foreign_key*>(key); + char dbuf[NAME_LEN]; + char tbuf[NAME_LEN]; + const char *ref_db= fk->ref_db.str ? fk->ref_db.str : alter_ctx->new_db; + const char *ref_table= fk->ref_table.str; + MDL_request mdl_request; + + if (lower_case_table_names) + { + strmake_buf(dbuf, ref_db); + my_casedn_str(system_charset_info, dbuf); + strmake_buf(tbuf, ref_table); + my_casedn_str(system_charset_info, tbuf); + ref_db= dbuf; + ref_table= tbuf; + } + + mdl_request.init(MDL_key::TABLE, ref_db, ref_table, MDL_SHARED_NO_WRITE, + MDL_TRANSACTION); + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + DBUG_RETURN(true); + } + } + DBUG_RETURN(false); } @@ -9093,6 +9137,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* Mark that we have created table in storage engine. */ no_ha_table= false; + DEBUG_SYNC(thd, "alter_table_intermediate_table_created"); if (create_info->tmp_table()) { @@ -9126,52 +9171,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, true, true); if (!new_table) goto err_new_table_cleanup; - - /* - Normally, an attempt to modify an FK parent table will cause - FK children to be prelocked, so the table-being-altered cannot - be modified by a cascade FK action, because ALTER holds a lock - and prelocking will wait. - - But if a new FK is being added by this very ALTER, then the target - table is not locked yet (it's a temporary table). So, we have to - lock FK parents explicitly. - */ - if (alter_info->flags & Alter_info::ADD_FOREIGN_KEY) - { - List <FOREIGN_KEY_INFO> fk_list; - List_iterator<FOREIGN_KEY_INFO> fk_list_it(fk_list); - FOREIGN_KEY_INFO *fk; - - /* tables_opened can be > 1 only for MERGE tables */ - DBUG_ASSERT(tables_opened == 1); - DBUG_ASSERT(&table_list->next_global == thd->lex->query_tables_last); - - new_table->file->get_foreign_key_list(thd, &fk_list); - while ((fk= fk_list_it++)) - { - MDL_request mdl_request; - - if (lower_case_table_names) - { - char buf[NAME_LEN]; - uint len; - strmake_buf(buf, fk->referenced_db->str); - len = my_casedn_str(files_charset_info, buf); - thd->make_lex_string(fk->referenced_db, buf, len); - strmake_buf(buf, fk->referenced_table->str); - len = my_casedn_str(files_charset_info, buf); - thd->make_lex_string(fk->referenced_table, buf, len); - } - - mdl_request.init(MDL_key::TABLE, - fk->referenced_db->str, fk->referenced_table->str, - MDL_SHARED_NO_WRITE, MDL_TRANSACTION); - if (thd->mdl_context.acquire_lock(&mdl_request, - thd->variables.lock_wait_timeout)) - goto err_new_table_cleanup; - } - } } /* Note: In case of MERGE table, we do not attach children. We do not diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index cd605b6b791..cd3a09d824a 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -14455,6 +14455,10 @@ get_foreign_key_info( LEX_STRING* referenced_key_name; LEX_STRING* name = NULL; + if (row_is_mysql_tmp_table_name(foreign->foreign_table_name)) { + return NULL; + } + ptr = dict_remove_db_name(foreign->id); f_key_info.foreign_id = thd_make_lex_string(thd, 0, ptr, (uint) strlen(ptr), 1); diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc index 583ee3b54f9..37d6614a8b6 100644 --- a/storage/xtradb/handler/ha_innodb.cc +++ b/storage/xtradb/handler/ha_innodb.cc @@ -15084,6 +15084,10 @@ get_foreign_key_info( LEX_STRING* referenced_key_name; LEX_STRING* name = NULL; + if (row_is_mysql_tmp_table_name(foreign->foreign_table_name)) { + return NULL; + } + ptr = dict_remove_db_name(foreign->id); f_key_info.foreign_id = thd_make_lex_string(thd, 0, ptr, (uint) strlen(ptr), 1); |