diff --git a/mysql-test/include/wait_show_pattern.inc b/mysql-test/include/wait_show_pattern.inc new file mode 100644 index 0000000000000000000000000000000000000000..c9f84ce7f08fdbc6569053f9c08f65efd65dea7f --- /dev/null +++ b/mysql-test/include/wait_show_pattern.inc @@ -0,0 +1,51 @@ +# include/wait_show_pattern.inc +# +# SUMMARY +# +# Waits until output produced by SHOW statement which particular type is +# specified as parameter matches certain pattern or maximum time reached. +# +# NOTES +# +# Only the first row produced by the parameter statement is checked. +# +# USAGE +# +# let $show_type= <Tail of SHOW statement>; +# let $show_pattern= 'Pattern to be used for LIKE matching'; +# --source wait_show_pattern.inc +# +# EXAMPLES +# +# alter_table-big.test, wait_slave_status.inc +# +# SEE ALSO +# +# wait_slave_status.inc, wait_condition.inc (>=5.1) +# +############################################################################### + +--disable_query_log + +# We accept to wait maximum 30 seconds (0.2 sec/loop). +let $wait_counter= 150; +while ($wait_counter) +{ + let $result= `SHOW $show_type`; + let $success= `SELECT '$result' LIKE $show_pattern`; + if ($success) + { + let $wait_counter= 0; + } + if (!$success) + { + real_sleep 0.2; + dec $wait_counter; + } +} +if (!$success) +{ + echo Timeout in wait_show_pattern.inc \$show_type= $show_type \$show_pattern= $show_pattern (\$result= '$result'); +} + +--enable_query_log diff --git a/mysql-test/include/wait_slave_status.inc b/mysql-test/include/wait_slave_status.inc index 7d3636e673cf04840fd0fa3476f69b27294de370..d8d048527cf7ce075ee42536504a8939b3ceeba2 100644 --- a/mysql-test/include/wait_slave_status.inc +++ b/mysql-test/include/wait_slave_status.inc @@ -104,50 +104,21 @@ eval SELECT "let \$result_pattern= $result_pattern ;" AS ""; SELECT '--source include/wait_slave_status.inc' AS ""; -# We accept to wait maximum 30 seconds (0.2 sec/loop). -let $max_wait= 150; -while ($max_wait) -{ - let $my_val= `SHOW SLAVE STATUS`; - # Now we have the first record of the SHOW result set as one fat string - # within the variable $my_val. - - eval SET @my_val = '$my_val'; - # DEBUG eval SELECT @my_val AS "response to SHOW SLAVE STATUS"; +let $show_type= SLAVE STATUS; +let $show_pattern= $result_pattern; +--enable_query_log - eval SELECT @my_val LIKE $result_pattern INTO @success; - # @success is '1' if we have a match - # '0' if we have no match - # DEBUG SELECT @success; +--source include/wait_show_pattern.inc - let $success= `SELECT @success`; - let $no_success= `SELECT @success = 0`; - if ($success) - { - # We reached the expected result and want to jump out of the loop - # without unneeded sleeps. - # Attention: Do not set $max_wait to 0, because "while" with negative value - # does not work. - let $max_wait= 1; - } - if ($no_success) - { - # We did not reach the expected result and will have to sleep again - # or jump out of the loop, when max_wait is exhausted. - real_sleep 0.2; - } - dec $max_wait; -} ---enable_query_log -if ($no_success) +if (!$success) { let $message= ! Attention: Timeout in wait_slave_status.inc. | Possible reasons with decreasing probability: - | - The LIKE pattern ($result_pattern) is wrong, because the + | - The LIKE pattern is wrong, because the | testcase was altered or the layout of the | SHOW SLAVE STATUS result set changed. | - There is a new bug within the replication. - | - We met an extreme testing environment and $max_wait is + | - We met an extreme testing environment and timeout is | too small.; --source include/show_msg80.inc --echo DEBUG INFO START (wait_slave_status.inc): diff --git a/mysql-test/r/alter_table-big.result b/mysql-test/r/alter_table-big.result new file mode 100644 index 0000000000000000000000000000000000000000..873978c60dea449b06fb79ddc893443996b82a9a --- /dev/null +++ b/mysql-test/r/alter_table-big.result @@ -0,0 +1,18 @@ +drop table if exists t1, t2; +create table t1 (n1 int, n2 int, n3 int, +key (n1, n2, n3), +key (n2, n3, n1), +key (n3, n1, n2)); +create table t2 (i int); +alter table t1 disable keys; +reset master; +alter table t1 enable keys;; +insert into t2 values (1); +insert into t1 values (1, 1, 1); +show binlog events in 'master-bin.000001' from 98; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query 1 # use `test`; insert into t2 values (1) +master-bin.000001 # Query 1 # use `test`; alter table t1 enable keys +master-bin.000001 # Query 1 # use `test`; insert into t1 values (1, 1, 1) +drop tables t1, t2; +End of 5.0 tests diff --git a/mysql-test/t/alter_table-big.test b/mysql-test/t/alter_table-big.test new file mode 100644 index 0000000000000000000000000000000000000000..9a773f48a9c9784d04c4f5709ed4144ab39af6bb --- /dev/null +++ b/mysql-test/t/alter_table-big.test @@ -0,0 +1,62 @@ +# In order to be more or less robust test for bug#25044 has to take +# significant time (e.g. about 9 seconds on my (Dmitri's) computer) +# so we probably want execute it only in --big-test mode. +# Also in 5.1 this test will require statement-based binlog. +--source include/big_test.inc + + +# +# Test for bug #25044 "ALTER TABLE ... ENABLE KEYS acquires global +# 'opening tables' lock". +# +# ALTER TABLE ... ENABLE KEYS should not acquire LOCK_open mutex for +# the whole its duration as it prevents other queries from execution. +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +connect (addconroot, localhost, root,,); +connection default; +create table t1 (n1 int, n2 int, n3 int, + key (n1, n2, n3), + key (n2, n3, n1), + key (n3, n1, n2)); +create table t2 (i int); + +# Populating 't1' table with keys disabled, so ALTER TABLE .. ENABLE KEYS +# will run for some time +alter table t1 disable keys; +--disable_query_log +insert into t1 values (RAND()*1000,RAND()*1000,RAND()*1000); +let $1=19; +while ($1) +{ + eval insert into t1 select RAND()*1000,RAND()*1000,RAND()*1000 from t1; + dec $1; +} +--enable_query_log + +# Later we use binlog to check the order in which statements are +# executed so let us reset it first. +reset master; +--send alter table t1 enable keys; +connection addconroot; +let $show_type= PROCESSLIST; +let $show_pattern= '%Repair by sorting%alter table t1 enable keys%'; +--source include/wait_show_pattern.inc +# This statement should not be blocked by in-flight ALTER and therefore +# should be executed and written to binlog before ALTER TABLE ... ENABLE KEYS +# finishes. +insert into t2 values (1); +# And this should wait until the end of ALTER TABLE ... ENABLE KEYS. +insert into t1 values (1, 1, 1); +connection default; +--reap +# Check that statements were executed/binlogged in correct order. +--replace_column 2 # 5 # +show binlog events in 'master-bin.000001' from 98; + +# Clean up +drop tables t1, t2; + + +--echo End of 5.0 tests diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 4f5f4c29854b1896fba3461512575f9fdb1c4ae5..d9dadfbfd81137f2dcf3bc7972b1ead5bb4408b8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3163,19 +3163,30 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) && !table->s->tmp_table) // no need to touch frm { - VOID(pthread_mutex_lock(&LOCK_open)); - switch (alter_info->keys_onoff) { case LEAVE_AS_IS: error= 0; break; case ENABLE: + /* + wait_while_table_is_used() ensures that table being altered is + opened only by this thread and that TABLE::TABLE_SHARE::version + of TABLE object corresponding to this table is 0. + The latter guarantees that no DML statement will open this table + until ALTER TABLE finishes (i.e. until close_thread_tables()) + while the fact that the table is still open gives us protection + from concurrent DDL statements. + */ + VOID(pthread_mutex_lock(&LOCK_open)); wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); + VOID(pthread_mutex_unlock(&LOCK_open)); error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); /* COND_refresh will be signaled in close_thread_tables() */ break; case DISABLE: + VOID(pthread_mutex_lock(&LOCK_open)); wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); + VOID(pthread_mutex_unlock(&LOCK_open)); error=table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); /* COND_refresh will be signaled in close_thread_tables() */ break; @@ -3188,6 +3199,16 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, error= 0; } + VOID(pthread_mutex_lock(&LOCK_open)); + /* + Unlike to the above case close_cached_table() below will remove ALL + instances of TABLE from table cache (it will also remove table lock + held by this thread). So to make actual table renaming and writing + to binlog atomic we have to put them into the same critical section + protected by LOCK_open mutex. This also removes gap for races between + access() and mysql_rename_table() calls. + */ + if (!error && (new_name != table_name || new_db != db)) { thd->proc_info="rename";