diff options
134 files changed, 5084 insertions, 2162 deletions
diff --git a/.bzr-mysql/default.conf b/.bzr-mysql/default.conf index 4eab3d239d0..be5e1c348fa 100644 --- a/.bzr-mysql/default.conf +++ b/.bzr-mysql/default.conf @@ -1,4 +1,4 @@ [MYSQL] post_commit_to = "commits@lists.mysql.com" post_push_to = "commits@lists.mysql.com" -tree_name = "mysql-trunk-bugfixing" +tree_name = "mysql-trunk-runtime" diff --git a/.bzrignore b/.bzrignore index 7733bd2aa7c..f5f678ca360 100644 --- a/.bzrignore +++ b/.bzrignore @@ -996,6 +996,8 @@ libmysqld/.deps/sql_crypt.Po libmysqld/.deps/sql_cursor.Po libmysqld/.deps/sql_db.Po libmysqld/.deps/sql_delete.Po +libmysqld/.deps/sql_truncate.Po +libmysqld/.deps/datadict.Po libmysqld/.deps/sql_derived.Po libmysqld/.deps/sql_do.Po libmysqld/.deps/sql_error.Po @@ -1172,6 +1174,8 @@ libmysqld/sql_cursor.cc libmysqld/sql_cursor.h libmysqld/sql_db.cc libmysqld/sql_delete.cc +libmysqld/sql_truncate.cc +libmysqld/datadict.cc libmysqld/sql_derived.cc libmysqld/sql_do.cc libmysqld/sql_error.cc @@ -2062,6 +2066,8 @@ sql/.deps/sql_crypt.Po sql/.deps/sql_cursor.Po sql/.deps/sql_db.Po sql/.deps/sql_delete.Po +sql/.deps/sql_truncate.Po +sql/.deps/datadict.Po sql/.deps/sql_derived.Po sql/.deps/sql_do.Po sql/.deps/sql_error.Po diff --git a/BUILD/SETUP.sh b/BUILD/SETUP.sh index 0bc16f120e5..626f932e045 100755 --- a/BUILD/SETUP.sh +++ b/BUILD/SETUP.sh @@ -122,7 +122,7 @@ fi # Override -DFORCE_INIT_OF_VARS from debug_cflags. It enables the macro # LINT_INIT(), which is only useful for silencing spurious warnings # of static analysis tools. We want LINT_INIT() to be a no-op in Valgrind. -valgrind_flags="-UFORCE_INIT_OF_VARS -DHAVE_purify " +valgrind_flags="-USAFEMALLOC -UFORCE_INIT_OF_VARS -DHAVE_purify " valgrind_flags="$valgrind_flags -DMYSQL_SERVER_SUFFIX=-valgrind-max" valgrind_configs="--with-valgrind" # diff --git a/BUILD/build_mccge.sh b/BUILD/build_mccge.sh index 2d7c0d2a2c2..c3803610e73 100755 --- a/BUILD/build_mccge.sh +++ b/BUILD/build_mccge.sh @@ -1010,7 +1010,7 @@ set_ccache_usage() set_valgrind_flags() { if test "x$valgrind_flag" = "xyes" ; then - loc_valgrind_flags="-UFORCE_INIT_OF_VARS -DHAVE_purify " + loc_valgrind_flags="-USAFEMALLOC -UFORCE_INIT_OF_VARS -DHAVE_purify " loc_valgrind_flags="$loc_valgrind_flags -DMYSQL_SERVER_SUFFIX=-valgrind-max" compiler_flags="$compiler_flags $loc_valgrind_flags" with_flags="$with_flags --with-valgrind" diff --git a/include/heap.h b/include/heap.h index 27aefb5beda..a585371e18f 100644 --- a/include/heap.h +++ b/include/heap.h @@ -184,12 +184,22 @@ typedef struct st_heap_info typedef struct st_heap_create_info { + HP_KEYDEF *keydef; + ulong max_records; + ulong min_records; uint auto_key; /* keynr [1 - maxkey] for auto key */ uint auto_key_type; + uint keys; + uint reclength; ulonglong max_table_size; ulonglong auto_increment; my_bool with_auto_increment; my_bool internal_table; + /* + TRUE if heap_create should 'pin' the created share by setting + open_count to 1. Is only looked at if not internal_table. + */ + my_bool pin_share; } HP_CREATE_INFO; /* Prototypes for heap-functions */ @@ -197,6 +207,7 @@ typedef struct st_heap_create_info extern HP_INFO *heap_open(const char *name, int mode); extern HP_INFO *heap_open_from_share(HP_SHARE *share, int mode); extern HP_INFO *heap_open_from_share_and_register(HP_SHARE *share, int mode); +extern void heap_release_share(HP_SHARE *share, my_bool internal_table); extern int heap_close(HP_INFO *info); extern int heap_write(HP_INFO *info,const uchar *buff); extern int heap_update(HP_INFO *info,const uchar *old,const uchar *newdata); @@ -205,9 +216,9 @@ extern int heap_scan_init(HP_INFO *info); extern int heap_scan(register HP_INFO *info, uchar *record); extern int heap_delete(HP_INFO *info,const uchar *buff); extern int heap_info(HP_INFO *info,HEAPINFO *x,int flag); -extern int heap_create(const char *name, uint keys, HP_KEYDEF *keydef, - uint reclength, ulong max_records, ulong min_records, - HP_CREATE_INFO *create_info, HP_SHARE **share); +extern int heap_create(const char *name, + HP_CREATE_INFO *create_info, HP_SHARE **share, + my_bool *created_new_share); extern int heap_delete_table(const char *name); extern void heap_drop_table(HP_INFO *info); extern int heap_extra(HP_INFO *info,enum ha_extra_function function); diff --git a/include/thr_lock.h b/include/thr_lock.h index 1f4072ca0c5..37dc37f8017 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -54,12 +54,6 @@ enum thr_lock_type { TL_IGNORE=-1, */ TL_WRITE_ALLOW_WRITE, /* - Write lock, but allow other threads to read. - Used by ALTER TABLE in MySQL to allow readers - to use the table until ALTER TABLE is finished. - */ - TL_WRITE_ALLOW_READ, - /* WRITE lock used by concurrent insert. Will allow READ, if one could use concurrent insert on table. */ diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index fa5088f288b..1cdf8e41a74 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -63,7 +63,8 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sql_class.cc ../sql/sql_crypt.cc ../sql/sql_cursor.cc ../sql/sql_db.cc ../sql/sql_delete.cc ../sql/sql_derived.cc ../sql/sql_do.cc ../sql/sql_error.cc ../sql/sql_handler.cc - ../sql/sql_help.cc ../sql/sql_insert.cc + ../sql/sql_help.cc ../sql/sql_insert.cc ../sql/datadict.cc + ../sql/sql_truncate.cc ../sql/sql_lex.cc ../sql/keycaches.cc ../sql/sql_list.cc ../sql/sql_load.cc ../sql/sql_locale.cc ../sql/sql_binlog.cc ../sql/sql_manager.cc ../sql/sql_map.cc diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index 5ff23be42ba..3519c6e2541 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -63,7 +63,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ protocol.cc net_serv.cc opt_range.cc \ opt_sum.cc procedure.cc records.cc sql_acl.cc \ sql_load.cc discover.cc sql_locale.cc \ - sql_profile.cc \ + sql_profile.cc sql_truncate.cc datadict.cc \ sql_analyse.cc sql_base.cc sql_cache.cc sql_class.cc \ sql_crypt.cc sql_db.cc sql_delete.cc sql_error.cc sql_insert.cc \ sql_lex.cc sql_list.cc sql_manager.cc sql_map.cc \ diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index e727122293c..b36a97759d2 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -634,7 +634,6 @@ void *create_embedded_thd(int client_flag) thd->variables.option_bits |= OPTION_BIG_SELECTS; thd->proc_info=0; // Remove 'login' thd->command=COM_SLEEP; - thd->version=refresh_version; thd->set_time(); thd->init_for_queries(); thd->client_capabilities= client_flag; diff --git a/mysql-test/extra/binlog_tests/binlog_truncate.test b/mysql-test/extra/binlog_tests/binlog_truncate.test index dce33b3cef0..24cf363f780 100644 --- a/mysql-test/extra/binlog_tests/binlog_truncate.test +++ b/mysql-test/extra/binlog_tests/binlog_truncate.test @@ -25,3 +25,44 @@ TRUNCATE TABLE t2; source include/show_binlog_events.inc; DROP TABLE t1,t2; + +--echo # +--echo # Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +--echo # + +eval CREATE TABLE t1 (a INT) ENGINE=$engine; +eval CREATE TABLE t2 (a INT) ENGINE=$engine; +INSERT INTO t1 VALUES (1),(2); + +let $binlog_start = query_get_value("SHOW MASTER STATUS", Position, 1); +if (`select length('$before_truncate') > 0`) { + eval $before_truncate; +} + +--echo # Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; + +connect (truncate,localhost,root,,); +--echo # Connection: truncate +send TRUNCATE TABLE t1; + +connection default; +--echo # Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COMMIT; + +connection truncate; +--echo # Connection: truncate +--echo # Reaping TRUNCATE TABLE +--reap +SELECT COUNT(*) FROM t1; +SELECT COUNT(*) FROM t2; + +connection default; +--echo # Connection: default + +source include/show_binlog_events.inc; +disconnect truncate; +DROP TABLE t1,t2; diff --git a/mysql-test/include/check_concurrent_insert.inc b/mysql-test/include/check_concurrent_insert.inc index 6a9ada65562..f4bec3c9cdb 100644 --- a/mysql-test/include/check_concurrent_insert.inc +++ b/mysql-test/include/check_concurrent_insert.inc @@ -10,9 +10,9 @@ # $con_aux2 Name of the second auxiliary connection to be used by this # script. # $statement Statement to be checked. -# $restore_table Table which might be modified affected by statement to be -# checked and thus needs backing up before its execution -# and restoring after it (can be empty). +# $restore_table Table which might be modified by statement to be checked +# and thus needs backing up before its execution and +# restoring after it (can be empty). # # EXAMPLE # lock_sync.test @@ -25,7 +25,7 @@ set debug_sync= "RESET"; if (`SELECT '$restore_table' <> ''`) { ---eval create table t_backup select * from $restore_table; +--eval create temporary table t_backup select * from $restore_table; } connection $con_aux1; @@ -34,19 +34,19 @@ set debug_sync='after_lock_tables_takes_lock SIGNAL parked WAIT_FOR go'; connection $con_aux2; set debug_sync='now WAIT_FOR parked'; ---send_eval insert into $table values (0); +--send_eval insert into $table (i) values (0); --enable_result_log --enable_query_log connection default; # Wait until concurrent insert is successfully executed while # statement being checked has its tables locked. -# We use wait_condition.inc instead of simply executing +# We use wait_condition.inc instead of simply reaping # concurrent insert here in order to avoid deadlocks if test -# fails and timing out instead. +# fails and to time out gracefully instead. let $wait_condition= select count(*) = 0 from information_schema.processlist - where info = "insert into $table values (0)"; + where info = "insert into $table (i) values (0)"; --source include/wait_condition.inc --disable_result_log @@ -86,7 +86,7 @@ if (`SELECT '$restore_table' <> ''`) { --eval truncate table $restore_table; --eval insert into $restore_table select * from t_backup; -drop table t_backup; +drop temporary table t_backup; } # Clean-up. Reset DEBUG_SYNC facility after use. diff --git a/mysql-test/include/check_no_concurrent_insert.inc b/mysql-test/include/check_no_concurrent_insert.inc index 278ffeffb1e..57772dddefc 100644 --- a/mysql-test/include/check_no_concurrent_insert.inc +++ b/mysql-test/include/check_no_concurrent_insert.inc @@ -10,9 +10,9 @@ # $con_aux2 Name of the second auxiliary connection to be used by this # script. # $statement Statement to be checked. -# $restore_table Table which might be modified affected by statement to be -# checked and thus needs backing up before its execution -# and restoring after it (can be empty). +# $restore_table Table which might be modified by statement to be checked +# and thus needs backing up before its execution and +# restoring after it (can be empty). # # EXAMPLE # lock_sync.test @@ -25,7 +25,7 @@ set debug_sync= "RESET"; if (`SELECT '$restore_table' <> ''`) { ---eval create table t_backup select * from $restore_table; +--eval create temporary table t_backup select * from $restore_table; } connection $con_aux1; @@ -34,7 +34,7 @@ set debug_sync='after_lock_tables_takes_lock SIGNAL parked WAIT_FOR go'; connection $con_aux2; set debug_sync='now WAIT_FOR parked'; ---send_eval insert into $table values (0); +--send_eval insert into $table (i) values (0); --enable_result_log --enable_query_log @@ -43,7 +43,7 @@ connection default; # of our statement. let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Table lock" and info = "insert into $table values (0)"; + where state = "Table lock" and info = "insert into $table (i) values (0)"; --source include/wait_condition.inc --disable_result_log @@ -71,7 +71,7 @@ if (`SELECT '$restore_table' <> ''`) { --eval truncate table $restore_table; --eval insert into $restore_table select * from t_backup; -drop table t_backup; +drop temporary table t_backup; } # Clean-up. Reset DEBUG_SYNC facility after use. diff --git a/mysql-test/include/check_no_row_lock.inc b/mysql-test/include/check_no_row_lock.inc index 958161b9b7f..c08e7f35b10 100644 --- a/mysql-test/include/check_no_row_lock.inc +++ b/mysql-test/include/check_no_row_lock.inc @@ -29,9 +29,9 @@ connection default; # Wait until statement is successfully executed while # all rows in table are X-locked. This means that it # does not acquire any row locks. -# We use wait_condition.inc instead of simply executing +# We use wait_condition.inc instead of simply reaping # statement here in order to avoid deadlocks if test -# fails and timing out instead. +# fails and to time out gracefully instead. let $wait_condition= select count(*) = 0 from information_schema.processlist where info = "$statement"; diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index 66648aaf1bf..fe6abe13892 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -1351,6 +1351,13 @@ connection con1; SELECT * FROM t1; ROLLBACK; +--echo # Switch to connection con2 +connection con2; +ROLLBACK; + +--echo # Switch to connection con1 +connection con1; + --echo # 2. test for serialized update: CREATE TABLE t2 (a INT); @@ -1435,6 +1442,7 @@ connection con2; --reap SELECT * FROM t1; +--enable_abort_on_error connection default; disconnect con1; disconnect con2; @@ -1556,3 +1564,36 @@ SELECT 1 FROM (SELECT COUNT(DISTINCT c1) DROP TABLE t1; --echo End of 5.1 tests + +--echo # +--echo # Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +--echo # +--echo # Check that a TRUNCATE TABLE statement, needing an exclusive meta +--echo # data lock, waits for a shared metadata lock owned by a concurrent +--echo # transaction. +--echo # + +eval CREATE TABLE t1 (a INT) ENGINE=$engine_type; +INSERT INTO t1 VALUES (1),(2),(3); +BEGIN; +SELECT * FROM t1 ORDER BY a; +--echo # Connection con1 +connect (con1, localhost, root,,); +--send TRUNCATE TABLE t1; +--echo # Connection default +connection default; +let $wait_condition= SELECT COUNT(*)=1 FROM information_schema.processlist + WHERE state='Waiting for table' AND info='TRUNCATE TABLE t1'; +--source include/wait_condition.inc +SELECT * FROM t1 ORDER BY a; +ROLLBACK; +--echo # Connection con1 +connection con1; +--echo # Reaping TRUNCATE TABLE +--reap +SELECT * FROM t1; +--echo # Disconnect con1 +disconnect con1; +--echo # Connection default +connection default; +DROP TABLE t1; diff --git a/mysql-test/r/archive.result b/mysql-test/r/archive.result index 88f38a13b4a..685e1e5ba4b 100644 --- a/mysql-test/r/archive.result +++ b/mysql-test/r/archive.result @@ -12749,6 +12749,14 @@ ERROR HY000: Can't find file: 't1' (errno: 2) DROP TABLE t1; ERROR 42S02: Unknown table 't1' # +# Ensure that TRUNCATE fails for non-empty archive tables. +# +CREATE TABLE t1 (a INT) ENGINE=ARCHIVE; +INSERT INTO t1 VALUES (1); +TRUNCATE TABLE t1; +ERROR HY000: Table storage engine for 't1' doesn't have this option +DROP TABLE t1; +# # BUG#46565 - repair of partition fail for archive engine # # Installing corrupted table files for t1. diff --git a/mysql-test/r/commit.result b/mysql-test/r/commit.result new file mode 100644 index 00000000000..8620d2077c6 --- /dev/null +++ b/mysql-test/r/commit.result @@ -0,0 +1,272 @@ +# +# Bug#20837 Apparent change of isolation level +# during transaction +# +# Bug#53343 completion_type=1, COMMIT/ROLLBACK +# AND CHAIN don't preserve the isolation +# level +connection default; +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +CREATE TABLE t1 (s1 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +COMMIT; +START TRANSACTION; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +ERROR 25001: Transaction isolation level can't be changed while a transaction is in progress +COMMIT; +SET @@autocommit=0; +COMMIT; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +START TRANSACTION; +SELECT @@tx_isolation; +@@tx_isolation +REPEATABLE-READ +Should be REPEATABLE READ +SELECT * FROM t1; +s1 +1 +2 +SELECT @@tx_isolation; +@@tx_isolation +REPEATABLE-READ +Should be REPEATABLE READ +INSERT INTO t1 VALUES (-1); +SELECT @@tx_isolation; +@@tx_isolation +REPEATABLE-READ +Should be REPEATABLE READ +COMMIT; +START TRANSACTION; +SELECT * FROM t1; +s1 +1 +2 +-1 +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (1000); +COMMIT; +connection default +We should not be able to read the '1000' +SELECT * FROM t1; +s1 +1 +2 +-1 +COMMIT; +Now, the '1000' should appear. +START TRANSACTION; +SELECT * FROM t1; +s1 +1 +2 +-1 +1000 +COMMIT; +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +connection default +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; +connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; +connection default +SELECT COUNT(*) FROM t1 WHERE s1 = 1001; +COUNT(*) +1 +Should be 1 +COMMIT AND CHAIN; +connection con1 +INSERT INTO t1 VALUES (1002); +COMMIT; +connection default +SELECT COUNT(*) FROM t1 WHERE s1 = 1002; +COUNT(*) +1 +Should be 1 +COMMIT; +SELECT * FROM t1; +s1 +1 +2 +-1 +1000 +1001 +1002 +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT; +connection default +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; +connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; +connection default +SELECT COUNT(*) FROM t1 WHERE s1 = 1001; +COUNT(*) +1 +Should be 1 +ROLLBACK AND CHAIN; +connection con1 +INSERT INTO t1 VALUES (1002); +COMMIT; +connection default +SELECT COUNT(*) FROM t1 WHERE s1 = 1002; +COUNT(*) +1 +Should be 1 +COMMIT; +SELECT * FROM t1; +s1 +1 +2 +-1 +1001 +1002 +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT; +SET @@completion_type=1; +connection default +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; +connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; +connection default +SELECT * FROM t1 WHERE s1 >= 1000; +s1 +1001 +Should see 1001 +COMMIT AND NO CHAIN; +default transaction is now in REPEATABLE READ +connection con1 +INSERT INTO t1 VALUES (1002); +COMMIT; +connection default +SELECT * FROM t1 WHERE s1 >= 1000; +s1 +1001 +1002 +Should see 1001 and 1002 +connection con1 +INSERT INTO t1 VALUES (1003); +COMMIT; +connection default +SELECT * FROM t1 WHERE s1 >= 1000; +s1 +1001 +1002 +Should see 1001 and 1002, but NOT 1003 +COMMIT; +SELECT * FROM t1; +s1 +1 +2 +-1 +1001 +1002 +1003 +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT AND NO CHAIN; +SET @@completion_type=0; +COMMIT; +connection default +SET @@completion_type=1; +COMMIT AND NO CHAIN; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; +connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; +connection default +SELECT * FROM t1 WHERE s1 >= 1000; +s1 +1001 +Should see 1001 +ROLLBACK AND NO CHAIN; +default transaction is now in REPEATABLE READ +connection con1 +INSERT INTO t1 VALUES (1002); +COMMIT; +connection default +SELECT * FROM t1 WHERE s1 >= 1000; +s1 +1001 +1002 +Should see 1001 and 1002 +connection con1 +INSERT INTO t1 VALUES (1003); +COMMIT; +connection default +SELECT * FROM t1 WHERE s1 >= 1000; +s1 +1001 +1002 +Should see 1001 and 1002, but NOT 1003 +COMMIT; +SELECT * FROM t1; +s1 +1 +2 +-1 +1001 +1002 +1003 +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT AND NO CHAIN; +SET @@completion_type=0; +COMMIT; +connection default +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +START TRANSACTION; +SELECT * FROM t1; +s1 +1 +2 +-1 +connection con1 +INSERT INTO t1 VALUES (1000); +COMMIT; +connection default +SELECT * FROM t1; +s1 +1 +2 +-1 +Should get same result as above (i.e should not read '1000') +COMMIT; +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT; +SET @@completion_type=1; +COMMIT AND NO CHAIN; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (1000); +SELECT * FROM t1; +s1 +1000 +Should read '1000' +connection con1 +INSERT INTO t1 VALUES (1001); +COMMIT; +connection default +SELECT * FROM t1; +s1 +1000 +Should only read the '1000' as this transaction is now in REP READ +COMMIT AND NO CHAIN; +SET @@completion_type=0; +COMMIT AND NO CHAIN; +SET @autocommit=1; +COMMIT; +DROP TABLE t1; +# +# End of test cases for Bug#20837 +# diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index e37f9d580ba..fce775c5952 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -99,6 +99,14 @@ create table t1 (`` int); ERROR 42000: Incorrect column name '' create table t1 (i int, index `` (i)); ERROR 42000: Incorrect index name '' +create table t1 (i int); +lock tables t1 read; +create table t2 (j int); +ERROR HY000: Table 't2' was not locked with LOCK TABLES +create temporary table t2 (j int); +drop temporary table t2; +unlock tables; +drop table t1; create table t1 (a int auto_increment not null primary key, B CHAR(20)); insert into t1 (b) values ("hello"),("my"),("world"); create table t2 (key (b)) select * from t1; @@ -377,6 +385,17 @@ ERROR 42S01: Table 't3' already exists drop table t1, t2, t3; drop table t3; drop database mysqltest; +create table t1 (i int); +create table t2 (j int); +lock tables t1 read; +create table t3 like t1; +ERROR HY000: Table 't3' was not locked with LOCK TABLES +create temporary table t3 like t1; +drop temporary table t3; +create temporary table t3 like t2; +ERROR HY000: Table 't2' was not locked with LOCK TABLES +unlock tables; +drop tables t1, t2; SET SESSION storage_engine="heap"; SELECT @@storage_engine; @@storage_engine @@ -2033,3 +2052,39 @@ ID 3 DROP TABLE t1; DROP TEMPORARY TABLE t2; +# +# Bug #22909 "Using CREATE ... LIKE is possible to create field +# with invalid default value" +# +# Altough original bug report suggests to use older version of MySQL +# for producing .FRM with invalid defaults we use sql_mode to achieve +# the same effect. +drop tables if exists t1, t2; +# Attempt to create table with invalid default should fail in normal mode +create table t1 (dt datetime default '2008-02-31 00:00:00'); +ERROR 42000: Invalid default value for 'dt' +set @old_mode= @@sql_mode; +set @@sql_mode='ALLOW_INVALID_DATES'; +# The same should be possible in relaxed mode +create table t1 (dt datetime default '2008-02-31 00:00:00'); +set @@sql_mode= @old_mode; +# In normal mode attempt to create copy of table with invalid +# default should fail +create table t2 like t1; +ERROR 42000: Invalid default value for 'dt' +set @@sql_mode='ALLOW_INVALID_DATES'; +# But should work in relaxed mode +create table t2 like t1; +# Check that table definitions match +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `dt` datetime DEFAULT '2008-02-31 00:00:00' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +show create table t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `dt` datetime DEFAULT '2008-02-31 00:00:00' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +set @@sql_mode= @old_mode; +drop tables t1, t2; diff --git a/mysql-test/r/drop.result b/mysql-test/r/drop.result index 17ec2b09a0a..f1712a650b1 100644 --- a/mysql-test/r/drop.result +++ b/mysql-test/r/drop.result @@ -157,3 +157,13 @@ Error 1051 Unknown table 't1' # -- # -- End of Bug#37431. # -- +# +# Bug#54282 Crash in MDL_context::upgrade_shared_lock_to_exclusive +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +LOCK TABLE t1 WRITE; +DROP TABLE t1, t1; +ERROR 42000: Not unique table/alias: 't1' +UNLOCK TABLES; +DROP TABLE t1; diff --git a/mysql-test/r/innodb_mysql_lock2.result b/mysql-test/r/innodb_mysql_lock2.result index aed704e6b3e..17dd747de6f 100644 --- a/mysql-test/r/innodb_mysql_lock2.result +++ b/mysql-test/r/innodb_mysql_lock2.result @@ -178,8 +178,7 @@ end| # 1.1 Simple SELECT statement. # # No locks are necessary as this statement won't be written -# to the binary log and thanks to how MyISAM works SELECT -# will see version of the table prior to concurrent insert. +# to the binary log and InnoDB supports snapshots. Success: 'select * from t1' doesn't take row locks on 't1'. # # 1.2 Multi-UPDATE statement. @@ -484,7 +483,7 @@ Success: 'insert into t2 values (f13((select i+10 from t1 where i=1)))' takes sh # row locks on the data it reads. Success: 'call p2(@a)' doesn't take row locks on 't1'. # -# 5.2 Function that modifes data and uses CALL, +# 5.2 Function that modifies data and uses CALL, # which reads a table through SELECT. # # Since a call to such function is written to the binary @@ -562,3 +561,68 @@ drop view v1, v2; drop procedure p1; drop procedure p2; drop table t1, t2, t3, t4, t5; +# +# Test for bug#51263 "Deadlock between transactional SELECT +# and ALTER TABLE ... REBUILD PARTITION". +# +drop table if exists t1, t2; +create table t1 (i int auto_increment not null primary key) engine=innodb; +create table t2 (i int) engine=innodb; +insert into t1 values (1), (2), (3), (4), (5); +begin; +# Acquire SR metadata lock on t1 and LOCK_S row-locks on its rows. +insert into t2 select count(*) from t1; +# Switching to connection 'con1'. +# Sending: +alter table t1 add column j int; +# Switching to connection 'default'. +# Wait until ALTER is blocked because it tries to upgrade SNW +# metadata lock to X lock. +# It should not be blocked during copying data to new version of +# table as it acquires LOCK_S locks on rows of old version, which +# are compatible with locks acquired by connection 'con1'. +# The below statement will deadlock because it will try to acquire +# SW lock on t1, which will conflict with ALTER's SNW lock. And +# ALTER will be waiting for this connection to release its SR lock. +# This deadlock should be detected by an MDL subsystem and this +# statement should be aborted with an appropriate error. +insert into t1 values (6); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock ALTER TABLE. +commit; +# Switching to connection 'con1'. +# Reaping ALTER TABLE. +# Switching to connection 'default'. +# +# Now test for scenario in which bug was reported originally. +# +drop tables t1, t2; +create table t1 (i int auto_increment not null primary key) engine=innodb +partition by hash (i) partitions 4; +create table t2 (i int) engine=innodb; +insert into t1 values (1), (2), (3), (4), (5); +begin; +# Acquire SR metadata lock on t1. +select * from t1; +i +1 +2 +3 +4 +5 +# Switching to connection 'con1'. +# Sending: +alter table t1 rebuild partition p0; +# Switching to connection 'default'. +# Wait until ALTER is blocked because of active SR lock. +# The below statement should succeed as transaction +# has SR metadata lock on t1 and only going to read +# rows from it. +insert into t2 select count(*) from t1; +# Unblock ALTER TABLE. +commit; +# Switching to connection 'con1'. +# Reaping ALTER TABLE. +# Switching to connection 'default'. +# Clean-up. +drop tables t1, t2; diff --git a/mysql-test/r/lock_sync.result b/mysql-test/r/lock_sync.result index e6265f1cb5e..3682f0df26a 100644 --- a/mysql-test/r/lock_sync.result +++ b/mysql-test/r/lock_sync.result @@ -511,7 +511,7 @@ Success: 'insert into t2 values (f13((select i+10 from t1 where i=1)))' doesn't # strong locks on the data it reads. Success: 'call p2(@a)' allows concurrent inserts into 't1'. # -# 5.2 Function that modifes data and uses CALL, +# 5.2 Function that modifies data and uses CALL, # which reads a table through SELECT. # # Since a call to such function is written to the binary diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index b78b8dadc77..67d778211dd 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -330,9 +330,9 @@ select column_name from information_schema.columns where table_schema='test' and table_name='t1'; column_name c1 -select count(*) from t1; -count(*) -4 +# Disable result log to make test robust against +# effects of concurrent insert. +select * from t1; insert into t1 values (1); # Check that SNW lock is not compatible with SW lock. # Again we use ALTER TABLE which fails after opening @@ -1765,6 +1765,7 @@ drop tables t1, t2; # locking subsystem. # drop tables if exists t0, t1, t2, t3, t4, t5; +set debug_sync= 'RESET'; create table t1 (i int); create table t2 (j int); create table t3 (k int); @@ -1943,6 +1944,98 @@ commit; # Reap ALTER TABLE ... RENAME. drop table t2; # +# Test that in situation when MDL subsystem detects a deadlock +# but it turns out that it can be resolved by backing-off locks +# acquired by one of participating transactions (which is +# possible when one of transactions consists only of currently +# executed statement, e.g. in autocommit mode) no error is +# reported. +# +create table t1 (i int); +create table t2 (j int); +# Ensure that the below SELECT stops once it has acquired metadata +# lock on table 't2'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +select * from t2, t1; +# +# Switching to connection 'deadlock_con1'. +# Wait till SELECT acquires MDL on 't2' and starts waiting for signal. +set debug_sync= 'now WAIT_FOR locked'; +# Sending: +lock tables t1 write, t2 write; +# +# Switching to connection 'deadlock_con2'. +# Wait until LOCK TABLES acquires SNRW lock on 't1' and is blocked +# while trying to acquire SNRW lock on 't1'. +# Resume SELECT execution, this should eventually unblock LOCK TABLES. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'deadlock_con1'. +# Reaping LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# Reaping SELECT. It succeed and not report ER_LOCK_DEADLOCK error. +j i +drop tables t1, t2; +# +# Test coverage for situation in which a race has happened +# during deadlock detection process which led to unwarranted +# ER_LOCK_DEADLOCK error. +# +create table t1 (i int); +# Ensure that ALTER waits once it has acquired SNW lock. +set debug_sync='after_open_table_mdl_shared SIGNAL parked1 WAIT_FOR go1'; +# Sending: +alter table t1 add column j int; +# +# Switching to connection 'deadlock_con1'. +# Wait till ALTER acquires SNW lock and stops. +set debug_sync='now WAIT_FOR parked1'; +# Ensure that INSERT is paused once it detects that there is +# a conflicting metadata lock so it has to wait, but before +# deadlock detection is run. +set debug_sync='mdl_acquire_lock_wait SIGNAL parked2 WAIT_FOR go2'; +# Sending: +insert into t1 values (); +# +# Switching to connection 'deadlock_con2'. +# Wait till INSERT is paused. +set debug_sync='now WAIT_FOR parked2'; +# Resume ALTER execution. Eventually it will release its +# metadata lock and INSERT's request for SW lock will be +# satisified. +set debug_sync='now SIGNAL go1'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +# Add a new request for SNW lock to waiting graph. +# Sending: +alter table t1 drop column j; +# +# Switching to connection 'deadlock_con2'. +# Wait until ALTER is blocked. +# Resume INSERT so it can start deadlock detection. +# +# At this point there is a discrepancy between the fact that INSERT's +# SW lock is already satisfied, but INSERT's connection is still +# marked as waiting for it. Looking for a loop in waiters graph +# without additional checks has detected a deadlock (INSERT waits +# for SW lock; which is not granted because of pending SNW lock from +# ALTER; which waits for active SW lock from INSERT). Since requests +# for SW and SNW locks have same weight ALTER was selected as a victim +# and ended with ER_LOCK_DEADLOCK error. +set debug_sync='now SIGNAL go2'; +# +# Switching to connection 'deadlock_con1'. +# Reaping INSERT. +# +# Switching to connection 'default'. +# Reaping ALTER. It should succeed and not produce ER_LOCK_DEADLOCK. +drop table t1; +set debug_sync= 'RESET'; +# # Test for bug #46748 "Assertion in MDL_context::wait_for_locks() # on INSERT + CREATE TRIGGER". # @@ -2175,7 +2268,7 @@ alter table t1 add column e int, rename to t2;; # # Switching to connection 'default'. set debug_sync='now WAIT_FOR alter_table_locked'; -set debug_sync='before_open_table_wait_refresh SIGNAL alter_go'; +set debug_sync='mdl_acquire_lock_wait SIGNAL alter_go'; # The below statement should get ER_LOCK_DEADLOCK error # (i.e. it should not allow ALTER to proceed, and then # fail due to 't1' changing its name to 't2'). @@ -2382,6 +2475,45 @@ commit; set debug_sync= 'RESET'; drop table t1; # +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +# Ensure that a acquired lock is not given up due to a conflict. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); +# Connection: con1 +SET debug_sync='lock_table_for_truncate SIGNAL parked_truncate WAIT_FOR go_truncate'; +TRUNCATE TABLE t1; +# Connection: default +SET debug_sync='now WAIT_FOR parked_truncate'; +# Connection: con2 +SET debug_sync='after_open_table_ignore_flush SIGNAL parked_show WAIT_FOR go_show'; +SHOW FIELDS FROM t1; +# Connection: default +SET debug_sync='now WAIT_FOR parked_show'; +# Connection: con3 +SET debug_sync='after_flush_unlock SIGNAL parked_flush WAIT_FOR go_flush'; +FLUSH TABLES t1; +# Connection: default +SET debug_sync='now WAIT_FOR parked_flush'; +SET debug_sync='now SIGNAL go_truncate'; +# Connection: con1 +# Reaping... +# Connection: default +SET debug_sync= 'now SIGNAL go_show'; +# Connection: con2 (SHOW FIELDS FROM t1) +# Reaping... +Field Type Null Key Default Extra +a int(11) YES NULL +# Connection: default +SET debug_sync= 'now SIGNAL go_flush'; +# Connection: con3 (FLUSH TABLES t1) +# Reaping... +# Connection: default +SET debug_sync= 'RESET'; +DROP TABLE t1; +# # Bug#52856 concurrent show columns or show full columns causes a crash!!! # CREATE TABLE t1(a CHAR(255)); diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index e46d8e75ab1..8f7ebb06c06 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -2699,4 +2699,23 @@ LOCK TABLE m1 WRITE; ALTER TABLE m1 ADD INDEX (c1); UNLOCK TABLES; DROP TABLE m1, t1; +# +# Test for bug #37371 "CREATE TABLE LIKE merge loses UNION parameter" +# +drop tables if exists t1, m1, m2; +create table t1 (i int) engine=myisam; +create table m1 (i int) engine=mrg_myisam union=(t1) insert_method=first; +create table m2 like m1; +# Table definitions should match +show create table m1; +Table Create Table +m1 CREATE TABLE `m1` ( + `i` int(11) DEFAULT NULL +) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=FIRST UNION=(`t1`) +show create table m2; +Table Create Table +m2 CREATE TABLE `m2` ( + `i` int(11) DEFAULT NULL +) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=FIRST UNION=(`t1`) +drop tables m1, m2, t1; End of 6.0 tests diff --git a/mysql-test/r/parser_not_embedded.result b/mysql-test/r/parser_not_embedded.result index 140b13c9864..5a5ae9f8178 100644 --- a/mysql-test/r/parser_not_embedded.result +++ b/mysql-test/r/parser_not_embedded.result @@ -47,3 +47,48 @@ +----------+--------+ | 4 | 4 | +----------+--------+ +# Bug#46527 "COMMIT AND CHAIN RELEASE does not make sense" +# +COMMIT AND CHAIN RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE' at line 1 +COMMIT AND NO CHAIN RELEASE; +COMMIT RELEASE; +COMMIT CHAIN RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN RELEASE' at line 1 +COMMIT NO CHAIN RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN RELEASE' at line 1 +COMMIT AND NO RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE' at line 1 +COMMIT AND RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE' at line 1 +COMMIT NO RELEASE; +COMMIT CHAIN NO RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN NO RELEASE' at line 1 +COMMIT NO CHAIN NO RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN NO RELEASE' at line 1 +COMMIT AND RELEASE CHAIN; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE CHAIN' at line 1 +COMMIT AND NO CHAIN NO RELEASE; +ROLLBACK AND CHAIN RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE' at line 1 +ROLLBACK AND NO CHAIN RELEASE; +ROLLBACK RELEASE; +ROLLBACK CHAIN RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN RELEASE' at line 1 +ROLLBACK NO CHAIN RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN RELEASE' at line 1 +ROLLBACK AND NO RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE' at line 1 +ROLLBACK AND RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE' at line 1 +ROLLBACK NO RELEASE; +ROLLBACK CHAIN NO RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN NO RELEASE' at line 1 +ROLLBACK NO CHAIN NO RELEASE; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CHAIN NO RELEASE' at line 1 +ROLLBACK AND RELEASE CHAIN; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE CHAIN' at line 1 +ROLLBACK AND NO CHAIN NO RELEASE; +# +# End of 5.5 tests +# diff --git a/mysql-test/r/partition_innodb_semi_consistent.result b/mysql-test/r/partition_innodb_semi_consistent.result index 48a1bb3d258..1ff7ffba6db 100644 --- a/mysql-test/r/partition_innodb_semi_consistent.result +++ b/mysql-test/r/partition_innodb_semi_consistent.result @@ -18,6 +18,7 @@ set autocommit=0; update t1 set a=10 where a=5; ERROR HY000: Lock wait timeout exceeded; try restarting transaction commit; +commit; set session transaction isolation level read committed; update t1 set a=10 where a=5; select * from t1 where a=2 for update; @@ -64,6 +65,7 @@ a b # Switch to connection con2 UPDATE t1 SET b = 21 WHERE a = 1; ERROR HY000: Lock wait timeout exceeded; try restarting transaction +ROLLBACK; # Switch to connection con1 SELECT * FROM t1; a b @@ -99,6 +101,7 @@ a b SELECT * FROM t1; a b 1 init+con1+con2 +COMMIT; # Switch to connection con1 # 3. test for updated key column: TRUNCATE t1; diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 16f6657ceeb..0e75ebd57ec 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -695,11 +695,11 @@ REPEATABLE-READ set transaction isolation level read committed; execute stmt; @@tx_isolation -READ-COMMITTED +REPEATABLE-READ set transaction isolation level serializable; execute stmt; @@tx_isolation -SERIALIZABLE +REPEATABLE-READ set @@tx_isolation=default; execute stmt; @@tx_isolation diff --git a/mysql-test/r/rename.result b/mysql-test/r/rename.result index 1257a668cce..edf05d0c5d3 100644 --- a/mysql-test/r/rename.result +++ b/mysql-test/r/rename.result @@ -55,14 +55,15 @@ t2 t4 drop table t2, t4; End of 4.1 tests +# +# Bug#14959: "ALTER TABLE isn't able to rename a view" +# Bug#53976: "ALTER TABLE RENAME is allowed on views +# (not documented, broken)" +# create table t1(f1 int); create view v1 as select * from t1; alter table v1 rename to v2; -alter table v1 rename to v2; -ERROR 42S02: Table 'test.v1' doesn't exist -rename table v2 to v1; -rename table v2 to v1; -ERROR 42S01: Table 'v1' already exists +ERROR HY000: 'test.v1' is not BASE TABLE drop view v1; drop table t1; End of 5.0 tests diff --git a/mysql-test/r/sp_sync.result b/mysql-test/r/sp_sync.result index a16babaef67..174b3c60745 100644 --- a/mysql-test/r/sp_sync.result +++ b/mysql-test/r/sp_sync.result @@ -59,30 +59,31 @@ SET DEBUG_SYNC= 'RESET'; # # Bug #48246 assert in close_thread_table # +CREATE TABLE t0 (b INTEGER); CREATE TABLE t1 (a INTEGER); CREATE FUNCTION f1(b INTEGER) RETURNS INTEGER RETURN 1; -CREATE PROCEDURE p1() SELECT COUNT(f1(a)) FROM t1; +CREATE PROCEDURE p1() SELECT COUNT(f1(a)) FROM t1, t0; +INSERT INTO t0 VALUES(1); INSERT INTO t1 VALUES(1), (2); # Connection 2 CALL p1(); COUNT(f1(a)) 2 -# Connection default -SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR called'; -# Sending: -CREATE TABLE t1 (a INTEGER); -# Connection 2 -SET DEBUG_SYNC= 'now WAIT_FOR locked'; -SET DEBUG_SYNC= 'before_open_table_wait_refresh SIGNAL called WAIT_FOR created'; -# This call used to cause an assertion. MDL locking conflict will -# cause back-off and retry. A variable indicating if a prelocking list -# exists, used to be not reset properly causing an eventual assert. +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked_t1 WAIT_FOR go_for_t0'; +# This call used to cause an assertion. MDL deadlock with upcoming +# LOCK TABLES statement will cause back-off and retry. +# A variable indicating if a prelocking list exists, used to be not +# reset properly causing an eventual assert. # Sending: CALL p1(); # Connection default -# Reaping: CREATE TABLE t1 (a INTEGER) -ERROR 42S01: Table 't1' already exists -SET DEBUG_SYNC= 'now SIGNAL created'; +SET DEBUG_SYNC= 'now WAIT_FOR locked_t1'; +# Issue LOCK TABLES statement which will enter in MDL deadlock +# with CALL statement and as result will cause it to perform +# back-off and retry. +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL go_for_t0'; +LOCK TABLES t0 WRITE, t1 WRITE; +UNLOCK TABLES; # Connection 2 # Reaping: CALL p1() COUNT(f1(a)) @@ -90,5 +91,5 @@ COUNT(f1(a)) # Connection default DROP PROCEDURE p1; DROP FUNCTION f1; -DROP TABLE t1; +DROP TABLES t0, t1; SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/r/truncate.result b/mysql-test/r/truncate.result index 8f237c81e75..773075f9dae 100644 --- a/mysql-test/r/truncate.result +++ b/mysql-test/r/truncate.result @@ -99,7 +99,7 @@ LOCK TABLE t1 WRITE; SELECT * FROM v1; ERROR HY000: Table 'v1' was not locked with LOCK TABLES TRUNCATE v1; -ERROR 42S02: Table 'test.v1' doesn't exist +ERROR HY000: Table 'v1' was not locked with LOCK TABLES SELECT * FROM v1; ERROR HY000: Table 'v1' was not locked with LOCK TABLES UNLOCK TABLES; @@ -107,7 +107,7 @@ LOCK TABLE t1 WRITE, t2 WRITE; SELECT * FROM v1; ERROR HY000: Table 'v1' was not locked with LOCK TABLES TRUNCATE v1; -ERROR 42S02: Table 'test.v1' doesn't exist +ERROR HY000: Table 'v1' was not locked with LOCK TABLES SELECT * FROM v1; ERROR HY000: Table 'v1' was not locked with LOCK TABLES UNLOCK TABLES; @@ -117,7 +117,7 @@ c1 1 3 TRUNCATE v1; -ERROR 42S02: Table 'test.v1' doesn't exist +ERROR HY000: Table 'v1' was not locked with LOCK TABLES SELECT * FROM v1; c1 1 @@ -129,7 +129,7 @@ c1 1 3 TRUNCATE v1; -ERROR 42S02: Table 'test.v1' doesn't exist +ERROR HY000: Table 'v1' was not locked with LOCK TABLES SELECT * FROM v1; c1 1 diff --git a/mysql-test/r/truncate_coverage.result b/mysql-test/r/truncate_coverage.result index 7a5021f55e2..a7a4b9c70f4 100644 --- a/mysql-test/r/truncate_coverage.result +++ b/mysql-test/r/truncate_coverage.result @@ -18,13 +18,15 @@ TRUNCATE TABLE t1; SET DEBUG_SYNC='now WAIT_FOR waiting'; KILL QUERY @id; # +# connection default +ERROR 70100: Query execution was interrupted +UNLOCK TABLES; +# # connection con1 # Release shared metadata lock by closing HANDLER. HANDLER t1 CLOSE; # # connection default -ERROR 70100: Query execution was interrupted -UNLOCK TABLES; DROP TABLE t1; SET DEBUG_SYNC='RESET'; CREATE TABLE t1 (c1 INT); @@ -64,10 +66,15 @@ TRUNCATE TABLE t1; # connection con1 SET DEBUG_SYNC='now WAIT_FOR waiting'; KILL QUERY @id; -COMMIT; # # connection default ERROR 70100: Query execution was interrupted +# +# connection con1 +# Release SW lock by committing transaction. +COMMIT; +# +# connection default UNLOCK TABLES; DROP TABLE t1; SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 95d2827c1a6..e1c1d6f4128 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -3954,6 +3954,7 @@ drop procedure p; CREATE TABLE t1 (a INT); CREATE VIEW v1 AS SELECT a FROM t1; ALTER TABLE v1; +ERROR HY000: 'test.v1' is not BASE TABLE DROP VIEW v1; DROP TABLE t1; # diff --git a/mysql-test/suite/binlog/r/binlog_innodb.result b/mysql-test/suite/binlog/r/binlog_innodb.result index 5f90dd5d3dd..dc170361026 100644 --- a/mysql-test/suite/binlog/r/binlog_innodb.result +++ b/mysql-test/suite/binlog/r/binlog_innodb.result @@ -2,65 +2,65 @@ SET BINLOG_FORMAT=MIXED; RESET MASTER; CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=INNODB; INSERT INTO t1 VALUES (1,1),(2,2),(3,3),(4,4),(5,5),(6,6); -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 2*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; UPDATE t1 SET b = a * a WHERE a > 3; COMMIT; SET BINLOG_FORMAT=STATEMENT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +BEGIN; UPDATE t1 SET b = 1*a WHERE a > 1; ERROR HY000: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED. COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; UPDATE t1 SET b = 2*a WHERE a > 2; ERROR HY000: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED. COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 3*a WHERE a > 3; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +BEGIN; UPDATE t1 SET b = 4*a WHERE a > 4; COMMIT; SET BINLOG_FORMAT=MIXED; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +BEGIN; UPDATE t1 SET b = 1*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; UPDATE t1 SET b = 2*a WHERE a > 2; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 3*a WHERE a > 3; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +BEGIN; UPDATE t1 SET b = 4*a WHERE a > 4; COMMIT; SET BINLOG_FORMAT=ROW; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +BEGIN; UPDATE t1 SET b = 1*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; UPDATE t1 SET b = 2*a WHERE a > 2; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 3*a WHERE a > 3; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +BEGIN; UPDATE t1 SET b = 4*a WHERE a > 4; COMMIT; show binlog events from <binlog_start>; diff --git a/mysql-test/suite/binlog/r/binlog_truncate_innodb.result b/mysql-test/suite/binlog/r/binlog_truncate_innodb.result index ab237898a74..8beeeb1a428 100644 --- a/mysql-test/suite/binlog/r/binlog_truncate_innodb.result +++ b/mysql-test/suite/binlog/r/binlog_truncate_innodb.result @@ -1,3 +1,6 @@ +SET @old_binlog_format=@@binlog_format; +SET BINLOG_FORMAT=ROW; +RESET MASTER; CREATE TABLE t1 (a INT) ENGINE=InnoDB; CREATE TABLE t2 (a INT) ENGINE=InnoDB; INSERT INTO t2 VALUES (1),(2),(3); @@ -9,6 +12,45 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +# Even though the isolation level might be permissive, truncate +# table follows a stricter isolation as its locking is based on +# (exclusive) metadata locks. CREATE TABLE t1 (a INT) ENGINE=InnoDB; CREATE TABLE t2 (a INT) ENGINE=InnoDB; INSERT INTO t2 VALUES (1),(2),(3); @@ -22,6 +64,43 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; CREATE TABLE t1 (a INT) ENGINE=InnoDB; CREATE TABLE t2 (a INT) ENGINE=InnoDB; INSERT INTO t2 VALUES (1),(2),(3); @@ -35,6 +114,196 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1),(2),(3); +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +**** Truncate of empty table shall be logged +TRUNCATE TABLE t1; +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +TRUNCATE TABLE t2; +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 +DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1),(2),(3); +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +**** Truncate of empty table shall be logged +TRUNCATE TABLE t1; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +TRUNCATE TABLE t2; +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 +DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +SET BINLOG_FORMAT=STATEMENT; +RESET MASTER; +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1),(2),(3); +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +**** Truncate of empty table shall be logged +TRUNCATE TABLE t1; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +TRUNCATE TABLE t2; +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 +DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +# Truncate is not supported for SBR if the isolation level is +# READ UNCOMMITTED or READ COMMITTED. These specific isolation +# levels are tested elsewhere. CREATE TABLE t1 (a INT) ENGINE=InnoDB; CREATE TABLE t2 (a INT) ENGINE=InnoDB; INSERT INTO t2 VALUES (1),(2),(3); @@ -48,6 +317,41 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; CREATE TABLE t1 (a INT) ENGINE=InnoDB; CREATE TABLE t2 (a INT) ENGINE=InnoDB; INSERT INTO t2 VALUES (1),(2),(3); @@ -61,3 +365,39 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +CREATE TABLE t2 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Xid # # COMMIT /* XID */ +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +SET BINLOG_FORMAT=@old_binlog_format; diff --git a/mysql-test/suite/binlog/r/binlog_truncate_myisam.result b/mysql-test/suite/binlog/r/binlog_truncate_myisam.result index 9f01c015178..1f5b206fd6f 100644 --- a/mysql-test/suite/binlog/r/binlog_truncate_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_truncate_myisam.result @@ -1,3 +1,5 @@ +SET @old_binlog_format=@@binlog_format; +SET BINLOG_FORMAT=ROW; RESET MASTER; CREATE TABLE t1 (a INT) ENGINE=MyISAM; CREATE TABLE t2 (a INT) ENGINE=MyISAM; @@ -10,3 +12,91 @@ Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=MyISAM; +CREATE TABLE t2 (a INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES (1),(2); +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t2) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +SET BINLOG_FORMAT=STATEMENT; +RESET MASTER; +CREATE TABLE t1 (a INT) ENGINE=MyISAM; +CREATE TABLE t2 (a INT) ENGINE=MyISAM; +INSERT INTO t2 VALUES (1),(2),(3); +**** Truncate of empty table shall be logged +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t2 +DROP TABLE t1,t2; +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +CREATE TABLE t1 (a INT) ENGINE=MyISAM; +CREATE TABLE t2 (a INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES (1),(2); +# Connection: default +BEGIN; +INSERT INTO t2 SELECT * FROM t1; +# Connection: truncate +TRUNCATE TABLE t1; +# Connection: default +INSERT INTO t2 SELECT * FROM t1; +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +COMMIT; +# Connection: truncate +# Reaping TRUNCATE TABLE +SELECT COUNT(*) FROM t1; +COUNT(*) +0 +SELECT COUNT(*) FROM t2; +COUNT(*) +4 +# Connection: default +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Query # # use `test`; INSERT INTO t2 SELECT * FROM t1 +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Query # # use `test`; TRUNCATE TABLE t1 +DROP TABLE t1,t2; +SET BINLOG_FORMAT=@old_binlog_format; diff --git a/mysql-test/suite/binlog/t/binlog_innodb.test b/mysql-test/suite/binlog/t/binlog_innodb.test index 482e787a724..8191b72d5a9 100644 --- a/mysql-test/suite/binlog/t/binlog_innodb.test +++ b/mysql-test/suite/binlog/t/binlog_innodb.test @@ -8,14 +8,14 @@ RESET MASTER; CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=INNODB; INSERT INTO t1 VALUES (1,1),(2,2),(3,3),(4,4),(5,5),(6,6); -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; # Should be logged as statement UPDATE t1 SET b = 2*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; # Should be logged as rows UPDATE t1 SET b = a * a WHERE a > 3; COMMIT; @@ -25,69 +25,69 @@ COMMIT; SET BINLOG_FORMAT=STATEMENT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +BEGIN; error ER_BINLOG_STMT_MODE_AND_ROW_ENGINE; UPDATE t1 SET b = 1*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; error ER_BINLOG_STMT_MODE_AND_ROW_ENGINE; UPDATE t1 SET b = 2*a WHERE a > 2; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 3*a WHERE a > 3; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +BEGIN; UPDATE t1 SET b = 4*a WHERE a > 4; COMMIT; SET BINLOG_FORMAT=MIXED; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +BEGIN; UPDATE t1 SET b = 1*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; UPDATE t1 SET b = 2*a WHERE a > 2; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 3*a WHERE a > 3; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +BEGIN; UPDATE t1 SET b = 4*a WHERE a > 4; COMMIT; SET BINLOG_FORMAT=ROW; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +BEGIN; UPDATE t1 SET b = 1*a WHERE a > 1; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; +BEGIN; UPDATE t1 SET b = 2*a WHERE a > 2; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +BEGIN; UPDATE t1 SET b = 3*a WHERE a > 3; COMMIT; -BEGIN; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +BEGIN; UPDATE t1 SET b = 4*a WHERE a > 4; COMMIT; diff --git a/mysql-test/suite/binlog/t/binlog_truncate_innodb.test b/mysql-test/suite/binlog/t/binlog_truncate_innodb.test index be0918a43f0..56dd5bda505 100644 --- a/mysql-test/suite/binlog/t/binlog_truncate_innodb.test +++ b/mysql-test/suite/binlog/t/binlog_truncate_innodb.test @@ -1,20 +1,18 @@ source include/have_log_bin.inc; source include/have_innodb.inc; -# It is necessary to reset the master since otherwise the binlog test -# might show the wrong binary log. The default for SHOW BINLOG EVENTS -# is to show the first binary log, not the current one (which is -# actually a better idea). +let $engine = InnoDB; + +SET @old_binlog_format=@@binlog_format; +SET BINLOG_FORMAT=ROW; RESET MASTER; -let $engine = InnoDB; source extra/binlog_tests/binlog_truncate.test; -# Under transaction isolation level READ UNCOMMITTED and READ -# COMMITTED, InnoDB does not permit statement-based replication of -# row-deleting statement. In these cases, TRUNCATE TABLE should still -# be replicated as a statement. +--echo # Even though the isolation level might be permissive, truncate +--echo # table follows a stricter isolation as its locking is based on +--echo # (exclusive) metadata locks. let $before_truncate = SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; source extra/binlog_tests/binlog_truncate.test; @@ -27,3 +25,20 @@ source extra/binlog_tests/binlog_truncate.test; let $before_truncate = SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; source extra/binlog_tests/binlog_truncate.test; + +SET BINLOG_FORMAT=STATEMENT; +RESET MASTER; + +source extra/binlog_tests/binlog_truncate.test; + +--echo # Truncate is not supported for SBR if the isolation level is +--echo # READ UNCOMMITTED or READ COMMITTED. These specific isolation +--echo # levels are tested elsewhere. + +let $before_truncate = SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +source extra/binlog_tests/binlog_truncate.test; + +let $before_truncate = SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +source extra/binlog_tests/binlog_truncate.test; + +SET BINLOG_FORMAT=@old_binlog_format; diff --git a/mysql-test/suite/binlog/t/binlog_truncate_myisam.test b/mysql-test/suite/binlog/t/binlog_truncate_myisam.test index e0e4673e876..fc28bd7c7e2 100644 --- a/mysql-test/suite/binlog/t/binlog_truncate_myisam.test +++ b/mysql-test/suite/binlog/t/binlog_truncate_myisam.test @@ -1,11 +1,17 @@ source include/have_log_bin.inc; -# It is necessary to reset the master since otherwise the binlog test -# might show the wrong binary log. The default for SHOW BINLOG EVENTS -# is to show the first binary log, not the current one (which is -# actually a better idea). +SET @old_binlog_format=@@binlog_format; +let $engine = MyISAM; + +SET BINLOG_FORMAT=ROW; +RESET MASTER; + +source extra/binlog_tests/binlog_truncate.test; + +SET BINLOG_FORMAT=STATEMENT; RESET MASTER; -let $engine = MyISAM; source extra/binlog_tests/binlog_truncate.test; + +SET BINLOG_FORMAT=@old_binlog_format; diff --git a/mysql-test/suite/binlog/t/disabled.def b/mysql-test/suite/binlog/t/disabled.def index b6086edb2f0..35ecf52decf 100644 --- a/mysql-test/suite/binlog/t/disabled.def +++ b/mysql-test/suite/binlog/t/disabled.def @@ -9,6 +9,7 @@ # Do not use any TAB characters for whitespace. # ############################################################################## + binlog_truncate_innodb : BUG#42643 2009-02-06 mats Changes to InnoDB requires to complete fix for BUG#36763 binlog_unsafe : BUG#50312 2010-01-13 lsoares Warnings for unsafe sub-statement not returned to client binlog_spurious_ddl_errors : BUG#54195 2010-06-03 alik binlog_spurious_ddl_errors.test fails, thus disabled diff --git a/mysql-test/suite/funcs_1/r/innodb_views.result b/mysql-test/suite/funcs_1/r/innodb_views.result index b4bce0b0d1b..86a61773a31 100644 --- a/mysql-test/suite/funcs_1/r/innodb_views.result +++ b/mysql-test/suite/funcs_1/r/innodb_views.result @@ -3521,6 +3521,7 @@ RENAME TABLE v1 TO v2; RENAME VIEW v2 TO v1; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'VIEW v2 TO v1' at line 1 ALTER TABLE v2 RENAME AS v1; +ERROR HY000: 'test.v2' is not BASE TABLE ALTER VIEW v1 RENAME AS v2; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RENAME AS v2' at line 1 DROP TABLE IF EXISTS t1, t2 ; diff --git a/mysql-test/suite/funcs_1/r/memory_views.result b/mysql-test/suite/funcs_1/r/memory_views.result index 2f7342088f8..7ed23c3a5c1 100644 --- a/mysql-test/suite/funcs_1/r/memory_views.result +++ b/mysql-test/suite/funcs_1/r/memory_views.result @@ -3522,6 +3522,7 @@ RENAME TABLE v1 TO v2; RENAME VIEW v2 TO v1; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'VIEW v2 TO v1' at line 1 ALTER TABLE v2 RENAME AS v1; +ERROR HY000: 'test.v2' is not BASE TABLE ALTER VIEW v1 RENAME AS v2; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RENAME AS v2' at line 1 DROP TABLE IF EXISTS t1, t2 ; diff --git a/mysql-test/suite/funcs_1/r/myisam_views.result b/mysql-test/suite/funcs_1/r/myisam_views.result index b77ea8a4bd9..ca4dba9f337 100644 --- a/mysql-test/suite/funcs_1/r/myisam_views.result +++ b/mysql-test/suite/funcs_1/r/myisam_views.result @@ -4024,6 +4024,7 @@ RENAME TABLE v1 TO v2; RENAME VIEW v2 TO v1; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'VIEW v2 TO v1' at line 1 ALTER TABLE v2 RENAME AS v1; +ERROR HY000: 'test.v2' is not BASE TABLE ALTER VIEW v1 RENAME AS v2; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RENAME AS v2' at line 1 DROP TABLE IF EXISTS t1, t2 ; diff --git a/mysql-test/suite/funcs_1/r/ndb_views.result b/mysql-test/suite/funcs_1/r/ndb_views.result index 10cb7613f47..6c5a10845a0 100644 --- a/mysql-test/suite/funcs_1/r/ndb_views.result +++ b/mysql-test/suite/funcs_1/r/ndb_views.result @@ -3521,6 +3521,7 @@ RENAME TABLE v1 TO v2; RENAME VIEW v2 TO v1; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'VIEW v2 TO v1' at line 1 ALTER TABLE v2 RENAME AS v1; +ERROR HY000: 'test.v2' is not BASE TABLE ALTER VIEW v1 RENAME AS v2; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RENAME AS v2' at line 1 DROP TABLE IF EXISTS t1, t2 ; diff --git a/mysql-test/suite/funcs_1/views/views_master.inc b/mysql-test/suite/funcs_1/views/views_master.inc index 4f8439efc3a..906f1f03b3b 100644 --- a/mysql-test/suite/funcs_1/views/views_master.inc +++ b/mysql-test/suite/funcs_1/views/views_master.inc @@ -298,7 +298,7 @@ RENAME TABLE v1 TO v2; # RENAME VIEW is not available even when we try it via rename table. --error ER_PARSE_ERROR RENAME VIEW v2 TO v1; -#--error ER_WRONG_OBJECT +--error ER_WRONG_OBJECT ALTER TABLE v2 RENAME AS v1; --error ER_PARSE_ERROR ALTER VIEW v1 RENAME AS v2; diff --git a/mysql-test/suite/innodb/r/innodb-semi-consistent.result b/mysql-test/suite/innodb/r/innodb-semi-consistent.result index ca0e362ef80..989cb818cec 100644 --- a/mysql-test/suite/innodb/r/innodb-semi-consistent.result +++ b/mysql-test/suite/innodb/r/innodb-semi-consistent.result @@ -13,6 +13,7 @@ set autocommit=0; update t1 set a=10 where a=5; ERROR HY000: Lock wait timeout exceeded; try restarting transaction commit; +commit; set session transaction isolation level read committed; update t1 set a=10 where a=5; select * from t1 where a=2 for update; diff --git a/mysql-test/suite/innodb/r/innodb_bug38231.result b/mysql-test/suite/innodb/r/innodb_bug38231.result index 41a40542b84..195775f74c8 100644 --- a/mysql-test/suite/innodb/r/innodb_bug38231.result +++ b/mysql-test/suite/innodb/r/innodb_bug38231.result @@ -1,11 +1 @@ SET storage_engine=InnoDB; -INSERT INTO bug38231_2 VALUES (1), (10), (300); -SET autocommit=0; -SELECT * FROM bug38231_2 FOR UPDATE; -a -1 -10 -300 -TRUNCATE TABLE bug38231_2; -COMMIT; -DROP TABLE bug38231_2; diff --git a/mysql-test/suite/innodb/r/innodb_information_schema.result b/mysql-test/suite/innodb/r/innodb_information_schema.result index ad8729804df..1737dab2ff0 100644 --- a/mysql-test/suite/innodb/r/innodb_information_schema.result +++ b/mysql-test/suite/innodb/r/innodb_information_schema.result @@ -63,4 +63,4 @@ RUNNING 4 0 0 7 1 0 REPEATABLE READ 1 1 trx_isolation_level trx_unique_checks trx_foreign_key_checks SERIALIZABLE 0 0 trx_state trx_isolation_level trx_last_foreign_key_error -RUNNING SERIALIZABLE `test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`c02`) REFERENCES `t1` (`c01`) +RUNNING REPEATABLE READ `test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`c02`) REFERENCES `t1` (`c01`) diff --git a/mysql-test/suite/innodb/r/innodb_mysql.result b/mysql-test/suite/innodb/r/innodb_mysql.result index 124370fa630..6d92d14e735 100644 --- a/mysql-test/suite/innodb/r/innodb_mysql.result +++ b/mysql-test/suite/innodb/r/innodb_mysql.result @@ -1590,6 +1590,9 @@ SELECT * FROM t1; a b 1 12 ROLLBACK; +# Switch to connection con2 +ROLLBACK; +# Switch to connection con1 # 2. test for serialized update: CREATE TABLE t2 (a INT); TRUNCATE t1; @@ -1764,6 +1767,37 @@ id select_type table type possible_keys key key_len ref rows Extra 2 DERIVED t1 index c3,c2 c2 14 NULL 5 DROP TABLE t1; End of 5.1 tests +# +# Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +# +# Check that a TRUNCATE TABLE statement, needing an exclusive meta +# data lock, waits for a shared metadata lock owned by a concurrent +# transaction. +# +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); +BEGIN; +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +# Connection con1 +TRUNCATE TABLE t1;; +# Connection default +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +ROLLBACK; +# Connection con1 +# Reaping TRUNCATE TABLE +SELECT * FROM t1; +a +# Disconnect con1 +# Connection default +DROP TABLE t1; drop table if exists t1, t2, t3; create table t1(a int); insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); diff --git a/mysql-test/suite/innodb/t/innodb-semi-consistent.test b/mysql-test/suite/innodb/t/innodb-semi-consistent.test index 61ad7815ca9..28bf532ff1f 100644 --- a/mysql-test/suite/innodb/t/innodb-semi-consistent.test +++ b/mysql-test/suite/innodb/t/innodb-semi-consistent.test @@ -23,6 +23,7 @@ set session transaction isolation level repeatable read; set autocommit=0; -- error ER_LOCK_WAIT_TIMEOUT update t1 set a=10 where a=5; +commit; connection a; commit; connection b; diff --git a/mysql-test/suite/innodb/t/innodb_bug38231.test b/mysql-test/suite/innodb/t/innodb_bug38231.test index a0a10bbd100..0d4262b4473 100644 --- a/mysql-test/suite/innodb/t/innodb_bug38231.test +++ b/mysql-test/suite/innodb/t/innodb_bug38231.test @@ -71,30 +71,7 @@ UNLOCK TABLES; DROP TABLE bug38231_1; -# test that TRUNCATE works with row-level locks - -DROP TABLE IF EXISTS bug38231_2; -CREATE TABLE bug38231_2 (a INT); - -- enable_query_log -- enable_result_log -INSERT INTO bug38231_2 VALUES (1), (10), (300); - --- connect (con4,localhost,root,,) - --- connection con4 -SET autocommit=0; -SELECT * FROM bug38231_2 FOR UPDATE; - -- connection default -TRUNCATE TABLE bug38231_2; - --- connection con4 -COMMIT; - --- connection default - --- disconnect con4 - -DROP TABLE bug38231_2; diff --git a/mysql-test/suite/perfschema/r/dml_setup_instruments.result b/mysql-test/suite/perfschema/r/dml_setup_instruments.result index 73dc1178a54..55256894743 100644 --- a/mysql-test/suite/perfschema/r/dml_setup_instruments.result +++ b/mysql-test/suite/perfschema/r/dml_setup_instruments.result @@ -25,7 +25,7 @@ wait/synch/rwlock/sql/LOCK_system_variables_hash YES YES wait/synch/rwlock/sql/LOCK_sys_init_connect YES YES wait/synch/rwlock/sql/LOCK_sys_init_slave YES YES wait/synch/rwlock/sql/LOGGER::LOCK_logger YES YES -wait/synch/rwlock/sql/MDL_context::waiting_for_lock YES YES +wait/synch/rwlock/sql/MDL_context::LOCK_waiting_for YES YES wait/synch/rwlock/sql/MDL_lock::rwlock YES YES wait/synch/rwlock/sql/Query_cache_query::lock YES YES wait/synch/rwlock/sql/THR_LOCK_servers YES YES diff --git a/mysql-test/suite/rpl/r/rpl_sp.result b/mysql-test/suite/rpl/r/rpl_sp.result index 0af76e93ab4..42f7a9c7ed8 100644 --- a/mysql-test/suite/rpl/r/rpl_sp.result +++ b/mysql-test/suite/rpl/r/rpl_sp.result @@ -1215,18 +1215,23 @@ lock table t2 write; # Sending 'insert into t1 (a) values (f1())'... insert into t1 (a) values (f1()); # Waitng for 'insert into t1 ...' to get blocked on table lock... -# Sending 'drop function f1'. It will abort the table lock wait. -drop function f1; +# Sending 'drop function f1'. It will wait till insert finishes. +drop function f1;; # --> connection default +# Check that 'drop function f1' gets blocked. # Now let's let 'insert' go through... unlock tables; -# --> connection con1 +# --> connection master # Reaping 'insert into t1 (a) values (f1())'... -ERROR 42000: FUNCTION test.f1 does not exist +# --> connection master1 +# Reaping 'drop function f1' +# --> connection master select * from t1; a +1 select * from t1; a +1 drop table t1, t2; drop function f1; ERROR 42000: FUNCTION test.f1 does not exist diff --git a/mysql-test/suite/rpl/t/rpl_sp.test b/mysql-test/suite/rpl/t/rpl_sp.test index 8ca4ff51eda..8510f387111 100644 --- a/mysql-test/suite/rpl/t/rpl_sp.test +++ b/mysql-test/suite/rpl/t/rpl_sp.test @@ -657,17 +657,25 @@ connection master1; let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='insert into t1 (a) values (f1())'; --source include/wait_condition.inc ---echo # Sending 'drop function f1'. It will abort the table lock wait. -drop function f1; +--echo # Sending 'drop function f1'. It will wait till insert finishes. +--send drop function f1; --echo # --> connection default connection default; +--echo # Check that 'drop function f1' gets blocked. +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc --echo # Now let's let 'insert' go through... unlock tables; ---echo # --> connection con1 +--echo # --> connection master connection master; --echo # Reaping 'insert into t1 (a) values (f1())'... ---error ER_SP_DOES_NOT_EXIST --reap +--echo # --> connection master1 +connection master1; +--echo # Reaping 'drop function f1' +--reap +--echo # --> connection master connection master; select * from t1; sync_slave_with_master; diff --git a/mysql-test/t/archive.test b/mysql-test/t/archive.test index 5b61176b0f6..a3665e5f455 100644 --- a/mysql-test/t/archive.test +++ b/mysql-test/t/archive.test @@ -1670,6 +1670,15 @@ SELECT * FROM t1; --error ER_BAD_TABLE_ERROR DROP TABLE t1; +--echo # +--echo # Ensure that TRUNCATE fails for non-empty archive tables. +--echo # +CREATE TABLE t1 (a INT) ENGINE=ARCHIVE; +INSERT INTO t1 VALUES (1); +--error ER_ILLEGAL_HA +TRUNCATE TABLE t1; +DROP TABLE t1; + --echo # --echo # BUG#46565 - repair of partition fail for archive engine diff --git a/mysql-test/t/commit.test b/mysql-test/t/commit.test new file mode 100644 index 00000000000..261867bd5cc --- /dev/null +++ b/mysql-test/t/commit.test @@ -0,0 +1,350 @@ +--source include/have_innodb.inc + +connect (con1,localhost,root,,); + +--echo # +--echo # Bug#20837 Apparent change of isolation level +--echo # during transaction +--echo # +--echo # Bug#53343 completion_type=1, COMMIT/ROLLBACK +--echo # AND CHAIN don't preserve the isolation +--echo # level +# +# A set of test cases that verifies operation of +# transaction isolation level and chaining is +# provided + +# init +--echo connection default; +connection default; + +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +CREATE TABLE t1 (s1 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2); +COMMIT; + +# +# Verify that SET TRANS ISO LEVEL is not allowed +# inside a transaction +# +START TRANSACTION; +--error ER_CANT_CHANGE_TX_ISOLATION +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +COMMIT; + +# +# Verify consistent output from +# SELECT @@tx_isolation (Bug#20837) +# +# The transaction will be in READ UNCOMMITTED mode, +# but SELECT @@tx_isolation should report the session +# value, which is REPEATABLE READ +# +SET @@autocommit=0; +COMMIT; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +START TRANSACTION; +SELECT @@tx_isolation; +--echo Should be REPEATABLE READ +SELECT * FROM t1; +SELECT @@tx_isolation; +--echo Should be REPEATABLE READ +INSERT INTO t1 VALUES (-1); +SELECT @@tx_isolation; +--echo Should be REPEATABLE READ +COMMIT; + +# +# Verify that a change in the session variable +# does not affect the currently started +# transaction +# +START TRANSACTION; +SELECT * FROM t1; +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; + +--echo connection con1 +connection con1; +START TRANSACTION; +INSERT INTO t1 VALUES (1000); +COMMIT; + +--echo connection default +connection default; +--echo We should not be able to read the '1000' +SELECT * FROM t1; +COMMIT; + +--echo Now, the '1000' should appear. +START TRANSACTION; +SELECT * FROM t1; +COMMIT; + +# restore the session value +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; + +# +# A set of test cases for verification that +# isolation level during chaining works. MySQL +# has three variants of chaining, i.e +# COMMIT AND CHAIN, ROLLBACK AND CHAIN, and +# the use of @completion_type +# + +# +# Verify isolation level with COMMIT AND CHAIN +# +# COMMIT AND CHAIN causes a new transaction to +# begin as soon as the current ends, and the new +# transaction will have the same tran. iso. level +# as the first. +# +--echo connection default +connection default; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; + +--echo connection con1 +connection con1; +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; + +--echo connection default +connection default; +SELECT COUNT(*) FROM t1 WHERE s1 = 1001; +--echo Should be 1 +COMMIT AND CHAIN; + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1002); +COMMIT; + +--echo connection default +connection default; +SELECT COUNT(*) FROM t1 WHERE s1 = 1002; +--echo Should be 1 +COMMIT; +SELECT * FROM t1; +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT; + +# +# Verify isolation level with ROLLBACK AND CHAIN +# +--echo connection default +connection default; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; + +--echo connection con1 +connection con1; +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; + +--echo connection default +connection default; +SELECT COUNT(*) FROM t1 WHERE s1 = 1001; +--echo Should be 1 +ROLLBACK AND CHAIN; + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1002); +COMMIT; + +--echo connection default +connection default; +SELECT COUNT(*) FROM t1 WHERE s1 = 1002; +--echo Should be 1 +COMMIT; +SELECT * FROM t1; +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT; + +# +# Verify isolation level with @completion_type=1. +# (A @@completion_type value of 1 is equivalentl to +# explicitly adding "AND CHAIN" to COMMIT or ROLLBACK) +# + +# +# Verify that COMMIT AND NO CHAIN overrides the value +# of @@completion_type +# +SET @@completion_type=1; + +--echo connection default +connection default; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; + +--echo connection con1 +connection con1; +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1 WHERE s1 >= 1000; +--echo Should see 1001 +COMMIT AND NO CHAIN; +--echo default transaction is now in REPEATABLE READ + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1002); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1 WHERE s1 >= 1000; +--echo Should see 1001 and 1002 + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1003); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1 WHERE s1 >= 1000; +--echo Should see 1001 and 1002, but NOT 1003 +COMMIT; + +SELECT * FROM t1; +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT AND NO CHAIN; +SET @@completion_type=0; +COMMIT; + +# +# Verify that ROLLBACK AND NO CHAIN overrides the value +# of @@completion_type +# +--echo connection default +connection default; +SET @@completion_type=1; +COMMIT AND NO CHAIN; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; + +--echo connection con1 +connection con1; +START TRANSACTION; +INSERT INTO t1 VALUES (1001); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1 WHERE s1 >= 1000; +--echo Should see 1001 +ROLLBACK AND NO CHAIN; +--echo default transaction is now in REPEATABLE READ + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1002); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1 WHERE s1 >= 1000; +--echo Should see 1001 and 1002 + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1003); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1 WHERE s1 >= 1000; +--echo Should see 1001 and 1002, but NOT 1003 + +COMMIT; +SELECT * FROM t1; +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT AND NO CHAIN; +SET @@completion_type=0; +COMMIT; + +# +# Verify that in the sequence: +# SET TRANSACTION ISOLATION LEVEL +# SET SESSION ISOLATION LEVEL +# +# SET SESSION ISOLATION LEVEL has precedence over +# SET TRANSACTION. (Note that this is _not_ +# in accordance with ISO 9075.) +# +--echo connection default +connection default; + +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; +START TRANSACTION; +SELECT * FROM t1; + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1000); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1; +--echo Should get same result as above (i.e should not read '1000') +COMMIT; + +DELETE FROM t1 WHERE s1 >= 1000; +COMMIT; + + +# +# Verify that a transaction ended with an +# implicit commit (i.e a DDL statement), the +# @@completetion_type setting is ignored, and +# the next transaction's isolation level is +# the session level. +# +SET @@completion_type=1; +COMMIT AND NO CHAIN; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION; +TRUNCATE TABLE t1; +INSERT INTO t1 VALUES (1000); +SELECT * FROM t1; +--echo Should read '1000' + +--echo connection con1 +connection con1; +INSERT INTO t1 VALUES (1001); +COMMIT; + +--echo connection default +connection default; +SELECT * FROM t1; +--echo Should only read the '1000' as this transaction is now in REP READ +COMMIT AND NO CHAIN; + +SET @@completion_type=0; +COMMIT AND NO CHAIN; + + +# +# Cleanup +# +SET @autocommit=1; +COMMIT; + +disconnect con1; + +DROP TABLE t1; + +--echo # +--echo # End of test cases for Bug#20837 +--echo # diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test index 383ba98ae6d..887ae4da404 100644 --- a/mysql-test/t/create.test +++ b/mysql-test/t/create.test @@ -102,6 +102,22 @@ create table t1 (`` int); create table t1 (i int, index `` (i)); # +# CREATE TABLE under LOCK TABLES +# +# We don't allow creation of non-temporary tables under LOCK TABLES +# as following meta-data locking protocol in this case can lead to +# deadlock. +create table t1 (i int); +lock tables t1 read; +--error ER_TABLE_NOT_LOCKED +create table t2 (j int); +# OTOH creating of temporary table should be OK +create temporary table t2 (j int); +drop temporary table t2; +unlock tables; +drop table t1; + +# # Test of CREATE ... SELECT with indexes # @@ -315,6 +331,26 @@ drop table t3; drop database mysqltest; # +# CREATE TABLE LIKE under LOCK TABLES +# +# Similarly to ordinary CREATE TABLE we don't allow creation of +# non-temporary tables under LOCK TABLES. Also we require source +# table to be locked. +create table t1 (i int); +create table t2 (j int); +lock tables t1 read; +--error ER_TABLE_NOT_LOCKED +create table t3 like t1; +# OTOH creating of temporary table should be OK +create temporary table t3 like t1; +drop temporary table t3; +# Source table should be locked +--error ER_TABLE_NOT_LOCKED +create temporary table t3 like t2; +unlock tables; +drop tables t1, t2; + +# # Test default table type # SET SESSION storage_engine="heap"; @@ -1731,3 +1767,34 @@ DROP TABLE t1; DROP TEMPORARY TABLE t2; + +--echo # +--echo # Bug #22909 "Using CREATE ... LIKE is possible to create field +--echo # with invalid default value" +--echo # +--echo # Altough original bug report suggests to use older version of MySQL +--echo # for producing .FRM with invalid defaults we use sql_mode to achieve +--echo # the same effect. +--disable_warnings +drop tables if exists t1, t2; +--enable_warnings +--echo # Attempt to create table with invalid default should fail in normal mode +--error ER_INVALID_DEFAULT +create table t1 (dt datetime default '2008-02-31 00:00:00'); +set @old_mode= @@sql_mode; +set @@sql_mode='ALLOW_INVALID_DATES'; +--echo # The same should be possible in relaxed mode +create table t1 (dt datetime default '2008-02-31 00:00:00'); +set @@sql_mode= @old_mode; +--echo # In normal mode attempt to create copy of table with invalid +--echo # default should fail +--error ER_INVALID_DEFAULT +create table t2 like t1; +set @@sql_mode='ALLOW_INVALID_DATES'; +--echo # But should work in relaxed mode +create table t2 like t1; +--echo # Check that table definitions match +show create table t1; +show create table t2; +set @@sql_mode= @old_mode; +drop tables t1, t2; diff --git a/mysql-test/t/drop.test b/mysql-test/t/drop.test index 5ef4a28b202..b5ca0817b6f 100644 --- a/mysql-test/t/drop.test +++ b/mysql-test/t/drop.test @@ -256,3 +256,20 @@ SHOW WARNINGS; --echo # -- --echo # -- End of Bug#37431. --echo # -- + + +--echo # +--echo # Bug#54282 Crash in MDL_context::upgrade_shared_lock_to_exclusive +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT); +LOCK TABLE t1 WRITE; +--error ER_NONUNIQ_TABLE +DROP TABLE t1, t1; + +UNLOCK TABLES; +DROP TABLE t1; diff --git a/mysql-test/t/innodb_mysql_lock2.test b/mysql-test/t/innodb_mysql_lock2.test index 5111d56225a..048d712183f 100644 --- a/mysql-test/t/innodb_mysql_lock2.test +++ b/mysql-test/t/innodb_mysql_lock2.test @@ -3,7 +3,12 @@ # This test requires statement/mixed mode binary logging. # Row-based mode puts weaker serializability requirements # so weaker locks are acquired for it. +# Also in ROW mode LOCK_S row locks won't be acquired for DML +# and test for bug#51263 won't trigger execution path on which +# this bug was encountered. --source include/have_binlog_format_mixed_or_statement.inc +# Original test case for bug#51263 needs partitioning. +--source include/have_partition.inc # Save the initial number of concurrent sessions. --source include/count_sessions.inc @@ -199,8 +204,7 @@ let $table= t1; --echo # 1.1 Simple SELECT statement. --echo # --echo # No locks are necessary as this statement won't be written ---echo # to the binary log and thanks to how MyISAM works SELECT ---echo # will see version of the table prior to concurrent insert. +--echo # to the binary log and InnoDB supports snapshots. let $statement= select * from t1; --source include/check_no_row_lock.inc @@ -654,7 +658,7 @@ let $statement= call p2(@a); --source include/check_no_row_lock.inc --echo # ---echo # 5.2 Function that modifes data and uses CALL, +--echo # 5.2 Function that modifies data and uses CALL, --echo # which reads a table through SELECT. --echo # --echo # Since a call to such function is written to the binary @@ -760,6 +764,104 @@ drop procedure p2; drop table t1, t2, t3, t4, t5; disconnect con1; + +--echo # +--echo # Test for bug#51263 "Deadlock between transactional SELECT +--echo # and ALTER TABLE ... REBUILD PARTITION". +--echo # +connect (con1,localhost,root,,test,,); +connection default; +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (i int auto_increment not null primary key) engine=innodb; +create table t2 (i int) engine=innodb; +insert into t1 values (1), (2), (3), (4), (5); + +begin; +--echo # Acquire SR metadata lock on t1 and LOCK_S row-locks on its rows. +insert into t2 select count(*) from t1; + +--echo # Switching to connection 'con1'. +connection con1; +--echo # Sending: +--send alter table t1 add column j int + +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until ALTER is blocked because it tries to upgrade SNW +--echo # metadata lock to X lock. +--echo # It should not be blocked during copying data to new version of +--echo # table as it acquires LOCK_S locks on rows of old version, which +--echo # are compatible with locks acquired by connection 'con1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist where state = + "Waiting for table" and info = "alter table t1 add column j int"; +--source include/wait_condition.inc + +--echo # The below statement will deadlock because it will try to acquire +--echo # SW lock on t1, which will conflict with ALTER's SNW lock. And +--echo # ALTER will be waiting for this connection to release its SR lock. +--echo # This deadlock should be detected by an MDL subsystem and this +--echo # statement should be aborted with an appropriate error. +--error ER_LOCK_DEADLOCK +insert into t1 values (6); +--echo # Unblock ALTER TABLE. +commit; + +--echo # Switching to connection 'con1'. +connection con1; +--echo # Reaping ALTER TABLE. +--reap + +--echo # Switching to connection 'default'. +connection default; + +--echo # +--echo # Now test for scenario in which bug was reported originally. +--echo # +drop tables t1, t2; +create table t1 (i int auto_increment not null primary key) engine=innodb + partition by hash (i) partitions 4; +create table t2 (i int) engine=innodb; +insert into t1 values (1), (2), (3), (4), (5); + +begin; +--echo # Acquire SR metadata lock on t1. +select * from t1; + +--echo # Switching to connection 'con1'. +connection con1; +--echo # Sending: +--send alter table t1 rebuild partition p0 + +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until ALTER is blocked because of active SR lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 rebuild partition p0"; +--source include/wait_condition.inc + +--echo # The below statement should succeed as transaction +--echo # has SR metadata lock on t1 and only going to read +--echo # rows from it. +insert into t2 select count(*) from t1; +--echo # Unblock ALTER TABLE. +commit; + +--echo # Switching to connection 'con1'. +connection con1; +--echo # Reaping ALTER TABLE. +--reap + +--echo # Switching to connection 'default'. +connection default; +disconnect con1; +--echo # Clean-up. +drop tables t1, t2; + + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/lock_sync.test b/mysql-test/t/lock_sync.test index 921ce991652..2f35da9d1ee 100644 --- a/mysql-test/t/lock_sync.test +++ b/mysql-test/t/lock_sync.test @@ -716,7 +716,7 @@ let $restore_table= ; --source include/check_concurrent_insert.inc --echo # ---echo # 5.2 Function that modifes data and uses CALL, +--echo # 5.2 Function that modifies data and uses CALL, --echo # which reads a table through SELECT. --echo # --echo # Since a call to such function is written to the binary diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index 0b4b9af5bc6..19f9b396087 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -462,7 +462,11 @@ handler t1 open; handler t1 close; select column_name from information_schema.columns where table_schema='test' and table_name='t1'; -select count(*) from t1; +--echo # Disable result log to make test robust against +--echo # effects of concurrent insert. +--disable_result_log +select * from t1; +--enable_result_log insert into t1 values (1); --echo # Check that SNW lock is not compatible with SW lock. --echo # Again we use ALTER TABLE which fails after opening @@ -2407,6 +2411,7 @@ drop tables t1, t2; --disable_warnings drop tables if exists t0, t1, t2, t3, t4, t5; --enable_warnings +set debug_sync= 'RESET'; connect(deadlock_con1,localhost,root,,); connect(deadlock_con2,localhost,root,,); @@ -2696,6 +2701,136 @@ connection default; drop table t2; +--echo # +--echo # Test that in situation when MDL subsystem detects a deadlock +--echo # but it turns out that it can be resolved by backing-off locks +--echo # acquired by one of participating transactions (which is +--echo # possible when one of transactions consists only of currently +--echo # executed statement, e.g. in autocommit mode) no error is +--echo # reported. +--echo # +create table t1 (i int); +create table t2 (j int); +--echo # Ensure that the below SELECT stops once it has acquired metadata +--echo # lock on table 't2'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send select * from t2, t1 + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait till SELECT acquires MDL on 't2' and starts waiting for signal. +set debug_sync= 'now WAIT_FOR locked'; +--echo # Sending: +--send lock tables t1 write, t2 write + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Wait until LOCK TABLES acquires SNRW lock on 't1' and is blocked +--echo # while trying to acquire SNRW lock on 't1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock tables t1 write, t2 write"; +--source include/wait_condition.inc +--echo # Resume SELECT execution, this should eventually unblock LOCK TABLES. +set debug_sync= 'now SIGNAL finish'; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Reaping LOCK TABLES. +--reap +unlock tables; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT. It succeed and not report ER_LOCK_DEADLOCK error. +--reap + +drop tables t1, t2; + +--echo # +--echo # Test coverage for situation in which a race has happened +--echo # during deadlock detection process which led to unwarranted +--echo # ER_LOCK_DEADLOCK error. +--echo # +create table t1 (i int); + +--echo # Ensure that ALTER waits once it has acquired SNW lock. +set debug_sync='after_open_table_mdl_shared SIGNAL parked1 WAIT_FOR go1'; +--echo # Sending: +--send alter table t1 add column j int + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait till ALTER acquires SNW lock and stops. +set debug_sync='now WAIT_FOR parked1'; +--echo # Ensure that INSERT is paused once it detects that there is +--echo # a conflicting metadata lock so it has to wait, but before +--echo # deadlock detection is run. +set debug_sync='mdl_acquire_lock_wait SIGNAL parked2 WAIT_FOR go2'; +--echo # Sending: +--send insert into t1 values () + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Wait till INSERT is paused. +set debug_sync='now WAIT_FOR parked2'; +--echo # Resume ALTER execution. Eventually it will release its +--echo # metadata lock and INSERT's request for SW lock will be +--echo # satisified. +set debug_sync='now SIGNAL go1'; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping ALTER TABLE. +--reap +--echo # Add a new request for SNW lock to waiting graph. +--echo # Sending: +--send alter table t1 drop column j + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Wait until ALTER is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 drop column j"; +--source include/wait_condition.inc +--echo # Resume INSERT so it can start deadlock detection. +--echo # +--echo # At this point there is a discrepancy between the fact that INSERT's +--echo # SW lock is already satisfied, but INSERT's connection is still +--echo # marked as waiting for it. Looking for a loop in waiters graph +--echo # without additional checks has detected a deadlock (INSERT waits +--echo # for SW lock; which is not granted because of pending SNW lock from +--echo # ALTER; which waits for active SW lock from INSERT). Since requests +--echo # for SW and SNW locks have same weight ALTER was selected as a victim +--echo # and ended with ER_LOCK_DEADLOCK error. +set debug_sync='now SIGNAL go2'; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Reaping INSERT. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping ALTER. It should succeed and not produce ER_LOCK_DEADLOCK. +--reap + +drop table t1; + +set debug_sync= 'RESET'; + disconnect deadlock_con1; disconnect deadlock_con2; disconnect deadlock_con3; @@ -3093,7 +3228,7 @@ set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR --echo # Switching to connection 'default'. connection default; set debug_sync='now WAIT_FOR alter_table_locked'; -set debug_sync='before_open_table_wait_refresh SIGNAL alter_go'; +set debug_sync='mdl_acquire_lock_wait SIGNAL alter_go'; --echo # The below statement should get ER_LOCK_DEADLOCK error --echo # (i.e. it should not allow ALTER to proceed, and then --echo # fail due to 't1' changing its name to 't2'). @@ -3469,6 +3604,86 @@ set debug_sync= 'RESET'; drop table t1; --echo # +--echo # Bug#42643: InnoDB does not support replication of TRUNCATE TABLE +--echo # +--echo # Ensure that a acquired lock is not given up due to a conflict. +--echo # + +connect (con1,localhost,root,,test,,); +connect (con2,localhost,root,,test,,); +connect (con3,localhost,root,,test,,); + +connection default; + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); + +--echo # Connection: con1 +connection con1; +SET debug_sync='lock_table_for_truncate SIGNAL parked_truncate WAIT_FOR go_truncate'; +send TRUNCATE TABLE t1; + +connection default; +--echo # Connection: default +SET debug_sync='now WAIT_FOR parked_truncate'; + +connection con2; +--echo # Connection: con2 +SET debug_sync='after_open_table_ignore_flush SIGNAL parked_show WAIT_FOR go_show'; +send SHOW FIELDS FROM t1; + +connection default; +--echo # Connection: default +SET debug_sync='now WAIT_FOR parked_show'; + +connection con3; +--echo # Connection: con3 +SET debug_sync='after_flush_unlock SIGNAL parked_flush WAIT_FOR go_flush'; +send FLUSH TABLES t1; + +connection default; +--echo # Connection: default +SET debug_sync='now WAIT_FOR parked_flush'; +SET debug_sync='now SIGNAL go_truncate'; + +connection con1; +--echo # Connection: con1 +--echo # Reaping... +reap; + +connection default; +--echo # Connection: default +SET debug_sync= 'now SIGNAL go_show'; + +connection con2; +--echo # Connection: con2 (SHOW FIELDS FROM t1) +--echo # Reaping... +reap; + +connection default; +--echo # Connection: default +SET debug_sync= 'now SIGNAL go_flush'; + +connection con3; +--echo # Connection: con3 (FLUSH TABLES t1) +--echo # Reaping... +reap; + +disconnect con1; +disconnect con2; +disconnect con3; + +connection default; +--echo # Connection: default +SET debug_sync= 'RESET'; +DROP TABLE t1; + + +--echo # --echo # Bug#52856 concurrent show columns or show full columns causes a crash!!! --echo # CREATE TABLE t1(a CHAR(255)); diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index a9d98da0403..29c0eae1df6 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -2187,5 +2187,20 @@ UNLOCK TABLES; DROP TABLE m1, t1; +--echo # +--echo # Test for bug #37371 "CREATE TABLE LIKE merge loses UNION parameter" +--echo # +--disable_warnings +drop tables if exists t1, m1, m2; +--enable_warnings +create table t1 (i int) engine=myisam; +create table m1 (i int) engine=mrg_myisam union=(t1) insert_method=first; +create table m2 like m1; +--echo # Table definitions should match +show create table m1; +show create table m2; +drop tables m1, m2, t1; + + --echo End of 6.0 tests diff --git a/mysql-test/t/parser.test b/mysql-test/t/parser.test index e6c9c8b423f..d477843b22b 100644 --- a/mysql-test/t/parser.test +++ b/mysql-test/t/parser.test @@ -732,3 +732,4 @@ DROP TABLE t1, t2, t3; --echo # --echo # End of 5.1 tests --echo # + diff --git a/mysql-test/t/parser_not_embedded.test b/mysql-test/t/parser_not_embedded.test index 8ebeb9a8301..22e9ae5a140 100644 --- a/mysql-test/t/parser_not_embedded.test +++ b/mysql-test/t/parser_not_embedded.test @@ -24,3 +24,78 @@ EOF --exec $MYSQL --comment --force --table test <$MYSQLTEST_VARDIR/tmp/bug39559.sql --remove_file $MYSQLTEST_VARDIR/tmp/bug39559.sql +--echo # Bug#46527 "COMMIT AND CHAIN RELEASE does not make sense" +--echo # +--error ER_PARSE_ERROR +COMMIT AND CHAIN RELEASE; + +COMMIT AND NO CHAIN RELEASE; +disconnect default; +connect(default, localhost, root,,); + +COMMIT RELEASE; +disconnect default; +connect(default, localhost, root,,); + +--error ER_PARSE_ERROR +COMMIT CHAIN RELEASE; + +--error ER_PARSE_ERROR +COMMIT NO CHAIN RELEASE; + +--error ER_PARSE_ERROR +COMMIT AND NO RELEASE; +--error ER_PARSE_ERROR +COMMIT AND RELEASE; + +COMMIT NO RELEASE; +--error ER_PARSE_ERROR +COMMIT CHAIN NO RELEASE; +--error ER_PARSE_ERROR +COMMIT NO CHAIN NO RELEASE; + +--error ER_PARSE_ERROR +COMMIT AND RELEASE CHAIN; + +COMMIT AND NO CHAIN NO RELEASE; + +--error ER_PARSE_ERROR +ROLLBACK AND CHAIN RELEASE; + +ROLLBACK AND NO CHAIN RELEASE; +disconnect default; +connect(default, localhost, root,,); + +ROLLBACK RELEASE; +disconnect default; +connect(default, localhost, root,,); + +--error ER_PARSE_ERROR +ROLLBACK CHAIN RELEASE; + +--error ER_PARSE_ERROR +ROLLBACK NO CHAIN RELEASE; +disconnect default; +connect(default, localhost, root,,); + +--error ER_PARSE_ERROR +ROLLBACK AND NO RELEASE; + +--error ER_PARSE_ERROR +ROLLBACK AND RELEASE; + +ROLLBACK NO RELEASE; + +--error ER_PARSE_ERROR +ROLLBACK CHAIN NO RELEASE; + +--error ER_PARSE_ERROR +ROLLBACK NO CHAIN NO RELEASE; +--error ER_PARSE_ERROR +ROLLBACK AND RELEASE CHAIN; + +ROLLBACK AND NO CHAIN NO RELEASE; + +--echo # +--echo # End of 5.5 tests +--echo # diff --git a/mysql-test/t/partition_innodb_semi_consistent.test b/mysql-test/t/partition_innodb_semi_consistent.test index 294521a45d5..7f6b3d48c63 100644 --- a/mysql-test/t/partition_innodb_semi_consistent.test +++ b/mysql-test/t/partition_innodb_semi_consistent.test @@ -31,6 +31,7 @@ set session transaction isolation level repeatable read; set autocommit=0; -- error ER_LOCK_WAIT_TIMEOUT update t1 set a=10 where a=5; +commit; connection a; #DELETE FROM t1 WHERE a=5; commit; @@ -101,6 +102,7 @@ connection con2; --error ER_LOCK_WAIT_TIMEOUT UPDATE t1 SET b = 21 WHERE a = 1; --disable_info +ROLLBACK; --echo # Switch to connection con1 connection con1; @@ -150,6 +152,7 @@ SELECT * FROM t1; connection con2; --reap SELECT * FROM t1; +COMMIT; --echo # Switch to connection con1 connection con1; diff --git a/mysql-test/t/rename.test b/mysql-test/t/rename.test index 5aa1a51a90f..bb90cbafd74 100644 --- a/mysql-test/t/rename.test +++ b/mysql-test/t/rename.test @@ -79,17 +79,15 @@ connection default; --echo End of 4.1 tests -# -# Bug#14959: ALTER TABLE isn't able to rename a view -# +--echo # +--echo # Bug#14959: "ALTER TABLE isn't able to rename a view" +--echo # Bug#53976: "ALTER TABLE RENAME is allowed on views +--echo # (not documented, broken)" +--echo # create table t1(f1 int); create view v1 as select * from t1; +--error ER_WRONG_OBJECT alter table v1 rename to v2; ---error ER_NO_SUCH_TABLE -alter table v1 rename to v2; -rename table v2 to v1; ---error ER_TABLE_EXISTS_ERROR -rename table v2 to v1; drop view v1; drop table t1; diff --git a/mysql-test/t/sp_sync.test b/mysql-test/t/sp_sync.test index 391298b604a..431db463b67 100644 --- a/mysql-test/t/sp_sync.test +++ b/mysql-test/t/sp_sync.test @@ -108,38 +108,35 @@ disconnect con3; --echo # Bug #48246 assert in close_thread_table --echo # +CREATE TABLE t0 (b INTEGER); CREATE TABLE t1 (a INTEGER); CREATE FUNCTION f1(b INTEGER) RETURNS INTEGER RETURN 1; -CREATE PROCEDURE p1() SELECT COUNT(f1(a)) FROM t1; +CREATE PROCEDURE p1() SELECT COUNT(f1(a)) FROM t1, t0; +INSERT INTO t0 VALUES(1); INSERT INTO t1 VALUES(1), (2); --echo # Connection 2 connect (con2, localhost, root); CALL p1(); ---echo # Connection default -connection default; -SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR called'; ---echo # Sending: ---send CREATE TABLE t1 (a INTEGER) - ---echo # Connection 2 -connection con2; -SET DEBUG_SYNC= 'now WAIT_FOR locked'; -SET DEBUG_SYNC= 'before_open_table_wait_refresh SIGNAL called WAIT_FOR created'; ---echo # This call used to cause an assertion. MDL locking conflict will ---echo # cause back-off and retry. A variable indicating if a prelocking list ---echo # exists, used to be not reset properly causing an eventual assert. +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked_t1 WAIT_FOR go_for_t0'; +--echo # This call used to cause an assertion. MDL deadlock with upcoming +--echo # LOCK TABLES statement will cause back-off and retry. +--echo # A variable indicating if a prelocking list exists, used to be not +--echo # reset properly causing an eventual assert. --echo # Sending: --send CALL p1() --echo # Connection default connection default; ---echo # Reaping: CREATE TABLE t1 (a INTEGER) ---error ER_TABLE_EXISTS_ERROR ---reap -SET DEBUG_SYNC= 'now SIGNAL created'; +SET DEBUG_SYNC= 'now WAIT_FOR locked_t1'; +--echo # Issue LOCK TABLES statement which will enter in MDL deadlock +--echo # with CALL statement and as result will cause it to perform +--echo # back-off and retry. +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL go_for_t0'; +LOCK TABLES t0 WRITE, t1 WRITE; +UNLOCK TABLES; --echo # Connection 2 connection con2; @@ -151,7 +148,7 @@ connection default; disconnect con2; DROP PROCEDURE p1; DROP FUNCTION f1; -DROP TABLE t1; +DROP TABLES t0, t1; SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/t/truncate.test b/mysql-test/t/truncate.test index cdfa448f78a..c7a066cc203 100644 --- a/mysql-test/t/truncate.test +++ b/mysql-test/t/truncate.test @@ -102,7 +102,7 @@ SELECT * FROM v1; LOCK TABLE t1 WRITE; --error ER_TABLE_NOT_LOCKED SELECT * FROM v1; ---error ER_NO_SUCH_TABLE +--error ER_TABLE_NOT_LOCKED TRUNCATE v1; --error ER_TABLE_NOT_LOCKED SELECT * FROM v1; @@ -111,7 +111,7 @@ UNLOCK TABLES; LOCK TABLE t1 WRITE, t2 WRITE; --error ER_TABLE_NOT_LOCKED SELECT * FROM v1; ---error ER_NO_SUCH_TABLE +--error ER_TABLE_NOT_LOCKED TRUNCATE v1; --error ER_TABLE_NOT_LOCKED SELECT * FROM v1; @@ -119,14 +119,14 @@ UNLOCK TABLES; # LOCK TABLE v1 WRITE; SELECT * FROM v1; ---error ER_NO_SUCH_TABLE +--error ER_TABLE_NOT_LOCKED TRUNCATE v1; SELECT * FROM v1; UNLOCK TABLES; # LOCK TABLE t1 WRITE, t2 WRITE, v1 WRITE; SELECT * FROM v1; ---error ER_NO_SUCH_TABLE +--error ER_TABLE_NOT_LOCKED TRUNCATE v1; SELECT * FROM v1; UNLOCK TABLES; diff --git a/mysql-test/t/truncate_coverage.test b/mysql-test/t/truncate_coverage.test index b7c08b03c8b..c9c4bd90ca4 100644 --- a/mysql-test/t/truncate_coverage.test +++ b/mysql-test/t/truncate_coverage.test @@ -55,6 +55,12 @@ let $invisible_assignment_in_select = `SELECT @id := $ID`; KILL QUERY @id; --disconnect con2 --echo # +--echo # connection default +--connection default +--error ER_QUERY_INTERRUPTED +reap; +UNLOCK TABLES; +--echo # --echo # connection con1 --connection con1 --echo # Release shared metadata lock by closing HANDLER. @@ -63,9 +69,6 @@ HANDLER t1 CLOSE; --echo # --echo # connection default --connection default ---error ER_QUERY_INTERRUPTED -reap; -UNLOCK TABLES; DROP TABLE t1; SET DEBUG_SYNC='RESET'; ######## @@ -151,13 +154,20 @@ send TRUNCATE TABLE t1; SET DEBUG_SYNC='now WAIT_FOR waiting'; let $invisible_assignment_in_select = `SELECT @id := $ID`; KILL QUERY @id; -COMMIT; ---disconnect con1 --echo # --echo # connection default --connection default --error ER_QUERY_INTERRUPTED reap; +--echo # +--echo # connection con1 +--connection con1 +--echo # Release SW lock by committing transaction. +COMMIT; +--disconnect con1 +--echo # +--echo # connection default +--connection default UNLOCK TABLES; DROP TABLE t1; SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index 4ef8b1898ae..c0564a82b23 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -3902,6 +3902,7 @@ drop procedure p; --echo # CREATE TABLE t1 (a INT); CREATE VIEW v1 AS SELECT a FROM t1; +--error ER_WRONG_OBJECT ALTER TABLE v1; DROP VIEW v1; DROP TABLE t1; diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 4c93f5ffd57..0ea65ce8e4b 100755 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -66,3 +66,7 @@ ADD_CONVENIENCE_LIBRARY(mysys ${MYSYS_SOURCES}) TARGET_LINK_LIBRARIES(mysys dbug strings ${ZLIB_LIBRARY} ${LIBNSL} ${LIBM} ${LIBRT}) DTRACE_INSTRUMENT(mysys) + +ADD_EXECUTABLE(thr_lock thr_lock.c) +TARGET_LINK_LIBRARIES(thr_lock mysys) +SET_TARGET_PROPERTIES(thr_lock PROPERTIES COMPILE_FLAGS "-DMAIN") diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 43db0470735..cff1b9b9845 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -29,7 +29,6 @@ TL_READ_WITH_SHARED_LOCKS TL_READ_HIGH_PRIORITY # High priority read TL_READ_NO_INSERT # Read without concurrent inserts TL_WRITE_ALLOW_WRITE # Write lock that allows other writers -TL_WRITE_ALLOW_READ # Write lock, but allow reading TL_WRITE_CONCURRENT_INSERT # Insert that can be mixed when selects TL_WRITE_DELAYED # Used by delayed insert @@ -41,7 +40,7 @@ TL_WRITE_ONLY # High priority write Locks are prioritized according to: -WRITE_ALLOW_WRITE, WRITE_ALLOW_READ, WRITE_CONCURRENT_INSERT, WRITE_DELAYED, +WRITE_ALLOW_WRITE, WRITE_CONCURRENT_INSERT, WRITE_DELAYED, WRITE_LOW_PRIORITY, READ, WRITE, READ_HIGH_PRIORITY and WRITE_ONLY Locks in the same privilege level are scheduled in first-in-first-out order. @@ -64,9 +63,8 @@ get_status: In MyISAM this stores the number of rows and size of the datafile for concurrent reads. -The lock algorithm allows one to have one TL_WRITE_ALLOW_READ, -TL_WRITE_CONCURRENT_INSERT or one TL_WRITE_DELAYED lock at the same time as -multiple read locks. +The lock algorithm allows one to have one TL_WRITE_CONCURRENT_INSERT or +one TL_WRITE_DELAYED lock at the same time as multiple read locks. */ @@ -238,7 +236,6 @@ static void check_locks(THR_LOCK *lock, const char *where, (((lock->write_wait.data->type == TL_WRITE_CONCURRENT_INSERT || lock->write_wait.data->type == TL_WRITE_ALLOW_WRITE) && !lock->read_no_write_count) || - lock->write_wait.data->type == TL_WRITE_ALLOW_READ || (lock->write_wait.data->type == TL_WRITE_DELAYED && !lock->read.data))) { @@ -543,14 +540,14 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, Request /------- H|++++ WRITE_ALLOW_WRITE - e|++++ WRITE_ALLOW_READ - l|+++- WRITE_CONCURRENT_INSERT - d|++++ WRITE_DELAYED - |||| + e|+++- WRITE_CONCURRENT_INSERT + l|++++ WRITE_DELAYED + d |||| |||\= READ_NO_INSERT ||\ = READ_HIGH_PRIORITY |\ = READ_WITH_SHARED_LOCKS \ = READ + + = Request can be satisified. - = Request cannot be satisified. @@ -620,14 +617,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, result= THR_LOCK_ABORTED; /* Can't wait for this one */ goto end; } - /* - if there is a TL_WRITE_ALLOW_READ lock, we have to wait for a lock - (TL_WRITE_ALLOW_READ is used for ALTER TABLE in MySQL) - */ - if ((!lock->write.data || - lock->write.data->type != TL_WRITE_ALLOW_READ) && - !have_specific_lock(lock->write_wait.data,TL_WRITE_ALLOW_READ) && - (lock->write.data || lock->read.data)) + if (lock->write.data || lock->read.data) { /* Add delayed write lock to write_wait queue, and return at once */ (*lock->write_wait.last)=data; @@ -680,8 +670,6 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, it is OK to grant new lock without additional checks in such situation. **) The exceptions are situations when: - - old lock type is TL_WRITE_ALLOW_READ and new lock type is - TL_WRITE_ALLOW_WRITE - when old lock type is TL_WRITE_DELAYED But these should never happen within MySQL. Therefore it is OK to allow acquiring write lock on the table if @@ -695,9 +683,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, ((lock_type <= lock->write.data->type || (lock_type == TL_WRITE && lock->write.data->type == TL_WRITE_LOW_PRIORITY)) && - ! ((lock_type < TL_WRITE_ALLOW_READ && - lock->write.data->type == TL_WRITE_ALLOW_READ) || - lock->write.data->type == TL_WRITE_DELAYED))); + lock->write.data->type != TL_WRITE_DELAYED)); if ((lock_type == TL_WRITE_ALLOW_WRITE && ! lock->write_wait.data && @@ -1265,10 +1251,7 @@ my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread_id) occurs also other waiters, both readers and writers can be allowed to start. The previous lock is often TL_WRITE_ONLY but can also be - TL_WRITE and TL_WRITE_ALLOW_READ. The normal downgrade variants are - TL_WRITE_ONLY => TL_WRITE_ALLOW_READ After a short exclusive lock - TL_WRITE_ALLOW_READ => TL_WRITE_ALLOW_WRITE After discovering that the - operation didn't need such a high lock. + TL_WRITE. The normal downgrade variants are: TL_WRITE_ONLY => TL_WRITE after a short exclusive lock while holding a write table lock TL_WRITE_ONLY => TL_WRITE_ALLOW_WRITE After a short exclusive lock after @@ -1289,11 +1272,6 @@ void thr_downgrade_write_lock(THR_LOCK_DATA *in_data, #ifndef DBUG_OFF enum thr_lock_type old_lock_type= in_data->type; #endif -#ifdef TO_BE_REMOVED - THR_LOCK_DATA *data, *next; - bool start_writers= FALSE; - bool start_readers= FALSE; -#endif DBUG_ENTER("thr_downgrade_write_only_lock"); mysql_mutex_lock(&lock->mutex); @@ -1302,165 +1280,6 @@ void thr_downgrade_write_lock(THR_LOCK_DATA *in_data, in_data->type= new_lock_type; check_locks(lock,"after downgrading lock",0); -#if TO_BE_REMOVED - switch (old_lock_type) - { - case TL_WRITE_ONLY: - case TL_WRITE: - case TL_WRITE_LOW_PRIORITY: - /* - Previous lock was exclusive we are now ready to start up most waiting - threads. - */ - switch (new_lock_type) - { - case TL_WRITE_ALLOW_READ: - /* Still cannot start WRITE operations. Can only start readers. */ - start_readers= TRUE; - break; - case TL_WRITE: - case TL_WRITE_LOW_PRIORITY: - /* - Still cannot start anything, but new requests are no longer - aborted. - */ - break; - case TL_WRITE_ALLOW_WRITE: - /* - We can start both writers and readers. - */ - start_writers= TRUE; - start_readers= TRUE; - break; - case TL_WRITE_CONCURRENT_INSERT: - case TL_WRITE_DELAYED: - /* - This routine is not designed for those. Lock will be downgraded - but no start of waiters will occur. This is not the optimal but - should be a correct behaviour. - */ - break; - default: - DBUG_ASSERT(0); - } - break; - case TL_WRITE_DELAYED: - case TL_WRITE_CONCURRENT_INSERT: - /* - This routine is not designed for those. Lock will be downgraded - but no start of waiters will occur. This is not the optimal but - should be a correct behaviour. - */ - break; - case TL_WRITE_ALLOW_READ: - DBUG_ASSERT(new_lock_type == TL_WRITE_ALLOW_WRITE); - /* - Previously writers were not allowed to start, now it is ok to - start them again. Readers are already allowed so no reason to - handle them. - */ - start_writers= TRUE; - break; - default: - DBUG_ASSERT(0); - break; - } - if (start_writers) - { - /* - At this time the only active writer can be ourselves. Thus we need - not worry about that there are other concurrent write operations - active on the table. Thus we only need to worry about starting - waiting operations. - We also only come here with TL_WRITE_ALLOW_WRITE as the new - lock type, thus we can start other writers also of the same type. - If we find a lock at exclusive level >= TL_WRITE_LOW_PRIORITY we - don't start any more operations that would be mean those operations - will have to wait for things started afterwards. - */ - DBUG_ASSERT(new_lock_type == TL_WRITE_ALLOW_WRITE); - for (data=lock->write_wait.data; data ; data= next) - { - /* - All WRITE requests compatible with new lock type are also - started - */ - next= data->next; - if (start_writers && data->type == new_lock_type) - { - mysql_cond_t *cond= data->cond; - /* - It is ok to start this waiter. - Move from being first in wait queue to be last in write queue. - */ - if (((*data->prev)= data->next)) - data->next->prev= data->prev; - else - lock->write_wait.last= data->prev; - data->prev= lock->write.last; - lock->write.last= &data->next; - data->next= 0; - check_locks(lock, "Started write lock after downgrade",0); - data->cond= 0; - mysql_cond_signal(cond); - } - else - { - /* - We found an incompatible lock, we won't start any more write - requests to avoid letting writers pass other writers in the - queue. - */ - start_writers= FALSE; - if (data->type >= TL_WRITE_LOW_PRIORITY) - { - /* - We have an exclusive writer in the queue so we won't start - readers either. - */ - start_readers= FALSE; - } - } - } - } - if (start_readers) - { - DBUG_ASSERT(new_lock_type == TL_WRITE_ALLOW_WRITE || - new_lock_type == TL_WRITE_ALLOW_READ); - /* - When we come here we know that the write locks are - TL_WRITE_ALLOW_WRITE or TL_WRITE_ALLOW_READ. This means that reads - are ok - */ - for (data=lock->read_wait.data; data ; data=next) - { - next= data->next; - /* - All reads are ok to start now except TL_READ_NO_INSERT when - write lock is TL_WRITE_ALLOW_READ. - */ - if (new_lock_type != TL_WRITE_ALLOW_READ || - data->type != TL_READ_NO_INSERT) - { - mysql_cond_t *cond= data->cond; - if (((*data->prev)= data->next)) - data->next->prev= data->prev; - else - lock->read_wait.last= data->prev; - data->prev= lock->read.last; - lock->read.last= &data->next; - data->next= 0; - - if (data->type == TL_READ_NO_INSERT) - lock->read_no_write_count++; - check_locks(lock, "Started read lock after downgrade",0); - data->cond= 0; - mysql_cond_signal(cond); - } - } - } - check_locks(lock,"after starting waiters after downgrading lock",0); -#endif mysql_mutex_unlock(&lock->mutex); DBUG_VOID_RETURN; } @@ -1646,15 +1465,14 @@ struct st_test test_8[] = {{1,TL_READ_NO_INSERT},{2,TL_READ_NO_INSERT},{3,TL_REA struct st_test test_9[] = {{4,TL_READ_HIGH_PRIORITY}}; struct st_test test_10[] ={{4,TL_WRITE}}; struct st_test test_11[] = {{0,TL_WRITE_LOW_PRIORITY},{1,TL_WRITE_LOW_PRIORITY},{2,TL_WRITE_LOW_PRIORITY},{3,TL_WRITE_LOW_PRIORITY}}; /* Many writes */ -struct st_test test_12[] = {{0,TL_WRITE_ALLOW_READ},{1,TL_WRITE_ALLOW_READ},{2,TL_WRITE_ALLOW_READ},{3,TL_WRITE_ALLOW_READ}}; /* Many writes */ -struct st_test test_13[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_WRITE_CONCURRENT_INSERT},{2,TL_WRITE_CONCURRENT_INSERT},{3,TL_WRITE_CONCURRENT_INSERT}}; -struct st_test test_14[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_READ}}; -struct st_test test_15[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_READ}}; -struct st_test test_16[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_WRITE_ALLOW_WRITE}}; +struct st_test test_12[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_WRITE_CONCURRENT_INSERT},{2,TL_WRITE_CONCURRENT_INSERT},{3,TL_WRITE_CONCURRENT_INSERT}}; +struct st_test test_13[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_READ}}; +struct st_test test_14[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_READ}}; +struct st_test test_15[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_WRITE_ALLOW_WRITE}}; struct st_test *tests[] = {test_0,test_1,test_2,test_3,test_4,test_5,test_6, test_7,test_8,test_9,test_10,test_11,test_12, - test_13,test_14,test_15,test_16}; + test_13,test_14,test_15}; int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test), sizeof(test_1)/sizeof(struct st_test), sizeof(test_2)/sizeof(struct st_test), @@ -1670,8 +1488,7 @@ int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test), sizeof(test_12)/sizeof(struct st_test), sizeof(test_13)/sizeof(struct st_test), sizeof(test_14)/sizeof(struct st_test), - sizeof(test_15)/sizeof(struct st_test), - sizeof(test_16)/sizeof(struct st_test) + sizeof(test_15)/sizeof(struct st_test) }; @@ -1681,6 +1498,7 @@ static uint thread_count; static ulong sum=0; #define MAX_LOCK_COUNT 8 +#define TEST_TIMEOUT 100000 /* The following functions is for WRITE_CONCURRENT_INSERT */ @@ -1727,7 +1545,7 @@ static void *test_thread(void *arg) multi_locks[i]= &data[i]; data[i].type= tests[param][i].lock_type; } - thr_multi_lock(multi_locks, lock_counts[param], &owner); + thr_multi_lock(multi_locks, lock_counts[param], &owner, TEST_TIMEOUT); mysql_mutex_lock(&LOCK_thread_count); { int tmp=rand() & 7; /* Do something from 0-2 sec */ diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index a0e157b7806..44160c32225 100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -75,7 +75,7 @@ SET (SQL_SOURCE sql_connect.cc scheduler.cc sql_profile.cc event_parse_data.cc sql_signal.cc rpl_handler.cc mdl.cc - transaction.cc sys_vars.cc + transaction.cc sys_vars.cc sql_truncate.cc datadict.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE}) diff --git a/sql/Makefile.am b/sql/Makefile.am index f57d1f3bc34..0616893a014 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -39,7 +39,9 @@ DTRACEFILES = filesort.o \ sql_connect.o \ sql_cursor.o \ sql_delete.o \ + sql_truncate.o \ sql_insert.o \ + datadict.o \ sql_parse.o \ sql_prepare.o \ sql_select.o \ @@ -56,7 +58,9 @@ DTRACEFILES_DEPEND = filesort.o \ sql_connect.o \ sql_cursor.o \ sql_delete.o \ + sql_truncate.o \ sql_insert.o \ + datadict.o \ sql_parse.o \ sql_prepare.o \ sql_select.o \ @@ -121,7 +125,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ sql_audit.h \ contributors.h sql_servers.h sql_signal.h records.h \ sql_prepare.h rpl_handler.h replication.h mdl.h \ - sql_plist.h transaction.h sys_vars.h + sql_plist.h transaction.h sys_vars.h sql_truncate.h \ + datadict.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -136,10 +141,10 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_connect.cc scheduler.cc sql_parse.cc \ keycaches.cc set_var.cc sql_yacc.yy sys_vars.cc \ sql_base.cc table.cc sql_select.cc sql_insert.cc \ - sql_profile.cc \ + datadict.cc sql_profile.cc \ sql_prepare.cc sql_error.cc sql_locale.cc \ sql_update.cc sql_delete.cc uniques.cc sql_do.cc \ - procedure.cc sql_test.cc \ + procedure.cc sql_test.cc sql_truncate.cc \ log.cc init.cc derror.cc sql_acl.cc \ unireg.cc des_key_file.cc \ log_event.cc rpl_record.cc \ diff --git a/sql/datadict.cc b/sql/datadict.cc new file mode 100644 index 00000000000..33c3b6bc700 --- /dev/null +++ b/sql/datadict.cc @@ -0,0 +1,161 @@ +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "datadict.h" +#include "sql_priv.h" +#include "sql_class.h" +#include "sql_table.h" + + +/** + Check type of .frm if we are not going to parse it. + + @param path path to FRM file + + @retval FRMTYPE_ERROR error + @retval FRMTYPE_TABLE table + @retval FRMTYPE_VIEW view +*/ + +frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) +{ + File file; + uchar header[10]; //"TYPE=VIEW\n" it is 10 characters + size_t error; + DBUG_ENTER("dd_frm_type"); + + *dbt= DB_TYPE_UNKNOWN; + + if ((file= mysql_file_open(key_file_frm, path, O_RDONLY | O_SHARE, MYF(0))) < 0) + DBUG_RETURN(FRMTYPE_ERROR); + error= mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP)); + mysql_file_close(file, MYF(MY_WME)); + + if (error) + DBUG_RETURN(FRMTYPE_ERROR); + if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header))) + DBUG_RETURN(FRMTYPE_VIEW); + + /* + This is just a check for DB_TYPE. We'll return default unknown type + if the following test is true (arg #3). This should not have effect + on return value from this function (default FRMTYPE_TABLE) + */ + if (header[0] != (uchar) 254 || header[1] != 1 || + (header[2] != FRM_VER && header[2] != FRM_VER+1 && + (header[2] < FRM_VER+3 || header[2] > FRM_VER+4))) + DBUG_RETURN(FRMTYPE_TABLE); + + *dbt= (enum legacy_db_type) (uint) *(header + 3); + + /* Probably a table. */ + DBUG_RETURN(FRMTYPE_TABLE); +} + + +/** + Given a table name, check if the storage engine for the + table referred by this name supports an option 'flag'. + Return an error if the table does not exist or is not a + base table. + + @pre Any metadata lock on the table. + + @param[in] thd The current session. + @param[in] db Table schema. + @param[in] table_name Table database. + @param[in] flag The option to check. + @param[out] yes_no The result. Undefined if error. +*/ + +bool dd_check_storage_engine_flag(THD *thd, + const char *db, const char *table_name, + uint32 flag, bool *yes_no) +{ + char path[FN_REFLEN + 1]; + enum legacy_db_type db_type; + handlerton *table_type; + LEX_STRING db_name = {(char *) db, strlen(db)}; + + if (check_db_name(&db_name)) + { + my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); + return TRUE; + } + + if (check_table_name(table_name, strlen(table_name), FALSE)) + { + my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name); + return TRUE; + } + + /* There should be at least some lock on the table. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, + table_name, MDL_SHARED)); + + (void) build_table_filename(path, sizeof(path) - 1, db, + table_name, reg_ext, 0); + + dd_frm_type(thd, path, &db_type); + + /* Type is unknown if the object is not found or is not a table. */ + if (db_type == DB_TYPE_UNKNOWN) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), db, table_name); + return TRUE; + } + + table_type= ha_resolve_by_legacy_type(thd, db_type); + *yes_no= ha_check_storage_engine_flag(table_type, flag); + + return FALSE; +} + + +/* + Regenerate a metadata locked table. + + @param thd Thread context. + @param db Name of the database to which the table belongs to. + @param name Table name. + + @retval FALSE Success. + @retval TRUE Error. +*/ + +bool dd_recreate_table(THD *thd, const char *db, const char *table_name) +{ + bool error= TRUE; + HA_CREATE_INFO create_info; + char path[FN_REFLEN + 1]; + DBUG_ENTER("dd_recreate_table"); + + /* There should be a exclusive metadata lock on the table. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, + MDL_EXCLUSIVE)); + + memset(&create_info, 0, sizeof(create_info)); + + /* Create a path to the table, but without a extension. */ + build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); + + /* Attempt to reconstruct the table. */ + mysql_mutex_lock(&LOCK_open); + error= ha_create_table(thd, path, db, table_name, &create_info, TRUE); + mysql_mutex_unlock(&LOCK_open); + + DBUG_RETURN(error); +} + diff --git a/sql/datadict.h b/sql/datadict.h new file mode 100644 index 00000000000..05b5a9bba4b --- /dev/null +++ b/sql/datadict.h @@ -0,0 +1,40 @@ +#ifndef DATADICT_INCLUDED +#define DATADICT_INCLUDED +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "handler.h" + +/* + Data dictionary API. +*/ + +enum frm_type_enum +{ + FRMTYPE_ERROR= 0, + FRMTYPE_TABLE, + FRMTYPE_VIEW +}; + + +frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt); + +bool dd_check_storage_engine_flag(THD *thd, + const char *db, const char *table_name, + uint32 flag, + bool *yes_no); +bool dd_recreate_table(THD *thd, const char *db, const char *table_name); + +#endif // DATADICT_INCLUDED diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 33b9a1cafda..c646642dbba 100755 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -203,7 +203,6 @@ pre_init_event_thread(THD* thd) */ thd->proc_info= "Initialized"; - thd->version= refresh_version; thd->set_time(); /* Do not use user-supplied timeout value for system threads. */ diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 9f003174d2e..68b98c79a50 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -7408,9 +7408,10 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); // Delete the table and all related files TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= (char*) db; - table_list.alias= table_list.table_name= (char*)file_name_str; + table_list.init_one_table(db, strlen(db), file_name_str, + strlen(file_name_str), file_name_str, + TL_WRITE); + table_list.mdl_request.set_type(MDL_EXCLUSIVE); (void)mysql_rm_table_part2(thd, &table_list, FALSE, /* if_exists */ FALSE, /* drop_temporary */ @@ -9509,7 +9510,6 @@ pthread_handler_t ndb_util_thread_func(void *arg __attribute__((unused))) if (thd->store_globals()) goto ndb_util_thread_fail; thd->init_for_queries(); - thd->version=refresh_version; thd->main_security_ctx.host_or_ip= ""; thd->client_capabilities = 0; my_net_init(&thd->net, 0); diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index a61f5b4feea..ab046164485 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -3679,7 +3679,6 @@ pthread_handler_t ndb_binlog_thread_func(void *arg) thd->init_for_queries(); thd->command= COM_DAEMON; thd->system_thread= SYSTEM_THREAD_NDBCLUSTER_BINLOG; - thd->version= refresh_version; thd->main_security_ctx.host_or_ip= ""; thd->client_capabilities= 0; my_net_init(&thd->net, 0); @@ -3966,9 +3965,9 @@ restart: !ndb_binlog_running)) break; /* Shutting down server */ - if (ndb_binlog_index && ndb_binlog_index->s->version < refresh_version) + if (ndb_binlog_index && ndb_binlog_index->s->needs_reopen()) { - if (ndb_binlog_index->s->version < refresh_version) + if (ndb_binlog_index->s->needs_reopen()) { close_thread_tables(thd); ndb_binlog_index= 0; diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index a6f106be3be..3fb5a30b560 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -1308,7 +1308,7 @@ int ha_partition::prepare_new_partition(TABLE *tbl, assumes that external_lock() is last call that may fail here. Otherwise see description for cleanup_new_partition(). */ - if ((error= file->ha_external_lock(ha_thd(), m_lock_type))) + if ((error= file->ha_external_lock(ha_thd(), F_WRLCK))) goto error_external_lock; DBUG_PRINT("info", ("partition %s external locked", part_name)); diff --git a/sql/handler.cc b/sql/handler.cc index 11f684a8010..587490dd708 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1302,7 +1302,6 @@ int ha_commit_one_phase(THD *thd, bool all) if (thd->transaction.changed_tables) query_cache.invalidate(thd->transaction.changed_tables); #endif - thd->variables.tx_isolation=thd->session_tx_isolation; } } /* Free resources and perform other cleanup even for 'empty' transactions. */ @@ -1379,8 +1378,6 @@ int ha_rollback_trans(THD *thd, bool all) if (is_real_trans && thd->transaction_rollback_request && thd->transaction.xid_state.xa_state != XA_NOTR) thd->transaction.xid_state.rm_error= thd->stmt_da->sql_errno(); - if (all) - thd->variables.tx_isolation=thd->session_tx_isolation; } /* Always cleanup. Even if nht==0. There may be savepoints. */ if (is_real_trans) diff --git a/sql/lock.cc b/sql/lock.cc index 3f13f15454a..52d97a2422b 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -415,7 +415,7 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) THR_LOCK_DATA **lock=sql_lock->locks; for (i=found=0 ; i < sql_lock->lock_count ; i++) { - if (sql_lock->locks[i]->type >= TL_WRITE_ALLOW_READ) + if (sql_lock->locks[i]->type > TL_WRITE_ALLOW_WRITE) { swap_variables(THR_LOCK_DATA *, *lock, sql_lock->locks[i]); lock++; @@ -435,7 +435,7 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) for (i=found=0 ; i < sql_lock->table_count ; i++) { DBUG_ASSERT(sql_lock->table[i]->lock_position == i); - if ((uint) sql_lock->table[i]->reginfo.lock_type >= TL_WRITE_ALLOW_READ) + if ((uint) sql_lock->table[i]->reginfo.lock_type > TL_WRITE_ALLOW_WRITE) { swap_variables(TABLE *, *table, sql_lock->table[i]); table++; @@ -866,6 +866,8 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, before calling it. Also it cannot be called while holding LOCK_open mutex. Both these invariants are enforced by asserts in MDL_context::acquire_locks(). + @note Initialization of MDL_request members of TABLE_LIST elements + is a responsibility of the caller. @retval FALSE Success. @retval TRUE Failure (OOM or thread was killed). @@ -880,12 +882,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) - { - lock_table->mdl_request.init(MDL_key::TABLE, - lock_table->db, lock_table->table_name, - MDL_EXCLUSIVE); mdl_requests.push_front(&lock_table->mdl_request); - } mdl_requests.push_front(&global_request); @@ -1301,7 +1298,8 @@ wait_if_global_read_lock(THD *thd, bool abort_on_refresh, old_message=thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, "Waiting for release of readlock"); while (must_wait && ! thd->killed && - (!abort_on_refresh || thd->version == refresh_version)) + (!abort_on_refresh || !thd->open_tables || + thd->open_tables->s->version == refresh_version)) { DBUG_PRINT("signal", ("Waiting for COND_global_read_lock")); mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); diff --git a/sql/lock.h b/sql/lock.h index f7c19913675..84c7bce0679 100644 --- a/sql/lock.h +++ b/sql/lock.h @@ -15,33 +15,32 @@ typedef struct st_mysql_lock MYSQL_LOCK; #define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 #define MYSQL_LOCK_LOG_TABLE 0x0010 -#define MYSQL_OPEN_TAKE_UPGRADABLE_MDL 0x0020 /** Do not try to acquire a metadata lock on the table: we already have one. */ -#define MYSQL_OPEN_HAS_MDL_LOCK 0x0040 +#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020 /** If in locked tables mode, ignore the locked tables and get a new instance of the table. */ -#define MYSQL_OPEN_GET_NEW_TABLE 0x0080 +#define MYSQL_OPEN_GET_NEW_TABLE 0x0040 /** Don't look up the table in the list of temporary tables. */ -#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 +#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080 /** Fail instead of waiting when conficting metadata lock is discovered. */ -#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0200 +#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100 /** Open tables using MDL_SHARED lock instead of one specified in parser. */ -#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0400 +#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200 /** Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified in parser. */ -#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0800 +#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400 /** When opening or locking the table, use the maximum timeout (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value. */ -#define MYSQL_LOCK_IGNORE_TIMEOUT 0x1000 +#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800 /** Please refer to the internals manual. */ #define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\ diff --git a/sql/log.cc b/sql/log.cc index fd17e04b212..680a56ec161 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -370,8 +370,8 @@ bool LOGGER::is_log_table_enabled(uint log_table_type) /* Check if a given table is opened log table */ -int check_if_log_table(uint db_len, const char *db, uint table_name_len, - const char *table_name, uint check_if_opened) +int check_if_log_table(size_t db_len, const char *db, size_t table_name_len, + const char *table_name, bool check_if_opened) { if (db_len == 5 && !(lower_case_table_names ? diff --git a/sql/log.h b/sql/log.h index cd3faace598..d264f62fb64 100644 --- a/sql/log.h +++ b/sql/log.h @@ -490,8 +490,8 @@ public: }; -int check_if_log_table(uint db_len, const char *db, uint table_name_len, - const char *table_name, uint check_if_opened); +int check_if_log_table(size_t db_len, const char *db, size_t table_name_len, + const char *table_name, bool check_if_opened); class Log_to_csv_event_handler: public Log_event_handler { diff --git a/sql/mdl.cc b/sql/mdl.cc index ddf518fbb1c..184b3c6051d 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -21,28 +21,28 @@ #ifdef HAVE_PSI_INTERFACE static PSI_mutex_key key_MDL_map_mutex; -static PSI_mutex_key key_MDL_context_signal_mutex; +static PSI_mutex_key key_MDL_wait_LOCK_wait_status; static PSI_mutex_info all_mdl_mutexes[]= { { &key_MDL_map_mutex, "MDL_map::mutex", PSI_FLAG_GLOBAL}, - { &key_MDL_context_signal_mutex, "MDL_context::signal", 0} + { &key_MDL_wait_LOCK_wait_status, "MDL_wait::LOCK_wait_status", 0} }; static PSI_rwlock_key key_MDL_lock_rwlock; -static PSI_rwlock_key key_MDL_context_waiting_for_rwlock; +static PSI_rwlock_key key_MDL_context_LOCK_waiting_for; static PSI_rwlock_info all_mdl_rwlocks[]= { { &key_MDL_lock_rwlock, "MDL_lock::rwlock", 0}, - { &key_MDL_context_waiting_for_rwlock, "MDL_context::waiting_for_lock", 0} + { &key_MDL_context_LOCK_waiting_for, "MDL_context::LOCK_waiting_for", 0} }; -static PSI_cond_key key_MDL_context_signal_cond; +static PSI_cond_key key_MDL_wait_COND_wait_status; static PSI_cond_info all_mdl_conds[]= { - { &key_MDL_context_signal_cond, "MDL_context::signal", 0} + { &key_MDL_wait_COND_wait_status, "MDL_context::COND_wait_status", 0} }; /** @@ -105,23 +105,46 @@ enum enum_deadlock_weight }; - /** A context of the recursive traversal through all contexts in all sessions in search for deadlock. */ -class Deadlock_detection_context +class Deadlock_detection_visitor { public: - Deadlock_detection_context(MDL_context *start_arg) - : start(start_arg), - victim(NULL), - current_search_depth(0) - { } - MDL_context *start; - MDL_context *victim; - uint current_search_depth; + Deadlock_detection_visitor(MDL_context *start_node_arg) + : m_start_node(start_node_arg), + m_victim(NULL), + m_current_search_depth(0) + {} + bool enter_node(MDL_context * /* unused */); + void leave_node(MDL_context * /* unused */); + + bool inspect_edge(MDL_context *dest); + + MDL_context *get_victim() const { return m_victim; } + + /** + Change the deadlock victim to a new one if it has lower deadlock + weight. + */ + MDL_context *opt_change_victim_to(MDL_context *new_victim); +private: + /** + The context which has initiated the search. There + can be multiple searches happening in parallel at the same time. + */ + MDL_context *m_start_node; + /** If a deadlock is found, the context that identifies the victim. */ + MDL_context *m_victim; + /** Set to the 0 at start. Increased whenever + we descend into another MDL context (aka traverse to the next + wait-for graph node). When MAX_SEARCH_DEPTH is reached, we + assume that a deadlock is found, even if we have not found a + loop. + */ + uint m_current_search_depth; /** Maximum depth for deadlock searches. After this depth is achieved we will unconditionally declare that there is a @@ -140,6 +163,74 @@ public: /** + Enter a node of a wait-for graph. After + a node is entered, inspect_edge() will be called + for all wait-for destinations of this node. Then + leave_node() will be called. + We call "enter_node()" for all nodes we inspect, + including the starting node. + + @retval TRUE Maximum search depth exceeded. + @retval FALSE OK. +*/ + +bool Deadlock_detection_visitor::enter_node(MDL_context * /* unused */) +{ + if (++m_current_search_depth >= MAX_SEARCH_DEPTH) + return TRUE; + return FALSE; +} + + +/** + Done inspecting this node. Decrease the search + depth. Clear the node for debug safety. +*/ + +void Deadlock_detection_visitor::leave_node(MDL_context * /* unused */) +{ + --m_current_search_depth; +} + + +/** + Inspect a wait-for graph edge from one MDL context to another. + + @retval TRUE A loop is found. + @retval FALSE No loop is found. +*/ + +bool Deadlock_detection_visitor::inspect_edge(MDL_context *node) +{ + return node == m_start_node; +} + + +/** + Change the deadlock victim to a new one if it has lower deadlock + weight. + + @retval new_victim Victim is not changed. + @retval !new_victim New victim became the current. +*/ + +MDL_context * +Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim) +{ + if (m_victim == NULL || + m_victim->get_deadlock_weight() >= new_victim->get_deadlock_weight()) + { + /* Swap victims, unlock the old one. */ + MDL_context *tmp= m_victim; + m_victim= new_victim; + return tmp; + } + /* No change, unlock the current context. */ + return new_victim; +} + + +/** Get a bit corresponding to enum_mdl_type value in a granted/waiting bitmaps and compatibility matrices. */ @@ -168,7 +259,9 @@ public: typedef I_P_List<MDL_ticket, I_P_List_adapter<MDL_ticket, &MDL_ticket::next_in_lock, - &MDL_ticket::prev_in_lock> > + &MDL_ticket::prev_in_lock>, + I_P_List_null_counter, + I_P_List_fast_push_back<MDL_ticket> > List; operator const List &() const { return m_list; } Ticket_list() :m_bitmap(0) {} @@ -254,25 +347,12 @@ public: } } - /** - Wake up contexts which are waiting to acquire lock on the object and - which may succeed now, when we released some lock on it or removed - some pending request from its waiters list (the latter can happen, - for example, when context trying to acquire exclusive on the object - lock is killed). - */ - void wake_up_waiters() - { - MDL_lock::Ticket_iterator it(m_waiting); - MDL_ticket *awake_ticket; + void reschedule_waiters(); - while ((awake_ticket= it++)) - awake_ticket->get_ctx()->awake(MDL_context::NORMAL_WAKE_UP); - } void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket); bool find_deadlock(MDL_ticket *waiting_ticket, - Deadlock_detection_context *deadlock_ctx); + Deadlock_detection_visitor *dvisitor); /** List of granted tickets for this lock. */ Ticket_list m_granted; @@ -408,7 +488,7 @@ mdl_locks_key(const uchar *record, size_t *length, statement, the design capitalizes on that to later save on look ups in the table definition cache. This leads to reduced contention overall and on LOCK_open in particular. - Please see the description of MDL_context::acquire_shared_lock() + Please see the description of MDL_context::acquire_lock() for details. */ @@ -635,13 +715,9 @@ MDL_context::MDL_context() :m_trans_sentinel(NULL), m_thd(NULL), m_needs_thr_lock_abort(FALSE), - m_waiting_for(NULL), - m_deadlock_weight(0), - m_signal(NO_WAKE_UP) + m_waiting_for(NULL) { - mysql_prlock_init(key_MDL_context_waiting_for_rwlock, &m_waiting_for_lock); - mysql_mutex_init(key_MDL_context_signal_mutex, &m_signal_lock, NULL); - mysql_cond_init(key_MDL_context_signal_mutex, &m_signal_cond, NULL); + mysql_prlock_init(key_MDL_context_LOCK_waiting_for, &m_LOCK_waiting_for); } @@ -661,9 +737,7 @@ void MDL_context::destroy() { DBUG_ASSERT(m_tickets.is_empty()); - mysql_prlock_destroy(&m_waiting_for_lock); - mysql_mutex_destroy(&m_signal_lock); - mysql_cond_destroy(&m_signal_cond); + mysql_prlock_destroy(&m_LOCK_waiting_for); } @@ -750,13 +824,6 @@ MDL_request::create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, } -uint MDL_request::get_deadlock_weight() const -{ - return key.mdl_namespace() == MDL_key::GLOBAL || - type > MDL_SHARED_NO_WRITE ? - MDL_DEADLOCK_WEIGHT_DDL : MDL_DEADLOCK_WEIGHT_DML; -} - /** Auxiliary functions needed for creation/destruction of MDL_lock objects. @@ -805,6 +872,21 @@ void MDL_ticket::destroy(MDL_ticket *ticket) /** + Return the 'weight' of this ticket for the + victim selection algorithm. Requests with + lower weight are preferred to requests + with higher weight when choosing a victim. +*/ + +uint MDL_ticket::get_deadlock_weight() const +{ + return (m_lock->key.mdl_namespace() == MDL_key::GLOBAL || + m_type > MDL_SHARED_NO_WRITE ? + MDL_DEADLOCK_WEIGHT_DDL : MDL_DEADLOCK_WEIGHT_DML); +} + + +/** Helper functions and macros to be used for killable waiting in metadata locking subsystem. @@ -865,27 +947,120 @@ static inline void mdl_exit_cond(THD *thd, } -MDL_context::mdl_signal_type MDL_context::timed_wait(struct timespec - *abs_timeout) +/** Construct an empty wait slot. */ + +MDL_wait::MDL_wait() + :m_wait_status(EMPTY) +{ + mysql_mutex_init(key_MDL_wait_LOCK_wait_status, &m_LOCK_wait_status, NULL); + mysql_cond_init(key_MDL_wait_COND_wait_status, &m_COND_wait_status, NULL); +} + + +/** Destroy system resources. */ + +MDL_wait::~MDL_wait() +{ + mysql_mutex_destroy(&m_LOCK_wait_status); + mysql_cond_destroy(&m_COND_wait_status); +} + + +/** + Set the status unless it's already set. Return FALSE if set, + TRUE otherwise. +*/ + +bool MDL_wait::set_status(enum_wait_status status_arg) +{ + bool was_occupied= TRUE; + mysql_mutex_lock(&m_LOCK_wait_status); + if (m_wait_status == EMPTY) + { + was_occupied= FALSE; + m_wait_status= status_arg; + mysql_cond_signal(&m_COND_wait_status); + } + mysql_mutex_unlock(&m_LOCK_wait_status); + return was_occupied; +} + + +/** Query the current value of the wait slot. */ + +MDL_wait::enum_wait_status MDL_wait::get_status() +{ + enum_wait_status result; + mysql_mutex_lock(&m_LOCK_wait_status); + result= m_wait_status; + mysql_mutex_unlock(&m_LOCK_wait_status); + return result; +} + + +/** Clear the current value of the wait slot. */ + +void MDL_wait::reset_status() +{ + mysql_mutex_lock(&m_LOCK_wait_status); + m_wait_status= EMPTY; + mysql_mutex_unlock(&m_LOCK_wait_status); +} + + +/** + Wait for the status to be assigned to this wait slot. + + @param abs_timeout Absolute time after which waiting should stop. + @param set_status_on_tiemout TRUE - If in case of timeout waiting + context should close the wait slot by + sending TIMEOUT to itself. + FALSE - Otherwise. + + @returns Signal posted. +*/ + +MDL_wait::enum_wait_status +MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout, + bool set_status_on_timeout) { const char *old_msg; - mdl_signal_type result; + enum_wait_status result; st_my_thread_var *mysys_var= my_thread_var; int wait_result= 0; - mysql_mutex_lock(&m_signal_lock); + mysql_mutex_lock(&m_LOCK_wait_status); - old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock); + old_msg= MDL_ENTER_COND(thd, mysys_var, &m_COND_wait_status, + &m_LOCK_wait_status); - while (!m_signal && !mysys_var->abort && + while (!m_wait_status && !mysys_var->abort && wait_result != ETIMEDOUT && wait_result != ETIME) - wait_result= mysql_cond_timedwait(&m_signal_cond, &m_signal_lock, + wait_result= mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status, abs_timeout); - result= (m_signal != NO_WAKE_UP || mysys_var->abort) ? - m_signal : TIMEOUT_WAKE_UP; + if (m_wait_status == EMPTY) + { + /* + Wait has ended not due to a status being set from another + thread but due to this connection/statement being killed or a + time out. + To avoid races, which may occur if another thread sets + GRANTED status before the code which calls this method + processes the abort/timeout, we assign the status under + protection of the m_LOCK_wait_status, within the critical + section. An exception is when set_status_on_timeout is + false, which means that the caller intends to restart the + wait. + */ + if (mysys_var->abort) + m_wait_status= KILLED; + else if (set_status_on_timeout) + m_wait_status= TIMEOUT; + } + result= m_wait_status; - MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg); + MDL_EXIT_COND(thd, mysys_var, &m_LOCK_wait_status, old_msg); return result; } @@ -925,7 +1100,11 @@ void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket) called by other threads. */ DBUG_ASSERT(ticket->get_lock()); - m_list.push_front(ticket); + /* + Add ticket to the *back* of the queue to ensure fairness + among requests with the same priority. + */ + m_list.push_back(ticket); m_bitmap|= MDL_BIT(ticket->get_type()); } @@ -953,6 +1132,75 @@ void MDL_lock::Ticket_list::remove_ticket(MDL_ticket *ticket) /** + Determine waiting contexts which requests for the lock can be + satisfied, grant lock to them and wake them up. + + @note Together with MDL_lock::add_ticket() this method implements + fair scheduling among requests with the same priority. + It tries to grant lock from the head of waiters list, while + add_ticket() adds new requests to the back of this list. + +*/ + +void MDL_lock::reschedule_waiters() +{ + MDL_lock::Ticket_iterator it(m_waiting); + MDL_ticket *ticket; + + /* + Find the first (and hence the oldest) waiting request which + can be satisfied (taking into account priority). Grant lock to it. + Repeat the process for the remainder of waiters. + Note we don't need to re-start iteration from the head of the + list after satisfying the first suitable request as in our case + all compatible types of requests have the same priority. + + TODO/FIXME: We should: + - Either switch to scheduling without priorities + which will allow to stop iteration through the + list of waiters once we found the first ticket + which can't be satisfied + - Or implement some check using bitmaps which will + allow to stop iteration in cases when, e.g., we + grant SNRW lock and there are no pending S or + SH locks. + */ + while ((ticket= it++)) + { + if (can_grant_lock(ticket->get_type(), ticket->get_ctx())) + { + if (! ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED)) + { + /* + Satisfy the found request by updating lock structures. + It is OK to do so even after waking up the waiter since any + session which tries to get any information about the state of + this lock has to acquire MDL_lock::m_rwlock first and thus, + when manages to do so, already sees an updated state of the + MDL_lock object. + */ + m_waiting.remove_ticket(ticket); + m_granted.add_ticket(ticket); + + /* If we are granting an X lock, release the cached object. */ + if (ticket->get_type() == MDL_EXCLUSIVE && cached_object) + (*cached_object_release_hook)(cached_object); + cached_object= NULL; + } + /* + If we could not update the wait slot of the waiter, + it can be due to fact that its connection/statement was + killed or it has timed out (i.e. the slot is not empty). + Since in all such cases the waiter assumes that the lock was + not been granted, we should keep the request in the waiting + queue and look for another request to reschedule. + */ + } + } +} + + +/** Compatibility (or rather "incompatibility") matrices for global metadata lock. Arrays of bitmaps which elements specify which granted/waiting locks are incompatible with type of lock being requested. @@ -1159,9 +1407,19 @@ void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket) { /* There can be some contexts waiting to acquire a lock - which now might be able to do it. Wake them up! + which now might be able to do it. Grant the lock to + them and wake them up! + + We always try to reschedule locks, since there is no easy way + (i.e. by looking at the bitmaps) to find out whether it is + required or not. + In a general case, even when the queue's bitmap is not changed + after removal of the ticket, there is a chance that some request + can be satisfied (due to the fact that a granted request + reflected in the bitmap might belong to the same context as a + pending request). */ - wake_up_waiters(); + reschedule_waiters(); mysql_prlock_unlock(&m_rwlock); } } @@ -1262,29 +1520,6 @@ MDL_context::find_ticket(MDL_request *mdl_request, /** - Acquire one lock with waiting for conflicting locks to go away if needed. - - @note This is an internal method which should not be used outside of MDL - subsystem as in most cases simply waiting for conflicting locks to - go away will lead to deadlock. - - @param mdl_request [in/out] Lock request object for lock to be acquired - - @param lock_wait_timeout [in] Seconds to wait before timeout. - - @retval FALSE Success. MDL_request::ticket points to the ticket - for the lock. - @retval TRUE Failure (Out of resources or waiting is aborted), -*/ - -bool -MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) -{ - return acquire_lock_impl(mdl_request, lock_wait_timeout); -} - - -/** Try to acquire one lock. Unlike exclusive locks, shared locks are acquired one by @@ -1306,15 +1541,55 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) @retval FALSE Success. The lock may have not been acquired. Check the ticket, if it's NULL, a conflicting lock - exists and another attempt should be made after releasing - all current locks and waiting for conflicting lock go - away (using MDL_context::wait_for_lock()). + exists. @retval TRUE Out of resources, an error has been reported. */ bool MDL_context::try_acquire_lock(MDL_request *mdl_request) { + MDL_ticket *ticket; + + if (try_acquire_lock_impl(mdl_request, &ticket)) + return TRUE; + + if (! mdl_request->ticket) + { + /* + Our attempt to acquire lock without waiting has failed. + Let us release resources which were acquired in the process. + We can't get here if we allocated a new lock object so there + is no need to release it. + */ + DBUG_ASSERT(! ticket->m_lock->is_empty()); + mysql_prlock_unlock(&ticket->m_lock->m_rwlock); + MDL_ticket::destroy(ticket); + } + + return FALSE; +} + + +/** + Auxiliary method for acquiring lock without waiting. + + @param mdl_request [in/out] Lock request object for lock to be acquired + @param out_ticket [out] Ticket for the request in case when lock + has not been acquired. + + @retval FALSE Success. The lock may have not been acquired. + Check MDL_request::ticket, if it's NULL, a conflicting + lock exists. In this case "out_ticket" out parameter + points to ticket which was constructed for the request. + MDL_ticket::m_lock points to the corresponding MDL_lock + object and MDL_lock::m_rwlock write-locked. + @retval TRUE Out of resources, an error has been reported. +*/ + +bool +MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, + MDL_ticket **out_ticket) +{ MDL_lock *lock; MDL_key *key= &mdl_request->key; MDL_ticket *ticket; @@ -1374,10 +1649,16 @@ MDL_context::try_acquire_lock(MDL_request *mdl_request) return TRUE; } + ticket->m_lock= lock; + if (lock->can_grant_lock(mdl_request->type, this)) { - ticket->m_lock= lock; lock->m_granted.add_ticket(ticket); + + if (mdl_request->type == MDL_EXCLUSIVE && lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + lock->cached_object= NULL; + mysql_prlock_unlock(&lock->m_rwlock); m_tickets.push_front(ticket); @@ -1385,12 +1666,7 @@ MDL_context::try_acquire_lock(MDL_request *mdl_request) mdl_request->ticket= ticket; } else - { - /* We can't get here if we allocated a new lock. */ - DBUG_ASSERT(! lock->is_empty()); - mysql_prlock_unlock(&lock->m_rwlock); - MDL_ticket::destroy(ticket); - } + *out_ticket= ticket; return FALSE; } @@ -1468,121 +1744,125 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) /** - Auxiliary method for acquiring an exclusive lock. - - @param mdl_request Request for the lock to be acqured. + Acquire one lock with waiting for conflicting locks to go away if needed. - @param lock_wait_timeout Seconds to wait before timeout. + @param mdl_request [in/out] Lock request object for lock to be acquired - @note Should not be used outside of MDL subsystem. Instead one - should call acquire_lock() or acquire_locks() - methods which ensure that conditions for deadlock-free - lock acquisition are fulfilled. + @param lock_wait_timeout [in] Seconds to wait before timeout. - @retval FALSE Success - @retval TRUE Failure + @retval FALSE Success. MDL_request::ticket points to the ticket + for the lock. + @retval TRUE Failure (Out of resources or waiting is aborted), */ -bool MDL_context::acquire_lock_impl(MDL_request *mdl_request, - ulong lock_wait_timeout) +bool +MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) { MDL_lock *lock; MDL_ticket *ticket; - bool not_used; - st_my_thread_var *mysys_var= my_thread_var; - MDL_key *key= &mdl_request->key; struct timespec abs_timeout; - struct timespec abs_shortwait; + MDL_wait::enum_wait_status wait_status; + /* Do some work outside the critical section. */ set_timespec(abs_timeout, lock_wait_timeout); - mysql_mutex_assert_not_owner(&LOCK_open); - - DBUG_ASSERT(mdl_request->ticket == NULL); - /* Don't take chances in production. */ - mdl_request->ticket= NULL; + if (try_acquire_lock_impl(mdl_request, &ticket)) + return TRUE; - /* - Check whether the context already holds an exclusive lock on the object, - and if so, grant the request. - */ - if ((ticket= find_ticket(mdl_request, ¬_used))) + if (mdl_request->ticket) { - DBUG_ASSERT(ticket->m_lock); - mdl_request->ticket= ticket; + /* + We have managed to acquire lock without waiting. + MDL_lock, MDL_context and MDL_request were updated + accordingly, so we can simply return success. + */ return FALSE; } - DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE || - is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE)); - - /* Early allocation: ticket will be needed in any case. */ - if (!(ticket= MDL_ticket::create(this, mdl_request->type))) - return TRUE; + /* + Our attempt to acquire lock without waiting has failed. + As a result of this attempt we got MDL_ticket with m_lock + member pointing to the corresponding MDL_lock object which + has MDL_lock::m_rwlock write-locked. + */ + lock= ticket->m_lock; - /* The below call implicitly locks MDL_lock::m_rwlock on success. */ - if (!(lock= mdl_locks.find_or_insert(key))) - { - MDL_ticket::destroy(ticket); - return TRUE; - } + lock->m_waiting.add_ticket(ticket); - ticket->m_lock= lock; + /* + Once we added a pending ticket to the waiting queue, + we must ensure that our wait slot is empty, so + that our lock request can be scheduled. Do that in the + critical section formed by the acquired write lock on MDL_lock. + */ + m_wait.reset_status(); - lock->m_waiting.add_ticket(ticket); + if (ticket->is_upgradable_or_exclusive()) + lock->notify_shared_locks(this); - while (!lock->can_grant_lock(mdl_request->type, this)) - { - wait_reset(); + mysql_prlock_unlock(&lock->m_rwlock); - if (ticket->is_upgradable_or_exclusive()) - lock->notify_shared_locks(this); + will_wait_for(ticket); - mysql_prlock_unlock(&lock->m_rwlock); + /* There is a shared or exclusive lock on the object. */ + DEBUG_SYNC(m_thd, "mdl_acquire_lock_wait"); - set_deadlock_weight(mdl_request->get_deadlock_weight()); - will_wait_for(ticket); + find_deadlock(); - /* There is a shared or exclusive lock on the object. */ - DEBUG_SYNC(m_thd, "mdl_acquire_lock_wait"); + if (ticket->is_upgradable_or_exclusive()) + { + struct timespec abs_shortwait; + set_timespec(abs_shortwait, 1); + wait_status= MDL_wait::EMPTY; - bool is_deadlock= find_deadlock(); - bool is_timeout= FALSE; - if (!is_deadlock) + while (cmp_timespec(abs_shortwait, abs_timeout) <= 0) { + /* abs_timeout is far away. Wait a short while and notify locks. */ + wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE); + + if (wait_status != MDL_wait::EMPTY) + break; + + mysql_prlock_wrlock(&lock->m_rwlock); + lock->notify_shared_locks(this); + mysql_prlock_unlock(&lock->m_rwlock); set_timespec(abs_shortwait, 1); - bool timeout_is_near= cmp_timespec(abs_shortwait, abs_timeout) > 0; - mdl_signal_type wait_result= - timed_wait(timeout_is_near ? &abs_timeout : &abs_shortwait); - - if (timeout_is_near && wait_result == TIMEOUT_WAKE_UP) - is_timeout= TRUE; - else if (wait_result == VICTIM_WAKE_UP) - is_deadlock= TRUE; } + if (wait_status == MDL_wait::EMPTY) + wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE); + } + else + wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE); - stop_waiting(); + done_waiting_for(); - if (mysys_var->abort || is_deadlock || is_timeout) + if (wait_status != MDL_wait::GRANTED) + { + lock->remove_ticket(&MDL_lock::m_waiting, ticket); + MDL_ticket::destroy(ticket); + switch (wait_status) { - lock->remove_ticket(&MDL_lock::m_waiting, ticket); - MDL_ticket::destroy(ticket); - if (is_deadlock) - my_error(ER_LOCK_DEADLOCK, MYF(0)); - else if (is_timeout) - my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); - return TRUE; + case MDL_wait::VICTIM: + my_error(ER_LOCK_DEADLOCK, MYF(0)); + break; + case MDL_wait::TIMEOUT: + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + break; + case MDL_wait::KILLED: + break; + default: + DBUG_ASSERT(0); + break; } - mysql_prlock_wrlock(&lock->m_rwlock); + return TRUE; } - lock->m_waiting.remove_ticket(ticket); - lock->m_granted.add_ticket(ticket); - - if (ticket->get_type() == MDL_EXCLUSIVE && lock->cached_object) - (*lock->cached_object_release_hook)(lock->cached_object); - lock->cached_object= NULL; - - mysql_prlock_unlock(&lock->m_rwlock); + /* + We have been granted our request. + State of MDL_lock object is already being appropriately updated by a + concurrent thread (@sa MDL_lock:reschedule_waiters()). + So all we need to do is to update MDL_context and MDL_request objects. + */ + DBUG_ASSERT(wait_status == MDL_wait::GRANTED); m_tickets.push_front(ticket); @@ -1652,7 +1932,7 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests, for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++) { - if (acquire_lock_impl(*p_req, lock_wait_timeout)) + if (acquire_lock(*p_req, lock_wait_timeout)) goto err; } my_free(sort_buf, MYF(0)); @@ -1689,9 +1969,8 @@ err: shared mode). @note There can be only one upgrader for a lock or we will have deadlock. - This invariant is ensured by code outside of metadata subsystem usually - by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, - TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. + This invariant is ensured by the fact that upgradeable locks SNW + and SNRW are not compatible with each other and themselves. @retval FALSE Success @retval TRUE Failure (thread was killed) @@ -1721,7 +2000,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE); - if (acquire_lock_impl(&mdl_xlock_request, lock_wait_timeout)) + if (acquire_lock(&mdl_xlock_request, lock_wait_timeout)) DBUG_RETURN(TRUE); is_new_ticket= ! has_lock(mdl_svp, mdl_xlock_request.ticket); @@ -1752,251 +2031,231 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket, - Deadlock_detection_context *deadlock_ctx) + Deadlock_detection_visitor *dvisitor) { MDL_ticket *ticket; - bool result= FALSE; + MDL_context *src_ctx= waiting_ticket->get_ctx(); + bool result= TRUE; mysql_prlock_rdlock(&m_rwlock); + /* Must be initialized after taking a read lock. */ Ticket_iterator granted_it(m_granted); Ticket_iterator waiting_it(m_waiting); + /* + MDL_lock's waiting and granted queues and MDL_context::m_waiting_for + member are updated by different threads when the lock is granted + (see MDL_context::acquire_lock() and MDL_lock::reschedule_waiters()). + As a result, here we may encounter a situation when MDL_lock data + already reflects the fact that the lock was granted but + m_waiting_for member has not been updated yet. + + For example, imagine that: + + thread1: Owns SNW lock on table t1. + thread2: Attempts to acquire SW lock on t1, + but sees an active SNW lock. + Thus adds the ticket to the waiting queue and + sets m_waiting_for to point to the ticket. + thread1: Releases SNW lock, updates MDL_lock object to + grant SW lock to thread2 (moves the ticket for + SW from waiting to the active queue). + Attempts to acquire a new SNW lock on t1, + sees an active SW lock (since it is present in the + active queue), adds ticket for SNW lock to the waiting + queue, sets m_waiting_for to point to this ticket. + + At this point deadlock detection algorithm run by thread1 will see that: + - Thread1 waits for SNW lock on t1 (since m_waiting_for is set). + - SNW lock is not granted, because it conflicts with active SW lock + owned by thread 2 (since ticket for SW is present in granted queue). + - Thread2 waits for SW lock (since its m_waiting_for has not been + updated yet!). + - SW lock is not granted because there is pending SNW lock from thread1. + Therefore deadlock should exist [sic!]. + + To avoid detection of such false deadlocks we need to check the "actual" + status of the ticket being waited for, before analyzing its blockers. + We do this by checking the wait status of the context which is waiting + for it. To avoid races this has to be done under protection of + MDL_lock::m_rwlock lock. + */ + if (src_ctx->m_wait.get_status() != MDL_wait::EMPTY) + { + result= FALSE; + goto end; + } + + /* + To avoid visiting nodes which were already marked as victims of + deadlock detection (or whose requests were already satisfied) we + enter the node only after peeking at its wait status. + This is necessary to avoid active waiting in a situation + when previous searches for a deadlock already selected the + node we're about to enter as a victim (see the comment + in MDL_context::find_deadlock() for explanation why several searches + can be performed for the same wait). + There is no guarantee that the node isn't chosen a victim while we + are visiting it but this is OK: in the worst case we might do some + extra work and one more context might be chosen as a victim. + */ + if (dvisitor->enter_node(src_ctx)) + goto end; + + /* + We do a breadth-first search first -- that is, inspect all + edges of the current node, and only then follow up to the next + node. In workloads that involve wait-for graph loops this + has proven to be a more efficient strategy [citation missing]. + */ while ((ticket= granted_it++)) { - if (ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && - ticket->get_ctx() != waiting_ticket->get_ctx() && - ticket->get_ctx() == deadlock_ctx->start) + /* Filter out edges that point to the same node. */ + if (ticket->get_ctx() != src_ctx && + ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && + dvisitor->inspect_edge(ticket->get_ctx())) { - result= TRUE; - goto end; + goto end_leave_node; } } while ((ticket= waiting_it++)) { - if (ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) && - ticket->get_ctx() != waiting_ticket->get_ctx() && - ticket->get_ctx() == deadlock_ctx->start) + /* Filter out edges that point to the same node. */ + if (ticket->get_ctx() != src_ctx && + ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) && + dvisitor->inspect_edge(ticket->get_ctx())) { - result= TRUE; - goto end; + goto end_leave_node; } } + /* Recurse and inspect all adjacent nodes. */ granted_it.rewind(); while ((ticket= granted_it++)) { - if (ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && - ticket->get_ctx() != waiting_ticket->get_ctx() && - ticket->get_ctx()->find_deadlock(deadlock_ctx)) + if (ticket->get_ctx() != src_ctx && + ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && + ticket->get_ctx()->find_deadlock(dvisitor)) { - result= TRUE; - goto end; + goto end_leave_node; } } waiting_it.rewind(); while ((ticket= waiting_it++)) { - if (ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) && - ticket->get_ctx() != waiting_ticket->get_ctx() && - ticket->get_ctx()->find_deadlock(deadlock_ctx)) + if (ticket->get_ctx() != src_ctx && + ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) && + ticket->get_ctx()->find_deadlock(dvisitor)) { - result= TRUE; - goto end; + goto end_leave_node; } } + result= FALSE; + +end_leave_node: + dvisitor->leave_node(src_ctx); + end: mysql_prlock_unlock(&m_rwlock); return result; } -bool MDL_context::find_deadlock(Deadlock_detection_context *deadlock_ctx) +/** + Recursively traverse the wait-for graph of MDL contexts + in search for deadlocks. + + @retval TRUE A deadlock is found. A victim is remembered + by the visitor. + @retval FALSE +*/ + +bool MDL_context::find_deadlock(Deadlock_detection_visitor *dvisitor) { + MDL_context *m_unlock_ctx= this; bool result= FALSE; - mysql_prlock_rdlock(&m_waiting_for_lock); + mysql_prlock_rdlock(&m_LOCK_waiting_for); if (m_waiting_for) { - /* - QQ: should we rather be checking for NO_WAKE_UP ? - - We want to do check signal only when m_waiting_for is set - to avoid reading left-overs from previous kills. - */ - if (peek_signal() != VICTIM_WAKE_UP) - { - - if (++deadlock_ctx->current_search_depth > - deadlock_ctx->MAX_SEARCH_DEPTH) - result= TRUE; - else - result= m_waiting_for->m_lock->find_deadlock(m_waiting_for, - deadlock_ctx); - --deadlock_ctx->current_search_depth; - } - } - - if (result) - { - if (! deadlock_ctx->victim) - deadlock_ctx->victim= this; - else if (deadlock_ctx->victim->m_deadlock_weight >= m_deadlock_weight) - { - mysql_prlock_unlock(&deadlock_ctx->victim->m_waiting_for_lock); - deadlock_ctx->victim= this; - } - else - mysql_prlock_unlock(&m_waiting_for_lock); + result= m_waiting_for->m_lock->find_deadlock(m_waiting_for, dvisitor); + if (result) + m_unlock_ctx= dvisitor->opt_change_victim_to(this); } - else - mysql_prlock_unlock(&m_waiting_for_lock); + /* + We may recurse into the same MDL_context more than once + in case this is not the starting node. Make sure we release the + read lock as it's been taken, except for 1 read lock for + the deadlock victim. + */ + if (m_unlock_ctx) + mysql_prlock_unlock(&m_unlock_ctx->m_LOCK_waiting_for); return result; } -bool MDL_context::find_deadlock() -{ - while (1) - { - /* - The fact that we use fresh instance of deadlock_ctx for each - search performed by find_deadlock() below is important, code - responsible for victim selection relies on this. - */ - Deadlock_detection_context deadlock_ctx(this); - - if (! find_deadlock(&deadlock_ctx)) - { - /* No deadlocks are found! */ - break; - } - - if (deadlock_ctx.victim != this) - { - deadlock_ctx.victim->awake(VICTIM_WAKE_UP); - mysql_prlock_unlock(&deadlock_ctx.victim->m_waiting_for_lock); - /* - After adding new arc to waiting graph we found that it participates - in some loop (i.e. there is a deadlock). We decided to destroy this - loop by removing some arc other than newly added. Since this doesn't - guarantee that all loops created by addition of this arc are - destroyed we have to repeat search. - */ - continue; - } - else - { - DBUG_ASSERT(&deadlock_ctx.victim->m_waiting_for_lock == &m_waiting_for_lock); - mysql_prlock_unlock(&deadlock_ctx.victim->m_waiting_for_lock); - return TRUE; - } - } - return FALSE; -} - - /** - Wait until there will be no locks that conflict with lock requests - in the given list. - - This is a part of the locking protocol and must be used by the - acquirer of shared locks after a back-off. - - Does not acquire the locks! + Try to find a deadlock. This function produces no errors. - @param lock_wait_timeout Seconds to wait before timeout. + @note If during deadlock resolution context which performs deadlock + detection is chosen as a victim it will be informed about the + fact by setting VICTIM status to its wait slot. - @retval FALSE Success. One can try to obtain metadata locks. - @retval TRUE Failure (thread was killed or deadlock is possible). + @retval TRUE A deadlock is found. + @retval FALSE No deadlock found. */ -bool -MDL_context::wait_for_lock(MDL_request *mdl_request, ulong lock_wait_timeout) +void MDL_context::find_deadlock() { - MDL_lock *lock; - st_my_thread_var *mysys_var= my_thread_var; - struct timespec abs_timeout; - set_timespec(abs_timeout, lock_wait_timeout); - - mysql_mutex_assert_not_owner(&LOCK_open); - - DBUG_ASSERT(mdl_request->ticket == NULL); - - while (TRUE) + while (1) { /* - We have to check if there are some HANDLERs open by this thread - which conflict with some pending exclusive locks. Otherwise we - might have a deadlock in situations when we are waiting for - pending writer to go away, which in its turn waits for HANDLER - open by our thread. - - TODO: investigate situations in which we need to broadcast on - COND_mdl because of above scenario. + The fact that we use fresh instance of dvisitor for each + search performed by find_deadlock() below is important, + the code responsible for victim selection relies on this. */ - mysql_ha_flush(m_thd); - - MDL_key *key= &mdl_request->key; - - /* The below call implicitly locks MDL_lock::m_rwlock on success. */ - if (! (lock= mdl_locks.find(key))) - return FALSE; - - if (lock->can_grant_lock(mdl_request->type, this)) - { - mysql_prlock_unlock(&lock->m_rwlock); - return FALSE; - } + Deadlock_detection_visitor dvisitor(this); + MDL_context *victim; - MDL_ticket *pending_ticket; - if (! (pending_ticket= MDL_ticket::create(this, mdl_request->type))) + if (! find_deadlock(&dvisitor)) { - mysql_prlock_unlock(&lock->m_rwlock); - return TRUE; - } - - pending_ticket->m_lock= lock; - - lock->m_waiting.add_ticket(pending_ticket); - - wait_reset(); - mysql_prlock_unlock(&lock->m_rwlock); - - set_deadlock_weight(MDL_DEADLOCK_WEIGHT_DML); - will_wait_for(pending_ticket); - - bool is_deadlock= find_deadlock(); - bool is_timeout= FALSE; - if (!is_deadlock) - { - mdl_signal_type wait_result= timed_wait(&abs_timeout); - if (wait_result == TIMEOUT_WAKE_UP) - is_timeout= TRUE; - else if (wait_result == VICTIM_WAKE_UP) - is_deadlock= TRUE; + /* No deadlocks are found! */ + break; } - stop_waiting(); + victim= dvisitor.get_victim(); - lock->remove_ticket(&MDL_lock::m_waiting, pending_ticket); - MDL_ticket::destroy(pending_ticket); + /* + Failure to change status of the victim is OK as it means + that the victim has received some other message and is + about to stop its waiting/to break deadlock loop. + Even when the initiator of the deadlock search is + chosen the victim, we need to set the respective wait + result in order to "close" it for any attempt to + schedule the request. + This is needed to avoid a possible race during + cleanup in case when the lock request on which the + context was waiting is concurrently satisfied. + */ + (void) victim->m_wait.set_status(MDL_wait::VICTIM); + mysql_prlock_unlock(&victim->m_LOCK_waiting_for); - if (mysys_var->abort || is_deadlock || is_timeout) - { - if (is_deadlock) - my_error(ER_LOCK_DEADLOCK, MYF(0)); - else if (is_timeout) - my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); - return TRUE; - } + if (victim == this) + break; + /* + After adding a new edge to the waiting graph we found that it + creates a loop (i.e. there is a deadlock). We decided to destroy + this loop by removing an edge, but not the one that we added. + Since this doesn't guarantee that all loops created by addition + of the new edge are destroyed, we have to repeat the search. + */ } - return TRUE; } @@ -2125,7 +2384,7 @@ void MDL_ticket::downgrade_exclusive_lock(enum_mdl_type type) m_lock->m_granted.remove_ticket(this); m_type= type; m_lock->m_granted.add_ticket(this); - m_lock->wake_up_waiters(); + m_lock->reschedule_waiters(); mysql_prlock_unlock(&m_lock->m_rwlock); } @@ -2330,7 +2589,6 @@ void MDL_context::move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket) if (m_trans_sentinel == NULL) { m_trans_sentinel= mdl_ticket; - /* sic: linear from the number of transactional tickets acquired so-far! */ m_tickets.push_back(mdl_ticket); } else diff --git a/sql/mdl.h b/sql/mdl.h index 89a679be264..43d88c143c0 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -34,7 +34,7 @@ class THD; class MDL_context; class MDL_lock; class MDL_ticket; -class Deadlock_detection_context; +class Deadlock_detection_visitor; /** Type of metadata lock request. @@ -308,6 +308,10 @@ public: MDL_key key; public: + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () + { return alloc_root(mem_root, size); } + static void operator delete(void *ptr, MEM_ROOT *mem_root) {} + void init(MDL_key::enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg, enum_mdl_type mdl_type_arg); @@ -318,8 +322,6 @@ public: DBUG_ASSERT(ticket == NULL); type= type_arg; } - uint get_deadlock_weight() const; - static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, const char *name, enum_mdl_type mdl_type, MEM_ROOT *root); @@ -413,6 +415,8 @@ public: bool is_incompatible_when_granted(enum_mdl_type type) const; bool is_incompatible_when_waiting(enum_mdl_type type) const; + /* A helper used to determine which lock request should be aborted. */ + uint get_deadlock_weight() const; private: friend class MDL_context; @@ -443,6 +447,37 @@ private: }; +/** + A reliable way to wait on an MDL lock. +*/ + +class MDL_wait +{ +public: + MDL_wait(); + ~MDL_wait(); + + enum enum_wait_status { EMPTY = 0, GRANTED, VICTIM, TIMEOUT, KILLED }; + + bool set_status(enum_wait_status result_arg); + enum_wait_status get_status(); + void reset_status(); + enum_wait_status timed_wait(THD *thd, struct timespec *abs_timeout, + bool signal_timeout); +private: + /** + Condvar which is used for waiting until this context's pending + request can be satisfied or this thread has to perform actions + to resolve a potential deadlock (we subscribe to such + notification by adding a ticket corresponding to the request + to an appropriate queue of waiters). + */ + mysql_mutex_t m_LOCK_wait_status; + mysql_cond_t m_COND_wait_status; + enum_wait_status m_wait_status; +}; + + typedef I_P_List<MDL_request, I_P_List_adapter<MDL_request, &MDL_request::next_in_list, &MDL_request::prev_in_list>, @@ -460,16 +495,13 @@ public: typedef I_P_List<MDL_ticket, I_P_List_adapter<MDL_ticket, &MDL_ticket::next_in_context, - &MDL_ticket::prev_in_context> > + &MDL_ticket::prev_in_context>, + I_P_List_null_counter, + I_P_List_fast_push_back<MDL_ticket> > Ticket_list; typedef Ticket_list::Iterator Ticket_iterator; - enum mdl_signal_type { NO_WAKE_UP = 0, - NORMAL_WAKE_UP, - VICTIM_WAKE_UP, - TIMEOUT_WAKE_UP }; - MDL_context(); void destroy(); @@ -481,8 +513,6 @@ public: bool clone_ticket(MDL_request *mdl_request); - bool wait_for_lock(MDL_request *mdl_request, ulong lock_wait_timeout); - void release_all_locks_for_name(MDL_ticket *ticket); void release_lock(MDL_ticket *ticket); @@ -524,17 +554,17 @@ public: inline THD *get_thd() const { return m_thd; } + /** @pre Only valid if we started waiting for lock. */ + inline uint get_deadlock_weight() const + { return m_waiting_for->get_deadlock_weight(); } /** - Wake up context which is waiting for a change of MDL_lock state. - */ - void awake(mdl_signal_type signal) - { - mysql_mutex_lock(&m_signal_lock); - m_signal= signal; - mysql_cond_signal(&m_signal_cond); - mysql_mutex_unlock(&m_signal_lock); - } + Post signal to the context (and wake it up if necessary). + @retval FALSE - Success, signal was posted. + @retval TRUE - Failure, signal was not posted since context + already has received some signal or closed + signal slot. + */ void init(THD *thd_arg) { m_thd= thd_arg; } void set_needs_thr_lock_abort(bool needs_thr_lock_abort) @@ -554,7 +584,13 @@ public: return m_needs_thr_lock_abort; } - bool find_deadlock(Deadlock_detection_context *deadlock_ctx); + bool find_deadlock(Deadlock_detection_visitor *dvisitor); +public: + /** + If our request for a lock is scheduled, or aborted by the deadlock + detector, the result is recorded in this class. + */ + MDL_wait m_wait; private: /** All MDL tickets acquired by this connection. @@ -636,71 +672,38 @@ private: important as deadlock detector won't work correctly otherwise. @sa Comment for MDL_lock::m_rwlock. */ - mysql_prlock_t m_waiting_for_lock; - MDL_ticket *m_waiting_for; - uint m_deadlock_weight; + mysql_prlock_t m_LOCK_waiting_for; /** - Condvar which is used for waiting until this context's pending - request can be satisfied or this thread has to perform actions - to resolve a potential deadlock (we subscribe to such - notification by adding a ticket corresponding to the request - to an appropriate queue of waiters). - */ - mysql_mutex_t m_signal_lock; - mysql_cond_t m_signal_cond; - mdl_signal_type m_signal; - + Tell the deadlock detector what lock this session is waiting for. + In principle, this is redundant, as information can be found + by inspecting waiting queues, but we'd very much like it to be + readily available to the wait-for graph iterator. + */ + MDL_ticket *m_waiting_for; private: MDL_ticket *find_ticket(MDL_request *mdl_req, bool *is_transactional); void release_locks_stored_before(MDL_ticket *sentinel); - bool acquire_lock_impl(MDL_request *mdl_request, ulong lock_wait_timeout); + bool try_acquire_lock_impl(MDL_request *mdl_request, + MDL_ticket **out_ticket); - bool find_deadlock(); + void find_deadlock(); + /** Inform the deadlock detector there is an edge in the wait-for graph. */ void will_wait_for(MDL_ticket *pending_ticket) { - mysql_prlock_wrlock(&m_waiting_for_lock); + mysql_prlock_wrlock(&m_LOCK_waiting_for); m_waiting_for= pending_ticket; - mysql_prlock_unlock(&m_waiting_for_lock); + mysql_prlock_unlock(&m_LOCK_waiting_for); } - void set_deadlock_weight(uint weight) + /** Remove the wait-for edge from the graph after we're done waiting. */ + void done_waiting_for() { - /* - m_deadlock_weight should not be modified while m_waiting_for is - non-NULL as in this case this context might participate in deadlock - and so m_deadlock_weight can be accessed from other threads. - */ - DBUG_ASSERT(m_waiting_for == NULL); - m_deadlock_weight= weight; - } - - void stop_waiting() - { - mysql_prlock_wrlock(&m_waiting_for_lock); + mysql_prlock_wrlock(&m_LOCK_waiting_for); m_waiting_for= NULL; - mysql_prlock_unlock(&m_waiting_for_lock); + mysql_prlock_unlock(&m_LOCK_waiting_for); } - - void wait_reset() - { - mysql_mutex_lock(&m_signal_lock); - m_signal= NO_WAKE_UP; - mysql_mutex_unlock(&m_signal_lock); - } - - mdl_signal_type timed_wait(struct timespec *abs_timeout); - - mdl_signal_type peek_signal() - { - mdl_signal_type result; - mysql_mutex_lock(&m_signal_lock); - result= m_signal; - mysql_mutex_unlock(&m_signal_lock); - return result; - } - private: MDL_context(const MDL_context &rhs); /* not implemented */ MDL_context &operator=(MDL_context &rhs); /* not implemented */ @@ -717,7 +720,6 @@ void mdl_destroy(); extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, bool needs_thr_lock_abort); -extern void mysql_ha_flush(THD *thd); extern "C" const char *set_thd_proc_info(void *thd_arg, const char *info, const char *calling_function, const char *calling_file, diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index cddf798aac0..81366d55fc6 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -102,7 +102,6 @@ static int init_failsafe_rpl_thread(THD* thd) thd->mem_root->free= thd->mem_root->used= 0; thd_proc_info(thd, "Thread initialized"); - thd->version=refresh_version; thd->set_time(); DBUG_RETURN(0); } diff --git a/sql/slave.cc b/sql/slave.cc index 6ebdea4a42a..bcb01d77e15 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -2055,7 +2055,6 @@ static int init_slave_thread(THD* thd, SLAVE_THD_TYPE thd_type) thd_proc_info(thd, "Waiting for the next event in relay log"); else thd_proc_info(thd, "Waiting for master update"); - thd->version=refresh_version; thd->set_time(); /* Do not use user-supplied timeout value for system threads. */ thd->variables.lock_wait_timeout= LONG_TIMEOUT; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 1e708bcb083..f75acf11984 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -4028,6 +4028,11 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; + /* + Since we don't allow DDL on base tables in prelocked mode it + is safe to infer the type of metadata lock from the type of + table lock. + */ table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, table->lock_type >= TL_WRITE_ALLOW_WRITE ? MDL_SHARED_WRITE : MDL_SHARED_READ); @@ -4057,8 +4062,9 @@ sp_head::add_used_tables_to_table_list(THD *thd, TABLE_LIST * sp_add_to_query_tables(THD *thd, LEX *lex, - const char *db, const char *name, - thr_lock_type locktype) + const char *db, const char *name, + thr_lock_type locktype, + enum_mdl_type mdl_type) { TABLE_LIST *table; @@ -4073,8 +4079,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->select_lex= lex->current_select; table->cacheable_table= 1; table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, - table->lock_type >= TL_WRITE_ALLOW_WRITE ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + mdl_type); lex->add_to_query_tables(table); return table; diff --git a/sql/sp_head.h b/sql/sp_head.h index 539a2da5f8c..9796c49fdfb 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -1342,7 +1342,9 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, TABLE_LIST * sp_add_to_query_tables(THD *thd, LEX *lex, const char *db, const char *name, - thr_lock_type locktype); + thr_lock_type locktype, + enum_mdl_type mdl_type); + Item * sp_prepare_func_item(THD* thd, Item **it_addr); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 325f054db02..78862985e97 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -16,18 +16,18 @@ /* Basic functions needed by many modules */ +#include "sql_base.h" // setup_table_map #include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" #include "unireg.h" #include "debug_sync.h" -#include "sql_base.h" // setup_table_map #include "lock.h" // broadcast_refresh, mysql_lock_remove, // mysql_unlock_tables, // mysql_lock_have_duplicate #include "sql_show.h" // append_identifier #include "strfunc.h" // find_type #include "parse_file.h" // sql_parse_prepare, File_parser -#include "sql_view.h" // mysql_frm_type, mysql_make_view, VIEW_ANY_ACL +#include "sql_view.h" // mysql_make_view, VIEW_ANY_ACL #include "sql_parse.h" // check_table_access #include "sql_insert.h" // kill_delayed_threads #include "sql_acl.h" // *_ACL, check_grant_all_columns, @@ -52,6 +52,7 @@ #include <hash.h> #include "rpl_filter.h" #include "sql_table.h" // build_table_filename +#include "datadict.h" // dd_frm_type() #ifdef __WIN__ #include <io.h> #endif @@ -438,6 +439,9 @@ static void table_def_unuse_table(TABLE *table) { DBUG_ASSERT(table->in_use); + /* We shouldn't put the table to 'unused' list if the share is old. */ + DBUG_ASSERT(! table->s->needs_reopen()); + table->in_use= 0; /* Remove table from the list of tables used in this share. */ table->s->used_tables.remove(table); @@ -558,7 +562,9 @@ found: DBUG_RETURN(0); } - if (!share->ref_count++ && share->prev) + ++share->ref_count; + + if (share->ref_count == 1 && share->prev) { /* Share was not used before and it was in the old_unused_share list @@ -685,7 +691,6 @@ static TABLE_SHARE void release_table_share(TABLE_SHARE *share) { - bool to_be_deleted= 0; DBUG_ENTER("release_table_share"); DBUG_PRINT("enter", ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", @@ -697,9 +702,8 @@ void release_table_share(TABLE_SHARE *share) DBUG_ASSERT(share->ref_count); if (!--share->ref_count) { - if (share->version != refresh_version || - table_def_shutdown_in_progress) - to_be_deleted=1; + if (share->needs_reopen() || table_def_shutdown_in_progress) + my_hash_delete(&table_def_cache, (uchar*) share); else { /* Link share last in used_table_share list */ @@ -711,15 +715,14 @@ void release_table_share(TABLE_SHARE *share) end_of_unused_share.prev= &share->next; share->next= &end_of_unused_share; - to_be_deleted= (table_def_cache.records > table_def_size); + if (table_def_cache.records > table_def_size) + { + /* Delete the least used share to preserve LRU order. */ + my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); + } } } - if (to_be_deleted) - { - DBUG_PRINT("info", ("Deleting share")); - my_hash_delete(&table_def_cache, (uchar*) share); - } DBUG_VOID_RETURN; } @@ -834,7 +837,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); while (it++) ++(*start_list)->in_use; - (*start_list)->locked= (share->version == 0) ? 1 : 0; + (*start_list)->locked= 0; /* Obsolete. */ start_list= &(*start_list)->next; *start_list=0; } @@ -1023,6 +1026,9 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, TABLE_LIST *tables_to_reopen= (tables ? tables : thd->locked_tables_list.locked_tables()); + /* Close open HANLER instances to avoid self-deadlock. */ + mysql_ha_flush_tables(thd, tables_to_reopen); + for (TABLE_LIST *table_list= tables_to_reopen; table_list; table_list= table_list->next_global) { @@ -1050,7 +1056,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, { found= FALSE; /* - To avoid self and other kinds of deadlock we have to flush open HANDLERs. + To a self-deadlock or deadlocks with other FLUSH threads + waiting on our open HANDLERs, we have to flush them. */ mysql_ha_flush(thd); DEBUG_SYNC(thd, "after_flush_unlock"); @@ -1065,7 +1072,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, { TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache, idx); - if (share->version != refresh_version) + if (share->needs_reopen()) { found= TRUE; break; @@ -1077,7 +1084,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, for (TABLE_LIST *table= tables; table; table= table->next_local) { TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); - if (share && share->version != refresh_version) + if (share && share->needs_reopen()) { found= TRUE; break; @@ -1295,29 +1302,21 @@ static void close_open_tables(THD *thd) mysql_mutex_assert_not_owner(&LOCK_open); - mysql_mutex_lock(&LOCK_open); - DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables)); while (thd->open_tables) found_old_table|= close_thread_table(thd, &thd->open_tables); - /* Free tables to hold down open files */ - while (table_cache_count > table_cache_size && unused_tables) - free_cache_entry(unused_tables); if (found_old_table) { /* Tell threads waiting for refresh that something has happened */ broadcast_refresh(); } - - mysql_mutex_unlock(&LOCK_open); } /** - Close all open instances of the table but keep the MDL lock, - if any. + Close all open instances of the table but keep the MDL lock. Works both under LOCK TABLES and in the normal mode. Removes all closed instances of the table from the table cache. @@ -1331,6 +1330,8 @@ static void close_open_tables(THD *thd) In that case the documented behaviour is to implicitly remove the table from LOCK TABLES list. + + @pre Must be called with an X MDL lock on the table. */ void @@ -1339,17 +1340,12 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, { char key[MAX_DBKEY_LENGTH]; uint key_length= share->table_cache_key.length; + const char *db= key; + const char *table_name= db + share->db.length + 1; memcpy(key, share->table_cache_key.str, key_length); mysql_mutex_assert_not_owner(&LOCK_open); - /* - We need to hold LOCK_open while changing the open_tables - list, since another thread may work on it. - @sa mysql_notify_thread_having_shared_lock() - */ - mysql_mutex_lock(&LOCK_open); - for (TABLE **prev= &thd->open_tables; *prev; ) { TABLE *table= *prev; @@ -1357,10 +1353,9 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) { - /* Inform handler that table will be dropped after close */ - if (table->db_stat) - table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); - + thd->locked_tables_list.unlink_from_list(thd, + table->pos_in_locked_tables, + remove_from_locked_tables); /* Does nothing if the table is not locked. This allows one to use this function after a table @@ -1368,12 +1363,9 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, */ mysql_lock_remove(thd, thd->lock, table); - thd->locked_tables_list.unlink_from_list(thd, - table->pos_in_locked_tables, - remove_from_locked_tables); - - /* Make sure the table is removed from the cache */ - table->s->version= 0; + /* Inform handler that table will be dropped after close */ + if (table->db_stat) /* Not true for partitioned tables. */ + table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); close_thread_table(thd, prev); } else @@ -1382,9 +1374,15 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, prev= &table->next; } } - /* We have been removing tables from the table cache. */ - broadcast_refresh(); + /* Remove the table share from the cache. */ + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name); mysql_mutex_unlock(&LOCK_open); + /* + There could be a FLUSH thread waiting + on the table to go away. Wake it up. + */ + broadcast_refresh(); } @@ -1583,30 +1581,43 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) DBUG_ENTER("close_thread_table"); DBUG_ASSERT(table->key_read == 0); DBUG_ASSERT(!table->file || table->file->inited == handler::NONE); - mysql_mutex_assert_owner(&LOCK_open); + mysql_mutex_assert_not_owner(&LOCK_open); + + table->mdl_ticket= NULL; + mysql_mutex_lock(&thd->LOCK_thd_data); *table_ptr=table->next; + mysql_mutex_unlock(&thd->LOCK_thd_data); - table->mdl_ticket= NULL; - if (table->s->needs_reopen() || - thd->version != refresh_version || table->needs_reopen() || + if (! table->needs_reopen()) + { + /* Avoid having MERGE tables with attached children in unused_tables. */ + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + /* Free memory and reset for next loop. */ + free_field_buffers_larger_than(table, MAX_TDC_BLOB_SIZE); + table->file->ha_reset(); + } + + mysql_mutex_lock(&LOCK_open); + + if (table->s->needs_reopen() || table->needs_reopen() || table_def_shutdown_in_progress) { free_cache_entry(table); - found_old_table=1; + found_old_table= 1; } else { - /* Avoid to have MERGE tables with attached children in unused_tables. */ DBUG_ASSERT(table->file); - table->file->extra(HA_EXTRA_DETACH_CHILDREN); - - /* Free memory and reset for next loop */ - free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE); - - table->file->ha_reset(); table_def_unuse_table(table); + /* + We free the least used table, not the subject table, + to keep the LRU order. + */ + if (table_cache_count > table_cache_size) + free_cache_entry(unused_tables); } + mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(found_old_table); } @@ -2227,14 +2238,15 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, DBUG_ASSERT(table == thd->open_tables); handlerton *table_type= table->s->db_type(); - /* Ensure the table is removed from the cache. */ - table->s->version= 0; - mysql_mutex_lock(&LOCK_open); table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); close_thread_table(thd, &thd->open_tables); - quick_rm_table(table_type, db_name, table_name, 0); + /* Remove the table share from the table cache. */ + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name); mysql_mutex_unlock(&LOCK_open); + /* Remove the table from the storage engine and rm the .frm. */ + quick_rm_table(table_type, db_name, table_name, 0); } DBUG_VOID_RETURN; } @@ -2294,7 +2306,7 @@ void wait_for_condition(THD *thd, mysql_mutex_t *mutex, mysql_cond_t *cond) @param[out] exists Out parameter which is set to TRUE if table exists and to FALSE otherwise. - @note This function assumes that caller owns LOCK_open mutex. + @note This function acquires LOCK_open internally. It also assumes that the fact that there are no exclusive metadata locks on the table was checked beforehand. @@ -2302,28 +2314,30 @@ void wait_for_condition(THD *thd, mysql_mutex_t *mutex, mysql_cond_t *cond) of engines (e.g. it was created on another node of NDB cluster) this function will fetch and create proper .FRM file for it. - @retval TRUE Some error occured + @retval TRUE Some error occurred @retval FALSE No error. 'exists' out parameter set accordingly. */ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) { char path[FN_REFLEN + 1]; - int rc; + int rc= 0; DBUG_ENTER("check_if_table_exists"); - mysql_mutex_assert_owner(&LOCK_open); + mysql_mutex_assert_not_owner(&LOCK_open); *exists= TRUE; + mysql_mutex_lock(&LOCK_open); + if (get_cached_table_share(table->db, table->table_name)) - DBUG_RETURN(FALSE); + goto end; build_table_filename(path, sizeof(path) - 1, table->db, table->table_name, reg_ext, 0); if (!access(path, F_OK)) - DBUG_RETURN(FALSE); + goto end; /* .FRM file doesn't exist. Check if some engine can provide it. */ @@ -2333,19 +2347,17 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) { /* Table does not exists in engines as well. */ *exists= FALSE; - DBUG_RETURN(FALSE); - } - else if (!rc) - { - /* Table exists in some engine and .FRM for it was created. */ - DBUG_RETURN(FALSE); + rc= 0; } - else /* (rc > 0) */ + else if (rc) { my_printf_error(ER_UNKNOWN_ERROR, "Failed to open '%-.64s', error while " "unpacking from engine", MYF(0), table->table_name); - DBUG_RETURN(TRUE); } + +end: + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(test(rc)); } @@ -2364,82 +2376,192 @@ void table_share_release_hook(void *share) /** - A helper function that acquires an MDL lock for a table - being opened. + An error handler which converts, if possible, ER_LOCK_DEADLOCK error + that can occur when we are trying to acquire a metadata lock to + a request for back-off and re-start of open_tables() process. */ -static bool -open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, - MDL_request *mdl_request, - Open_table_context *ot_ctx, - uint flags) +class MDL_deadlock_handler : public Internal_error_handler +{ +public: + MDL_deadlock_handler(Open_table_context *ot_ctx_arg) + : m_ot_ctx(ot_ctx_arg), m_is_active(FALSE) + {} + + virtual ~MDL_deadlock_handler() {} + + virtual bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + MYSQL_ERROR::enum_warning_level level, + const char* msg, + MYSQL_ERROR ** cond_hdl); + +private: + /** Open table context to be used for back-off request. */ + Open_table_context *m_ot_ctx; + /** + Indicates that we are already in the process of handling + ER_LOCK_DEADLOCK error. Allows to re-emit the error from + the error handler without falling into infinite recursion. + */ + bool m_is_active; +}; + + +bool MDL_deadlock_handler::handle_condition(THD *, + uint sql_errno, + const char*, + MYSQL_ERROR::enum_warning_level, + const char*, + MYSQL_ERROR ** cond_hdl) { - if (table_list->lock_strategy) + *cond_hdl= NULL; + if (! m_is_active && sql_errno == ER_LOCK_DEADLOCK) { - MDL_request_list mdl_requests; - MDL_request *global_request; + /* Disable the handler to avoid infinite recursion. */ + m_is_active= TRUE; + (void) m_ot_ctx->request_backoff_action(Open_table_context::OT_MDL_CONFLICT, + NULL); + m_is_active= FALSE; /* - In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table - may not yet exist. Let's acquire an exclusive lock for that - case. If later it turns out the table existsed, we will - downgrade the lock to shared. Note that, according to the - locking protocol, all exclusive locks must be acquired before - shared locks. This invariant is preserved here and is also - enforced by asserts in metadata locking subsystem. + If the above back-off request failed, a new instance of + ER_LOCK_DEADLOCK error was emitted. Thus the current + instance of error condition can be treated as handled. */ + return TRUE; + } + return FALSE; +} - mdl_request->set_type(MDL_EXCLUSIVE); - DBUG_ASSERT(! thd->mdl_context.has_locks() || - thd->handler_tables_hash.records || - thd->global_read_lock.is_acquired()); - - if (!(global_request= ot_ctx->get_global_mdl_request(thd))) - return 1; - mdl_requests.push_front(mdl_request); - mdl_requests.push_front(global_request); +/** + Try to acquire an MDL lock for a table being opened. + + @param[in,out] thd Session context, to report errors. + @param[out] ot_ctx Open table context, to hold the back off + state. If we failed to acquire a lock + due to a lock conflict, we add the + failed request to the open table context. + @param[in,out] mdl_request A request for an MDL lock. + If we managed to acquire a ticket + (no errors or lock conflicts occurred), + contains a reference to it on + return. However, is not modified if MDL + lock type- modifying flags were provided. + @param[in] flags flags MYSQL_OPEN_FORCE_SHARED_MDL, + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL or + MYSQL_OPEN_FAIL_ON_MDL_CONFLICT + @sa open_table(). + @param[out] mdl_ticket Only modified if there was no error. + If we managed to acquire an MDL + lock, contains a reference to the + ticket, otherwise is set to NULL. + + @retval TRUE An error occurred. + @retval FALSE No error, but perhaps a lock conflict, check mdl_ticket. +*/ - if (thd->mdl_context.acquire_locks(&mdl_requests, ot_ctx->get_timeout())) - return 1; - } - else +static bool +open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, + MDL_request *mdl_request, + uint flags, + MDL_ticket **mdl_ticket) +{ + if (flags & (MYSQL_OPEN_FORCE_SHARED_MDL | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)) { - if (flags & MYSQL_OPEN_FORCE_SHARED_MDL) - { - /* - While executing PREPARE for prepared statement we override - type-of-operation aware type of shared metadata lock which - was set in the parser with simple shared metadata lock. - This is necessary to allow concurrent execution of PREPARE - and LOCK TABLES WRITE statement which locks one of the tables - used in the statement being prepared. - */ - DBUG_ASSERT(!(flags & (MYSQL_OPEN_TAKE_UPGRADABLE_MDL | - MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))); + /* + MYSQL_OPEN_FORCE_SHARED_MDL flag means that we are executing + PREPARE for a prepared statement and want to override + the type-of-operation aware metadata lock which was set + in the parser/during view opening with a simple shared + metadata lock. + This is necessary to allow concurrent execution of PREPARE + and LOCK TABLES WRITE statement against the same table. + + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL flag means that we open + the table in order to get information about it for one of I_S + queries and also want to override the type-of-operation aware + shared metadata lock which was set earlier (e.g. during view + opening) with a high-priority shared metadata lock. + This is necessary to avoid unnecessary waiting and extra + ER_WARN_I_S_SKIPPED_TABLE warnings when accessing I_S tables. + + These two flags are mutually exclusive. + */ + DBUG_ASSERT(!(flags & MYSQL_OPEN_FORCE_SHARED_MDL) || + !(flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)); - mdl_request->set_type(MDL_SHARED); - } - else if (flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) - { - DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL)); - mdl_request->set_type(MDL_SHARED_HIGH_PRIO); - } + mdl_request= new (thd->mem_root) MDL_request(mdl_request); + if (mdl_request == NULL) + return TRUE; - ot_ctx->add_request(mdl_request); + mdl_request->set_type((flags & MYSQL_OPEN_FORCE_SHARED_MDL) ? + MDL_SHARED : MDL_SHARED_HIGH_PRIO); + } - if (thd->mdl_context.try_acquire_lock(mdl_request)) - return 1; + ot_ctx->add_request(mdl_request); + if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) + { + /* + When table is being open in order to get data for I_S table, + we might have some tables not only open but also locked (e.g. when + this happens under LOCK TABLES or in a stored function). + As a result by waiting on a conflicting metadata lock to go away + we may create a deadlock which won't entirely belong to the + MDL subsystem and thus won't be detectable by this subsystem's + deadlock detector. + To avoid such situation we skip the trouble-making table if + there is a conflicting lock. + */ + if (thd->mdl_context.try_acquire_lock(mdl_request)) + return TRUE; if (mdl_request->ticket == NULL) { - if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) - my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), table_list->db, table_list->table_name); - else - ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK); - return 1; + my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), + mdl_request->key.db_name(), mdl_request->key.name()); + return TRUE; } } - return 0; + else + { + /* + We are doing a normal table open. Let us try to acquire a metadata + lock on the table. If there is a conflicting lock, acquire_lock() + will wait for it to go away. Sometimes this waiting may lead to a + deadlock, with the following results: + 1) If a deadlock is entirely within MDL subsystem, it is + detected by the deadlock detector of this subsystem. + ER_LOCK_DEADLOCK error is produced. Then, the error handler + that is installed prior to the call to acquire_lock() attempts + to request a back-off and retry. Upon success, ER_LOCK_DEADLOCK + error is suppressed, otherwise propagated up the calling stack. + 2) Otherwise, a deadlock may occur when the wait-for graph + includes edges not visible to the MDL deadlock detector. + One such example is a wait on an InnoDB row lock, e.g. when: + conn C1 gets SR MDL lock on t1 with SELECT * FROM t1 + conn C2 gets a row lock on t2 with SELECT * FROM t2 FOR UPDATE + conn C3 gets in and waits on C1 with DROP TABLE t0, t1 + conn C2 continues and blocks on C3 with SELECT * FROM t0 + conn C1 deadlocks by waiting on C2 by issuing SELECT * FROM + t2 LOCK IN SHARE MODE. + Such circular waits are currently only resolved by timeouts, + e.g. @@innodb_lock_wait_timeout or @@lock_wait_timeout. + */ + MDL_deadlock_handler mdl_deadlock_handler(ot_ctx); + + thd->push_internal_handler(&mdl_deadlock_handler); + bool result= thd->mdl_context.acquire_lock(mdl_request, + ot_ctx->get_timeout()); + thd->pop_internal_handler(); + + if (result && !ot_ctx->can_recover_from_failed_open()) + return TRUE; + } + *mdl_ticket= mdl_request->ticket; + return FALSE; } @@ -2474,11 +2596,9 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, is never opened. In both cases, metadata locks are always taken according to the lock strategy. - This function will take a exclusive metadata lock on the table if - TABLE_LIST::lock_strategy is EXCLUSIVE_DOWNGRADABLE_MDL or EXCLUSIVE_MDL. - If the lock strategy is EXCLUSIVE_DOWNGRADABLE_MDL and opening the table - is successful, the exclusive metadata lock is downgraded to a shared - lock. + If the lock strategy is OTLS_DOWNGRADE_IF_EXISTS and opening the table + is successful, the exclusive metadata lock acquired by the caller + is downgraded to a shared lock. RETURN TRUE Open failed. "action" parameter may contain type of action @@ -2490,13 +2610,13 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - Open_table_context *ot_ctx, uint flags) + Open_table_context *ot_ctx) { reg1 TABLE *table; char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; - MDL_request *mdl_request; + uint flags= ot_ctx->get_flags(); MDL_ticket *mdl_ticket; int error; TABLE_SHARE *share; @@ -2532,9 +2652,10 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (thd->global_read_lock.wait_if_global_read_lock(thd, 1, 1)) DBUG_RETURN(TRUE); - if (thd->version != refresh_version) + if (thd->open_tables && thd->open_tables->s->version != refresh_version) { - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, + NULL); DBUG_RETURN(TRUE); } } @@ -2678,7 +2799,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, during prelocking process (in this case in theory we still should hold shared metadata lock on it). */ - if (mysql_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) + if (dd_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) { if (!tdc_open_view(thd, table_list, alias, key, key_length, mem_root, 0)) @@ -2707,66 +2828,45 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, This is the normal use case. */ - mdl_request= &table_list->mdl_request; if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - if (open_table_get_mdl_lock(thd, table_list, mdl_request, ot_ctx, flags)) + if (open_table_get_mdl_lock(thd, ot_ctx, &table_list->mdl_request, + flags, &mdl_ticket) || + mdl_ticket == NULL) { DEBUG_SYNC(thd, "before_open_table_wait_refresh"); DBUG_RETURN(TRUE); } DEBUG_SYNC(thd, "after_open_table_mdl_shared"); } - - /* - Grab reference to the granted MDL lock ticket. Must be done after - open_table_get_mdl_lock as the lock on the table might have been - acquired previously (MYSQL_OPEN_HAS_MDL_LOCK). - */ - mdl_ticket= mdl_request->ticket; + else + { + /* + Grab reference to the MDL lock ticket that was acquired + by the caller. + */ + mdl_ticket= table_list->mdl_request.ticket; + } hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); - mysql_mutex_lock(&LOCK_open); - /* - If it's the first table from a list of tables used in a query, - remember refresh_version (the version of open_cache state). - If the version changes while we're opening the remaining tables, - we will have to back off, close all the tables opened-so-far, - and try to reopen them. - Note: refresh_version is currently changed only during FLUSH TABLES. - */ - if (!thd->open_tables) - thd->version=refresh_version; - else if ((thd->version != refresh_version) && - ! (flags & MYSQL_OPEN_IGNORE_FLUSH)) - { - /* Someone did a refresh while thread was opening tables */ - mysql_mutex_unlock(&LOCK_open); - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); - DBUG_RETURN(TRUE); - } if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS) { bool exists; if (check_if_table_exists(thd, table_list, &exists)) - goto err_unlock2; + DBUG_RETURN(TRUE); if (!exists) - { - mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(FALSE); - } + /* Table exists. Let us try to open it. */ } else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB) - { - mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(FALSE); - } + mysql_mutex_lock(&LOCK_open); #ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL if (!(share= (TABLE_SHARE *) mdl_ticket->get_cached_object())) #endif @@ -2788,7 +2888,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, my_error(ER_WRONG_MRG_TABLE, MYF(0)); goto err_unlock; } - + /* This table is a view. Validate its metadata version: in particular, that it was a view when the statement was prepared. @@ -2802,26 +2902,15 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (open_new_frm(thd, share, alias, (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - (flags & OPEN_VIEW_NO_PARSE), thd->open_options, + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + thd->open_options, 0, table_list, mem_root)) goto err_unlock; /* TODO: Don't free this */ release_table_share(share); - if (flags & OPEN_VIEW_NO_PARSE) - { - /* - VIEW not really opened, only frm were read. - Set 1 as a flag here - */ - table_list->view= (LEX*)1; - } - else - { - DBUG_ASSERT(table_list->view); - } + DBUG_ASSERT(table_list->view); mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(FALSE); @@ -2868,7 +2957,15 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } #endif - if (share->version != refresh_version) + + /* + If the version changes while we're opening the tables, + we have to back off, close all the tables opened-so-far, + and try to reopen them. Note: refresh_version is currently + changed only during FLUSH TABLES. + */ + if (share->needs_reopen() || + (thd->open_tables && thd->open_tables->s->version != share->version)) { if (!(flags & MYSQL_OPEN_IGNORE_FLUSH)) { @@ -2884,11 +2981,10 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ release_table_share(share); mysql_mutex_unlock(&LOCK_open); - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, + NULL); DBUG_RETURN(TRUE); } - /* Force close at once after usage */ - thd->version= share->version; } if (!share->free_tables.is_empty()) @@ -2904,9 +3000,11 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, while (table_cache_count > table_cache_size && unused_tables) free_cache_entry(unused_tables); + mysql_mutex_unlock(&LOCK_open); + /* make a new table */ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) - goto err_unlock; + goto err_lock; error= open_table_from_share(thd, share, alias, (uint) (HA_OPEN_KEYFILE | @@ -2922,26 +3020,23 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, my_free(table, MYF(0)); if (error == 7) - { - share->version= 0; - (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER); - } + (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER, + table_list); else if (share->crashed) - { - share->version= 0; - (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR); - } + (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR, + table_list); - goto err_unlock; + goto err_lock; } if (open_table_entry_fini(thd, share, table)) { closefrm(table, 0); my_free((uchar*)table, MYF(0)); - goto err_unlock; + goto err_lock; } + mysql_mutex_lock(&LOCK_open); /* Add table to the share's used tables list. */ table_def_add_used_table(thd, table); } @@ -2953,14 +3048,14 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table exists now we should downgrade our exclusive metadata lock on this table to SW metadata lock. */ - if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL && + if (table_list->lock_strategy == TABLE_LIST::OTLS_DOWNGRADE_IF_EXISTS && !(flags & MYSQL_OPEN_HAS_MDL_LOCK)) mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_WRITE); table->mdl_ticket= mdl_ticket; - table->next=thd->open_tables; /* Link into simple list */ - thd->open_tables=table; + table->next= thd->open_tables; /* Link into simple list */ + thd->set_open_tables(table); table->reginfo.lock_type=TL_READ; /* Assume read */ @@ -3003,6 +3098,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->file->extra(HA_EXTRA_DETACH_CHILDREN); DBUG_RETURN(FALSE); +err_lock: + mysql_mutex_lock(&LOCK_open); err_unlock: release_table_share(share); err_unlock2: @@ -3300,7 +3397,6 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) */ if (reopen_count) { - mysql_mutex_lock(&LOCK_open); while (reopen_count--) { /* @@ -3317,7 +3413,6 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) close_thread_table(thd, &thd->open_tables); } broadcast_refresh(); - mysql_mutex_unlock(&LOCK_open); } /* Exclude all closed tables from the LOCK TABLES list. */ for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= @@ -3351,7 +3446,7 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) bool Locked_tables_list::reopen_tables(THD *thd) { - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); size_t reopen_count= 0; MYSQL_LOCK *lock; MYSQL_LOCK *merged_lock; @@ -3363,8 +3458,7 @@ Locked_tables_list::reopen_tables(THD *thd) continue; /* Links into thd->open_tables upon success */ - if (open_table(thd, table_list, thd->mem_root, &ot_ctx_unused, - MYSQL_OPEN_REOPEN)) + if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) { unlink_all_closed_tables(thd, 0, reopen_count); return TRUE; @@ -3719,7 +3813,7 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) TABLE_SHARE *share; TABLE *entry; int not_used; - bool result= FALSE; + bool result= TRUE; my_hash_value_type hash_value; cache_key_length= create_table_def_key(thd, cache_key, table_list, 0); @@ -3734,20 +3828,19 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) cache_key_length, OPEN_VIEW, ¬_used, hash_value))) - { - mysql_mutex_unlock(&LOCK_open); - return TRUE; - } + goto end_unlock; if (share->is_view) - goto end_with_lock_open; + { + release_table_share(share); + goto end_unlock; + } if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME)))) { - result= TRUE; - goto end_with_lock_open; + release_table_share(share); + goto end_unlock; } - share->version= 0; mysql_mutex_unlock(&LOCK_open); if (open_table_from_share(thd, share, table_list->alias, @@ -3766,19 +3859,21 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) share->table_name.str); if (entry->file) closefrm(entry, 0); - result= TRUE; } else { thd->clear_error(); // Clear error message closefrm(entry, 0); + result= FALSE; } my_free(entry, MYF(0)); mysql_mutex_lock(&LOCK_open); - -end_with_lock_open: release_table_share(share); + /* Remove the repaired share from the table cache. */ + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + table_list->db, table_list->table_name); +end_unlock: mysql_mutex_unlock(&LOCK_open); return result; } @@ -3786,14 +3881,17 @@ end_with_lock_open: /** Open_table_context */ -Open_table_context::Open_table_context(THD *thd, ulong timeout) - :m_action(OT_NO_ACTION), +Open_table_context::Open_table_context(THD *thd, uint flags) + :m_failed_table(NULL), m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()), + m_global_mdl_request(NULL), + m_timeout(flags & MYSQL_LOCK_IGNORE_TIMEOUT ? + LONG_TIMEOUT : thd->variables.lock_wait_timeout), + m_flags(flags), + m_action(OT_NO_ACTION), m_has_locks((thd->in_multi_stmt_transaction_mode() && thd->mdl_context.has_locks()) || - thd->mdl_context.trans_sentinel()), - m_global_mdl_request(NULL), - m_timeout(timeout) + thd->mdl_context.trans_sentinel()) {} @@ -3807,10 +3905,8 @@ MDL_request *Open_table_context::get_global_mdl_request(THD *thd) { if (! m_global_mdl_request) { - char *buff; - if ((buff= (char*)thd->alloc(sizeof(MDL_request)))) + if ((m_global_mdl_request= new (thd->mem_root) MDL_request())) { - m_global_mdl_request= new (buff) MDL_request(); m_global_mdl_request->init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); } @@ -3829,30 +3925,51 @@ MDL_request *Open_table_context::get_global_mdl_request(THD *thd) bool Open_table_context:: -request_backoff_action(enum_open_table_action action_arg) +request_backoff_action(enum_open_table_action action_arg, + TABLE_LIST *table) { /* - We are inside a transaction that already holds locks and have - met a broken table or a table which needs re-discovery. - Performing any recovery action requires acquiring an exclusive - metadata lock on this table. Doing that with locks breaks the - metadata locking protocol and might lead to deadlocks, - so we report an error. - - However, if we have only met a conflicting lock or an old - TABLE version, and just need to wait for the conflict to - disappear/old version to go away, allow waiting. - While waiting, we use a simple empiric to detect - deadlocks: we never wait on someone who's waiting too. - Waiting will be done after releasing metadata locks acquired - by this statement. + A back off action may be one of the three kinds: + + * We met a broken table that needs repair, or a table that + is not present on this MySQL server and needs re-discovery. + To perform the action, we need an exclusive metadata lock on + the table. Acquiring an X lock while holding other shared + locks is very deadlock-prone. If this is a multi- statement + transaction that holds metadata locks for completed + statements, we don't do it, and report an error instead. + * Our attempt to acquire an MDL lock lead to a deadlock, + detected by the MDL deadlock detector. The current + session was chosen a victim. If this is a multi-statement + transaction that holds metadata locks for completed statements, + restarting locking for the current statement may lead + to a livelock. Thus, again, if m_has_locks is set, + we report an error. Otherwise, when there are no metadata + locks other than which belong to this statement, we can + try to recover from error by releasing all locks and + restarting the pre-locking. + * Finally, we could have met a TABLE_SHARE with old version. + Again, if this is a first statement in a transaction we can + close all tables, release all metadata locks and wait for + the old version to go away. Otherwise, waiting with MDL locks + may lead to criss-cross wait between this connection and a + connection that has an open table and waits on a metadata lock, + i.e. to a deadlock. + Since there is no way to detect such a deadlock, we prevent + it by reporting an error. */ - if (m_has_locks && action_arg != OT_WAIT_MDL_LOCK) + if (m_has_locks) { my_error(ER_LOCK_DEADLOCK, MYF(0)); return TRUE; } m_action= action_arg; + /* + If auto-repair or discovery are requested, a pointer to table + list element must be provided. + */ + DBUG_ASSERT((m_action != OT_DISCOVER && m_action != OT_REPAIR) || table); + m_failed_table= table; return FALSE; } @@ -3861,10 +3978,6 @@ request_backoff_action(enum_open_table_action action_arg) Recover from failed attempt of open table by performing requested action. @param thd Thread context - @param mdl_request MDL_request of the object that caused the problem. - @param table Optional (can be NULL). Used only if action is OT_REPAIR. - In that case a TABLE_LIST for the table to be repaired. - @todo: It's unnecessary and should be removed. @pre This function should be called only with "action" != OT_NO_ACTION and after having called @sa close_tables_for_reopen(). @@ -3875,8 +3988,7 @@ request_backoff_action(enum_open_table_action action_arg) bool Open_table_context:: -recover_from_failed_open(THD *thd, MDL_request *mdl_request, - TABLE_LIST *table) +recover_from_failed_open(THD *thd) { bool result= FALSE; /* @@ -3887,8 +3999,7 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, /* Execute the action. */ switch (m_action) { - case OT_WAIT_MDL_LOCK: - result= thd->mdl_context.wait_for_lock(mdl_request, get_timeout()); + case OT_MDL_CONFLICT: break; case OT_WAIT_TDC: result= tdc_wait_for_old_versions(thd, &m_mdl_requests, get_timeout()); @@ -3897,7 +4008,7 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, case OT_DISCOVER: { MDL_request mdl_global_request; - MDL_request mdl_xlock_request(mdl_request); + MDL_request mdl_xlock_request(&m_failed_table->mdl_request); MDL_request_list mdl_requests; mdl_global_request.init(MDL_key::GLOBAL, "", "", @@ -3911,14 +4022,11 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, thd->mdl_context.acquire_locks(&mdl_requests, get_timeout()))) break; - DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, - mdl_request->key.db_name(), - mdl_request->key.name()); - ha_create_table_from_engine(thd, - mdl_request->key.db_name(), - mdl_request->key.name()); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db, + m_failed_table->table_name); + ha_create_table_from_engine(thd, m_failed_table->db, + m_failed_table->table_name); mysql_mutex_unlock(&LOCK_open); thd->warning_info->clear_warning_info(thd->query_id); @@ -3929,7 +4037,7 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, case OT_REPAIR: { MDL_request mdl_global_request; - MDL_request mdl_xlock_request(mdl_request); + MDL_request mdl_xlock_request(&m_failed_table->mdl_request); MDL_request_list mdl_requests; mdl_global_request.init(MDL_key::GLOBAL, "", "", @@ -3943,14 +4051,12 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, thd->mdl_context.acquire_locks(&mdl_requests, get_timeout()))) break; - DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, - mdl_request->key.db_name(), - mdl_request->key.name()); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db, + m_failed_table->table_name); mysql_mutex_unlock(&LOCK_open); - result= auto_repair_table(thd, table); + result= auto_repair_table(thd, m_failed_table); thd->mdl_context.release_transactional_locks(); break; } @@ -3959,6 +4065,12 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, } /* Remove all old requests, they will be re-added. */ m_mdl_requests.empty(); + /* + Reset the pointers to conflicting MDL request and the + TABLE_LIST element, set when we need auto-discovery or repair, + for safety. + */ + m_failed_table= NULL; /* Prepare for possible another back-off. */ m_action= OT_NO_ACTION; return result; @@ -4081,15 +4193,25 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, */ DBUG_ASSERT(rt->mdl_request.type == MDL_SHARED); - if (thd->mdl_context.try_acquire_lock(&rt->mdl_request)) - DBUG_RETURN(TRUE); + /* + Waiting for a conflicting metadata lock to go away may + lead to a deadlock, detected by MDL subsystem. + If possible, we try to resolve such deadlocks by releasing all + metadata locks and restarting the pre-locking process. + To prevent the error from polluting the diagnostics area + in case of successful resolution, install a special error + handler for ER_LOCK_DEADLOCK error. + */ + MDL_deadlock_handler mdl_deadlock_handler(ot_ctx); - if (rt->mdl_request.ticket == NULL) - { - /* A lock conflict. Someone's trying to modify SP metadata. */ - ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK); + thd->push_internal_handler(&mdl_deadlock_handler); + bool result= thd->mdl_context.acquire_lock(&rt->mdl_request, + ot_ctx->get_timeout()); + thd->pop_internal_handler(); + + if (result) DBUG_RETURN(TRUE); - } + DEBUG_SYNC(thd, "after_shared_lock_pname"); /* Ensures the routine is up-to-date and cached, if exists. */ @@ -4234,12 +4356,14 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, */ if (tables->view) { + MDL_ticket *mdl_ticket; /* We still need to take a MDL lock on the merged view to protect it from concurrent changes. */ - if (!open_table_get_mdl_lock(thd, tables, &tables->mdl_request, - ot_ctx, flags)) + if (!open_table_get_mdl_lock(thd, ot_ctx, &tables->mdl_request, + flags, &mdl_ticket) && + mdl_ticket != NULL) goto process_view_routines; /* Fall-through to return error. */ } @@ -4268,12 +4392,12 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, */ Prelock_error_handler prelock_handler; thd->push_internal_handler(& prelock_handler); - error= open_table(thd, tables, new_frm_mem, ot_ctx, flags); + error= open_table(thd, tables, new_frm_mem, ot_ctx); thd->pop_internal_handler(); safe_to_ignore_table= prelock_handler.safely_trapped_errors(); } else - error= open_table(thd, tables, new_frm_mem, ot_ctx, flags); + error= open_table(thd, tables, new_frm_mem, ot_ctx); free_root(new_frm_mem, MYF(MY_KEEP_PREALLOC)); @@ -4429,6 +4553,8 @@ end: should be acquired. @param tables_end End of list of tables. @param ot_ctx Context of open_tables() operation. + @param flags Bitmap of flags to modify how the tables will be + open, see open_table() description for details. @retval FALSE Success. @retval TRUE Failure (e.g. connection was killed) @@ -4437,31 +4563,30 @@ end: static bool open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, TABLE_LIST *tables_end, - Open_table_context *ot_ctx) + Open_table_context *ot_ctx, + uint flags) { MDL_request_list mdl_requests; TABLE_LIST *table; DBUG_ASSERT(!thd->locked_tables_mode); - DEBUG_SYNC(thd, "open_tables_acquire_upgradable_mdl"); for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->lock_type >= TL_WRITE_ALLOW_WRITE && + if (table->mdl_request.type >= MDL_SHARED_NO_WRITE && !(table->open_type == OT_TEMPORARY_ONLY || + (flags & MYSQL_OPEN_TEMPORARY_ONLY) || (table->open_type != OT_BASE_ONLY && + ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && find_temporary_table(thd, table)))) - { - table->mdl_request.set_type(table->lock_type > TL_WRITE_ALLOW_READ ? - MDL_SHARED_NO_READ_WRITE : - MDL_SHARED_NO_WRITE); mdl_requests.push_front(&table->mdl_request); - } } if (! mdl_requests.is_empty()) { + DEBUG_SYNC(thd, "open_tables_acquire_upgradable_mdl"); + MDL_request *global_request= ot_ctx->get_global_mdl_request(thd); if (global_request == NULL) @@ -4475,11 +4600,8 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->lock_type >= TL_WRITE_ALLOW_WRITE) - { + if (table->mdl_request.type >= MDL_SHARED_NO_WRITE) table->mdl_request.ticket= NULL; - table->mdl_request.set_type(MDL_SHARED_WRITE); - } } return FALSE; @@ -4495,6 +4617,8 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, @param tables_start Start of list of tables on which upgradable locks should be searched for. @param tables_end End of list of tables. + @param flags Bitmap of flags to modify how the tables will be + open, see open_table() description for details. @retval FALSE Success. @retval TRUE Failure (e.g. connection was killed) @@ -4502,7 +4626,7 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, static bool open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, - TABLE_LIST *tables_end) + TABLE_LIST *tables_end, uint flags) { TABLE_LIST *table; @@ -4511,9 +4635,11 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->lock_type >= TL_WRITE_ALLOW_WRITE && + if (table->mdl_request.type >= MDL_SHARED_NO_WRITE && !(table->open_type == OT_TEMPORARY_ONLY || + (flags & MYSQL_OPEN_TEMPORARY_ONLY) || (table->open_type != OT_BASE_ONLY && + ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && find_temporary_table(thd, table)))) { /* @@ -4525,8 +4651,14 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, lock, all other instances of TABLE for the same table will have the same ticket. - Note that find_table_for_mdl_upgrade() will report an error if a - ticket is not found. + Note that this works OK even for CREATE TABLE statements which + request X type of metadata lock. This is because under LOCK TABLES + such statements don't create the table but only check if it exists + or, in most complex case, only insert into it. + Thus SNRW lock should be enough. + + Note that find_table_for_mdl_upgrade() will report an error if + no suitable ticket is found. */ if (!find_table_for_mdl_upgrade(thd->open_tables, table->db, table->table_name, FALSE)) @@ -4580,26 +4712,13 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, TABLE_LIST **table_to_open; Sroutine_hash_entry **sroutine_to_open; TABLE_LIST *tables; - Open_table_context ot_ctx(thd, (flags & MYSQL_LOCK_IGNORE_TIMEOUT) ? - LONG_TIMEOUT : thd->variables.lock_wait_timeout); + Open_table_context ot_ctx(thd, flags); bool error= FALSE; MEM_ROOT new_frm_mem; bool has_prelocking_list; DBUG_ENTER("open_tables"); /* - Close HANDLER tables which are marked for flush or against which there - are pending exclusive metadata locks. Note that we do this not to avoid - deadlocks (calls to mysql_ha_flush() in mdl_wait_for_locks() and - tdc_wait_for_old_version() are enough for this) but in order to have - a point during statement execution at which such HANDLERs are closed - even if they don't create problems for current thread (i.e. to avoid - having DDL blocked by HANDLERs opened for long time). - */ - if (thd->handler_tables_hash.records) - mysql_ha_flush(thd); - - /* temporary mem_root for new .frm parsing. TODO: variables for size */ @@ -4607,6 +4726,17 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, thd->current_tablenr= 0; restart: + /* + Close HANDLER tables which are marked for flush or against which there + are pending exclusive metadata locks. This is needed both in order to + avoid deadlocks and to have a point during statement execution at + which such HANDLERs are closed even if they don't create problems for + the current session (i.e. to avoid having a DDL blocked by HANDLERs + opened for a long time). + */ + if (thd->handler_tables_hash.records) + mysql_ha_flush(thd); + has_prelocking_list= thd->lex->requires_prelocking(); table_to_open= start; sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first; @@ -4618,21 +4748,19 @@ restart: (in non-LOCK TABLES mode) we might have to acquire upgradable semi-exclusive metadata locks (SNW or SNRW) on some of the tables to be opened. - So we acquire all such locks at once here as doing this in one + When executing CREATE TABLE .. If NOT EXISTS .. SELECT, the + table may not yet exist, in which case we acquire an exclusive + lock. + We acquire all such locks at once here as doing this in one by one fashion may lead to deadlocks or starvation. Later when we will be opening corresponding table pre-acquired metadata lock will be reused (thanks to the fact that in recursive case metadata locks are acquired without waiting). */ - if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) + if (! (flags & (MYSQL_OPEN_HAS_MDL_LOCK | + MYSQL_OPEN_FORCE_SHARED_MDL | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))) { - /* - open_tables_acquire_upgradable_mdl() does not currenly handle - these two flags. At this point, that does not matter as they - are not used together with MYSQL_OPEN_TAKE_UPGRADABLE_MDL. - */ - DBUG_ASSERT(!(flags & (MYSQL_OPEN_SKIP_TEMPORARY | - MYSQL_OPEN_TEMPORARY_ONLY))); if (thd->locked_tables_mode) { /* @@ -4640,7 +4768,8 @@ restart: need to check if appropriate locks were pre-acquired. */ if (open_tables_check_upgradable_mdl(thd, *start, - thd->lex->first_not_own_table())) + thd->lex->first_not_own_table(), + flags)) { error= TRUE; goto err; @@ -4648,7 +4777,7 @@ restart: } else if (open_tables_acquire_upgradable_mdl(thd, *start, thd->lex->first_not_own_table(), - &ot_ctx)) + &ot_ctx, flags)) { error= TRUE; goto err; @@ -4694,7 +4823,6 @@ restart: have failed to open since closing tables can trigger removal of elements from the table list (if MERGE tables are involved), */ - TABLE_LIST *failed_table= *table_to_open; close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); /* @@ -4702,8 +4830,7 @@ restart: TABLE_LIST element. Altough currently this assumption is valid it may change in future. */ - if (ot_ctx.recover_from_failed_open(thd, &failed_table->mdl_request, - failed_table)) + if (ot_ctx.recover_from_failed_open(thd)) goto err; error= FALSE; @@ -4747,7 +4874,7 @@ restart: { close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); - if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL)) + if (ot_ctx.recover_from_failed_open(thd)) goto err; error= FALSE; @@ -5054,8 +5181,8 @@ static bool check_lock_and_start_stmt(THD *thd, else lock_type= table_list->lock_type; - if ((int) lock_type >= (int) TL_WRITE_ALLOW_READ && - (int) table_list->table->reginfo.lock_type < (int) TL_WRITE_ALLOW_READ) + if ((int) lock_type > (int) TL_WRITE_ALLOW_WRITE && + (int) table_list->table->reginfo.lock_type <= (int) TL_WRITE_ALLOW_WRITE) { my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_list->alias); DBUG_RETURN(1); @@ -5156,8 +5283,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, uint lock_flags) { TABLE *table; - Open_table_context ot_ctx(thd, (lock_flags & MYSQL_LOCK_IGNORE_TIMEOUT) ? - LONG_TIMEOUT : thd->variables.lock_wait_timeout); + Open_table_context ot_ctx(thd, lock_flags); bool error; DBUG_ENTER("open_ltable"); @@ -5169,7 +5295,10 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, /* open_ltable can be used only for BASIC TABLEs */ table_list->required_type= FRMTYPE_TABLE; - while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, lock_flags)) && + /* This function can't properly handle requests for such metadata locks. */ + DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_NO_WRITE); + + while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx)) && ot_ctx.can_recover_from_failed_open()) { /* @@ -5179,8 +5308,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, */ thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp()); table_list->mdl_request.ticket= 0; - if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request, - table_list)) + if (ot_ctx.recover_from_failed_open(thd)) break; } @@ -5554,15 +5682,6 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, /* We have to cleanup translation tables of views. */ tmp->cleanup_items(); } - /* - Metadata lock requests for tables from extended part of prelocking set - are part of list of requests to be waited for in Open_table_context. - So to satisfy assumptions in MDL_context::wait_for_locks(), which will - performs the waiting, we have to reset MDL_request::ticket values for - them as well. - */ - for (tmp= first_not_own_table; tmp; tmp= tmp->next_global) - tmp->mdl_request.ticket= NULL; close_thread_tables(thd); thd->mdl_context.rollback_to_savepoint(start_of_statement_svp); } @@ -8478,7 +8597,7 @@ my_bool mysql_rm_tmp_tables(void) all not used tables. */ -void flush_tables() +void tdc_flush_unused_tables() { mysql_mutex_lock(&LOCK_open); while (unused_tables) @@ -8529,10 +8648,10 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, mysql_mutex_unlock(&in_use->mysys_var->mutex); signalled= TRUE; } - mysql_mutex_lock(&LOCK_open); if (needs_thr_lock_abort) { + mysql_mutex_lock(&in_use->LOCK_thd_data); for (TABLE *thd_table= in_use->open_tables; thd_table ; thd_table= thd_table->next) @@ -8543,10 +8662,11 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, and do not remove such instances from the THD::open_tables for some time, during which other thread can see those instances (e.g. see partitioning code). - */ + */ if (!thd_table->needs_reopen()) signalled|= mysql_lock_abort_for_thread(thd, thd_table); } + mysql_mutex_unlock(&in_use->LOCK_thd_data); } /* Wake up threads waiting in tdc_wait_for_old_versions(). @@ -8559,7 +8679,6 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, a multi-statement transaction. */ broadcast_refresh(); - mysql_mutex_unlock(&LOCK_open); return signalled; } @@ -8668,8 +8787,12 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests, while (!thd->killed) { /* - Here we have situation as in mdl_wait_for_locks() we need to - get rid of offending HANDLERs to avoid deadlock. + We have to get rid of HANDLERs which are open by this thread + and have old TABLE versions. Otherwise we might get a deadlock + in situation when we are waiting for an old TABLE object which + corresponds to a HANDLER open by another session. And this + other session waits for our HANDLER object to get closed. + TODO: We should also investigate in which situations we have to broadcast on COND_refresh because of this. */ @@ -8686,7 +8809,7 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests, if ((share= get_cached_table_share(mdl_request->key.db_name(), mdl_request->key.name())) && - share->version != refresh_version) + share->needs_reopen()) break; } if (!mdl_request) diff --git a/sql/sql_base.h b/sql/sql_base.h index 0fe70e4bc9d..20a068e27d7 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -89,7 +89,7 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - Open_table_context *ot_ctx, uint flags); + Open_table_context *ot_ctx); bool name_lock_locked_table(THD *thd, TABLE_LIST *tables); bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in); TABLE *table_cache_insert_placeholder(THD *thd, const char *key, @@ -233,7 +233,6 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table); void remove_db_from_cache(const char *db); -void flush_tables(); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); /* Functions to work with system tables. */ @@ -263,10 +262,12 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); +void tdc_flush_unused_tables(); TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, const char *table_name, bool no_error); void mark_tmp_table_for_reuse(TABLE *table); +bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists); extern uint table_cache_count; extern TABLE *unused_tables; @@ -322,6 +323,7 @@ inline TABLE_LIST *find_table_in_local_list(TABLE_LIST *table, db_name, table_name); } + inline bool setup_fields_with_no_wrap(THD *thd, Item **ref_pointer_array, List<Item> &item, enum_mark_columns mark_used_columns, @@ -336,6 +338,89 @@ inline bool setup_fields_with_no_wrap(THD *thd, Item **ref_pointer_array, return res; } +/** + An abstract class for a strategy specifying how the prelocking + algorithm should extend the prelocking set while processing + already existing elements in the set. +*/ + +class Prelocking_strategy +{ +public: + virtual ~Prelocking_strategy() { } + + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking) = 0; + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) = 0; + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking)= 0; +}; + + +/** + A Strategy for prelocking algorithm suitable for DML statements. + + Ensures that all tables used by all statement's SF/SP/triggers and + required for foreign key checks are prelocked and SF/SPs used are + cached. +*/ + +class DML_prelocking_strategy : public Prelocking_strategy +{ +public: + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking); + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); +}; + + +/** + A strategy for prelocking algorithm to be used for LOCK TABLES + statement. +*/ + +class Lock_tables_prelocking_strategy : public DML_prelocking_strategy +{ + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); +}; + + +/** + Strategy for prelocking algorithm to be used for ALTER TABLE statements. + + Unlike DML or LOCK TABLES strategy, it doesn't + prelock triggers, views or stored routines, since they are not + used during ALTER. +*/ + +class Alter_table_prelocking_strategy : public Prelocking_strategy +{ +public: + + Alter_table_prelocking_strategy(Alter_info *alter_info) + : m_alter_info(alter_info) + {} + + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking); + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + +private: + Alter_info *m_alter_info; +}; + + inline bool open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) { @@ -355,4 +440,85 @@ inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, &prelocking_strategy); } + +/** + A context of open_tables() function, used to recover + from a failed open_table() or open_routine() attempt. +*/ + +class Open_table_context +{ +public: + enum enum_open_table_action + { + OT_NO_ACTION= 0, + OT_MDL_CONFLICT, + OT_WAIT_TDC, + OT_DISCOVER, + OT_REPAIR + }; + Open_table_context(THD *thd, uint flags); + + bool recover_from_failed_open(THD *thd); + bool request_backoff_action(enum_open_table_action action_arg, + TABLE_LIST *table); + + void add_request(MDL_request *request) + { m_mdl_requests.push_front(request); } + + bool can_recover_from_failed_open() const + { return m_action != OT_NO_ACTION; } + + /** + When doing a back-off, we close all tables acquired by this + statement. Return an MDL savepoint taken at the beginning of + the statement, so that we can rollback to it before waiting on + locks. + */ + MDL_ticket *start_of_statement_svp() const + { + return m_start_of_statement_svp; + } + + MDL_request *get_global_mdl_request(THD *thd); + + inline ulong get_timeout() const + { + return m_timeout; + } + + uint get_flags() const { return m_flags; } +private: + /** List of requests for all locks taken so far. Used for waiting on locks. */ + MDL_request_list m_mdl_requests; + /** + For OT_DISCOVER and OT_REPAIR actions, the table list element for + the table which definition should be re-discovered or which + should be repaired. + */ + TABLE_LIST *m_failed_table; + MDL_ticket *m_start_of_statement_svp; + /** + Request object for global intention exclusive lock which is acquired during + opening tables for statements which take upgradable shared metadata locks. + */ + MDL_request *m_global_mdl_request; + /** + Lock timeout in seconds. Initialized to LONG_TIMEOUT when opening system + tables or to the "lock_wait_timeout" system variable for regular tables. + */ + ulong m_timeout; + /* open_table() flags. */ + uint m_flags; + /** Back off action. */ + enum enum_open_table_action m_action; + /** + Whether we had any locks when this context was created. + If we did, they are from the previous statement of a transaction, + and we can't safely do back-off (and release them). + */ + bool m_has_locks; +}; + + #endif /* SQL_BASE_INCLUDED */ diff --git a/sql/sql_bitmap.h b/sql/sql_bitmap.h index 80a4712dd69..8d00c984d14 100644 --- a/sql/sql_bitmap.h +++ b/sql/sql_bitmap.h @@ -22,6 +22,7 @@ #ifndef SQL_BITMAP_INCLUDED #define SQL_BITMAP_INCLUDED +#include <my_sys.h> #include <my_bitmap.h> template <uint default_width> class Bitmap diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 8c147421363..d29796149a7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -353,7 +353,7 @@ int thd_sql_command(const THD *thd) extern "C" int thd_tx_isolation(const THD *thd) { - return (int) thd->variables.tx_isolation; + return (int) thd->tx_isolation; } extern "C" @@ -592,7 +592,7 @@ THD::THD() *scramble= '\0'; /* Call to init() below requires fully initialized Open_tables_state. */ - init_open_tables_state(this, refresh_version); + reset_open_tables_state(this); init(); #if defined(ENABLED_PROFILING) @@ -960,7 +960,7 @@ void THD::init(void) update_lock_default= (variables.low_priority_updates ? TL_WRITE_LOW_PRIORITY : TL_WRITE); - session_tx_isolation= (enum_tx_isolation) variables.tx_isolation; + tx_isolation= (enum_tx_isolation) variables.tx_isolation; update_charset(); reset_current_stmt_binlog_format_row(); bzero((char *) &status_var, sizeof(status_var)); diff --git a/sql/sql_class.h b/sql/sql_class.h index 25b136bc4ca..c11c15571f2 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1006,7 +1006,6 @@ public: of the main statement is called. */ enum enum_locked_tables_mode locked_tables_mode; - ulong version; uint current_tablenr; enum enum_flags { @@ -1025,15 +1024,6 @@ public: */ Open_tables_state() : state_flags(0U) { } - /** - Prepare Open_tables_state instance for operations dealing with tables. - */ - void init_open_tables_state(THD *thd, ulong version_arg) - { - reset_open_tables_state(thd); - version= version_arg; - } - void set_open_tables_state(Open_tables_state *state) { *this= *state; @@ -1229,162 +1219,6 @@ private: /** - An abstract class for a strategy specifying how the prelocking - algorithm should extend the prelocking set while processing - already existing elements in the set. -*/ - -class Prelocking_strategy -{ -public: - virtual ~Prelocking_strategy() { } - - virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, - Sroutine_hash_entry *rt, sp_head *sp, - bool *need_prelocking) = 0; - virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking) = 0; - virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking)= 0; -}; - - -/** - A Strategy for prelocking algorithm suitable for DML statements. - - Ensures that all tables used by all statement's SF/SP/triggers and - required for foreign key checks are prelocked and SF/SPs used are - cached. -*/ - -class DML_prelocking_strategy : public Prelocking_strategy -{ -public: - virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, - Sroutine_hash_entry *rt, sp_head *sp, - bool *need_prelocking); - virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking); - virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking); -}; - - -/** - A strategy for prelocking algorithm to be used for LOCK TABLES - statement. -*/ - -class Lock_tables_prelocking_strategy : public DML_prelocking_strategy -{ - virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking); -}; - - -/** - Strategy for prelocking algorithm to be used for ALTER TABLE statements. - - Unlike DML or LOCK TABLES strategy, it doesn't - prelock triggers, views or stored routines, since they are not - used during ALTER. -*/ - -class Alter_table_prelocking_strategy : public Prelocking_strategy -{ -public: - - Alter_table_prelocking_strategy(Alter_info *alter_info) - : m_alter_info(alter_info) - {} - - virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, - Sroutine_hash_entry *rt, sp_head *sp, - bool *need_prelocking); - virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking); - virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, - TABLE_LIST *table_list, bool *need_prelocking); - -private: - Alter_info *m_alter_info; -}; - - -/** - A context of open_tables() function, used to recover - from a failed open_table() or open_routine() attempt. - - Implemented in sql_base.cc. -*/ - -class Open_table_context -{ -public: - enum enum_open_table_action - { - OT_NO_ACTION= 0, - OT_WAIT_MDL_LOCK, - OT_WAIT_TDC, - OT_DISCOVER, - OT_REPAIR - }; - Open_table_context(THD *thd, ulong timeout); - - bool recover_from_failed_open(THD *thd, MDL_request *mdl_request, - TABLE_LIST *table); - bool request_backoff_action(enum_open_table_action action_arg); - - void add_request(MDL_request *request) - { m_mdl_requests.push_front(request); } - - bool can_recover_from_failed_open() const - { return m_action != OT_NO_ACTION; } - - /** - When doing a back-off, we close all tables acquired by this - statement. Return an MDL savepoint taken at the beginning of - the statement, so that we can rollback to it before waiting on - locks. - */ - MDL_ticket *start_of_statement_svp() const - { - return m_start_of_statement_svp; - } - - MDL_request *get_global_mdl_request(THD *thd); - - inline ulong get_timeout() const - { - return m_timeout; - } - -private: - /** List of requests for all locks taken so far. Used for waiting on locks. */ - MDL_request_list m_mdl_requests; - /** Back off action. */ - enum enum_open_table_action m_action; - MDL_ticket *m_start_of_statement_svp; - /** - Whether we had any locks when this context was created. - If we did, they are from the previous statement of a transaction, - and we can't safely do back-off (and release them). - */ - bool m_has_locks; - /** - Request object for global intention exclusive lock which is acquired during - opening tables for statements which take upgradable shared metadata locks. - */ - MDL_request *m_global_mdl_request; - /** - Lock timeout in seconds. Initialized to LONG_TIMEOUT when opening system - tables or to the "lock_wait_timeout" system variable for regular tables. - */ - uint m_timeout; -}; - - -/** Tables that were locked with LOCK TABLES statement. Encapsulates a list of TABLE_LIST instances for tables @@ -2097,8 +1931,31 @@ public: uint server_status,open_options; enum enum_thread_type system_thread; uint select_number; //number of select (used for EXPLAIN) - /* variables.transaction_isolation is reset to this after each commit */ - enum_tx_isolation session_tx_isolation; + /* + Current or next transaction isolation level. + When a connection is established, the value is taken from + @@session.tx_isolation (default transaction isolation for + the session), which is in turn taken from @@global.tx_isolation + (the global value). + If there is no transaction started, this variable + holds the value of the next transaction's isolation level. + When a transaction starts, the value stored in this variable + becomes "actual". + At transaction commit or rollback, we assign this variable + again from @@session.tx_isolation. + The only statement that can otherwise change the value + of this variable is SET TRANSACTION ISOLATION LEVEL. + Its purpose is to effect the isolation level of the next + transaction in this session. When this statement is executed, + the value in this variable is changed. However, since + this statement is only allowed when there is no active + transaction, this assignment (naturally) only affects the + upcoming transaction. + At the end of the current active transaction the value is + be reset again from @@session.tx_isolation, as described + above. + */ + enum_tx_isolation tx_isolation; enum_check_fields count_cuted_fields; DYNAMIC_ARRAY user_var_events; /* For user variables replication */ @@ -2843,6 +2700,12 @@ public: void set_query_and_id(char *query_arg, uint32 query_length_arg, query_id_t new_query_id); void set_query_id(query_id_t new_query_id); + void set_open_tables(TABLE *open_tables_arg) + { + mysql_mutex_lock(&LOCK_thd_data); + open_tables= open_tables_arg; + mysql_mutex_unlock(&LOCK_thd_data); + } void enter_locked_tables_mode(enum_locked_tables_mode mode_arg) { DBUG_ASSERT(locked_tables_mode == LTM_NONE); diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 7b0ef3eb226..35ba39afd81 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -1085,7 +1085,6 @@ static void prepare_new_connection_state(THD* thd) embedded server library. TODO: refactor this to avoid code duplication there */ - thd->version= refresh_version; thd->proc_info= 0; thd->command= COM_SLEEP; thd->set_time(); diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index 59bf0764ada..ca724ec262f 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -357,7 +357,7 @@ void Sensitive_cursor::reset_thd(THD *thd) { thd->derived_tables= 0; - thd->open_tables= 0; + thd->set_open_tables(NULL); thd->lock= 0; thd->free_list= 0; thd->change_list.empty(); @@ -436,7 +436,7 @@ Sensitive_cursor::fetch(ulong num_rows) thd->lock == 0); thd->derived_tables= derived_tables; - thd->open_tables= open_tables; + thd->set_open_tables(open_tables); thd->lock= lock; thd->set_query_id(query_id); change_list.move_elements_to(&thd->change_list); @@ -519,14 +519,14 @@ Sensitive_cursor::close() TABLE *tmp_derived_tables= thd->derived_tables; MYSQL_LOCK *tmp_lock= thd->lock; - thd->open_tables= open_tables; + thd->set_open_tables(open_tables); thd->derived_tables= derived_tables; thd->lock= lock; /* Is expected to at least close tables and empty thd->change_list */ stmt_arena->cleanup_stmt(); - thd->open_tables= tmp_derived_tables; + thd->set_open_tables(tmp_derived_tables); thd->derived_tables= tmp_derived_tables; thd->lock= tmp_lock; } diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 15fdd842e34..2e48475f298 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1203,6 +1203,8 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, table_list->alias= table_list->table_name; // If lower_case_table_names=2 table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix); + table_list->mdl_request.init(MDL_key::TABLE, table_list->db, + table_list->table_name, MDL_EXCLUSIVE); /* Link into list */ (*tot_list_next)= table_list; tot_list_next= &table_list->next_local; @@ -1918,9 +1920,11 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) Table_ident *new_ident= new Table_ident(thd, new_db, table_str, 0); if (!old_ident || !new_ident || !sl->add_table_to_list(thd, old_ident, NULL, - TL_OPTION_UPDATING, TL_IGNORE) || + TL_OPTION_UPDATING, TL_IGNORE, + MDL_EXCLUSIVE) || !sl->add_table_to_list(thd, new_ident, NULL, - TL_OPTION_UPDATING, TL_IGNORE)) + TL_OPTION_UPDATING, TL_IGNORE, + MDL_EXCLUSIVE)) { error= 1; my_dirend(dirp); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index d71d5c56980..c4a773fee9c 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -14,7 +14,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* - Delete of records and truncate of tables. + Delete of records tables. Multi-table deletes were introduced by Monty and Sinisa */ @@ -47,8 +47,7 @@ */ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order, ha_rows limit, ulonglong options, - bool reset_auto_increment) + SQL_I_List<ORDER> *order, ha_rows limit, ulonglong options) { bool will_batch; int error, loc_error; @@ -59,17 +58,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, bool transactional_table, safe_update, const_cond; bool const_cond_result; ha_rows deleted= 0; - bool triggers_applicable; uint usable_index= MAX_KEY; SELECT_LEX *select_lex= &thd->lex->select_lex; THD::killed_state killed_status= THD::NOT_KILLED; + THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; DBUG_ENTER("mysql_delete"); - bool save_binlog_row_based; - - THD::enum_binlog_query_type query_type= - thd->lex->sql_command == SQLCOM_TRUNCATE ? - THD::STMT_QUERY_TYPE : - THD::ROW_QUERY_TYPE; if (open_and_lock_tables(thd, table_list, TRUE, 0)) DBUG_RETURN(TRUE); @@ -129,25 +122,20 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, any side-effects (because of triggers), so we can use optimized handler::delete_all_rows() method. - We implement fast TRUNCATE for InnoDB even if triggers are - present. TRUNCATE ignores triggers. - We can use delete_all_rows() if and only if: - We allow new functions (not using option --skip-new), and are not in safe mode (not using option --safe-mode) - There is no limit clause - The condition is constant - If there is a condition, then it it produces a non-zero value - - If the current command is DELETE FROM with no where clause - (i.e., not TRUNCATE) then: - - We should not be binlogging this statement row-based, and + - If the current command is DELETE FROM with no where clause, then: + - We should not be binlogging this statement in row-based, and - there should be no delete triggers associated with the table. */ if (!using_limit && const_cond_result && !(specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE)) && - (thd->lex->sql_command == SQLCOM_TRUNCATE || (!thd->is_current_stmt_binlog_format_row() && - !(table->triggers && table->triggers->has_delete_triggers())))) + !(table->triggers && table->triggers->has_delete_triggers()))) { /* Update the table->file->stats.records number */ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); @@ -160,16 +148,14 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, query in row format, so we have to log it in statement format. */ query_type= THD::STMT_QUERY_TYPE; - error= -1; // ok + error= -1; deleted= maybe_deleted; - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); goto cleanup; } if (error != HA_ERR_WRONG_COMMAND) { table->file->print_error(error,MYF(0)); error=0; - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); goto cleanup; } /* Handler didn't support fast delete; Delete rows one by one */ @@ -212,11 +198,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (thd->is_error()) DBUG_RETURN(TRUE); my_ok(thd, 0); - /* - We don't need to call reset_auto_increment in this case, because - mysql_truncate always gives a NULL conds argument, hence we never - get here. - */ DBUG_RETURN(0); // Nothing to delete } @@ -287,12 +268,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, init_ftfuncs(thd, select_lex, 1); thd_proc_info(thd, "updating"); - /* NOTE: TRUNCATE must not invoke triggers. */ - - triggers_applicable= table->triggers && - thd->lex->sql_command != SQLCOM_TRUNCATE; - - if (triggers_applicable && + if (table->triggers && table->triggers->has_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER)) { @@ -310,11 +286,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, table->mark_columns_needed_for_delete(); - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - if (thd->lex->sql_command == SQLCOM_TRUNCATE && - thd->is_current_stmt_binlog_format_row()) - thd->clear_current_stmt_binlog_format_row(); - while (!(error=info.read_record(&info)) && !thd->killed && ! thd->is_error()) { @@ -323,7 +294,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (!(select && select->skip_record())&& ! thd->is_error() ) { - if (triggers_applicable && + if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE)) { @@ -334,7 +305,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (!(error= table->file->ha_delete_row(table->record[0]))) { deleted++; - if (triggers_applicable && + if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE)) { @@ -379,21 +350,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_NORMAL); - if (reset_auto_increment && (error < 0)) - { - /* - We're really doing a truncate and need to reset the table's - auto-increment counter. - */ - int error2= table->file->ha_reset_auto_increment(0); - - if (error2 && (error2 != HA_ERR_WRONG_COMMAND)) - { - table->file->print_error(error2, MYF(0)); - error= 1; - } - } - cleanup: /* Invalidate the table in the query cache if something changed. This must @@ -414,34 +370,24 @@ cleanup: /* See similar binlogging code in sql_update.cc, for comments */ if ((error < 0) || thd->transaction.stmt.modified_non_trans_table) { - if (mysql_bin_log.is_open() && - !(thd->lex->sql_command == SQLCOM_TRUNCATE && - thd->is_current_stmt_binlog_format_row() && - find_temporary_table(thd, table_list))) + if (mysql_bin_log.is_open()) { - bool const is_trans= - thd->lex->sql_command == SQLCOM_TRUNCATE ? - FALSE : - transactional_table; - int errcode= 0; if (error < 0) thd->clear_error(); else errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - + /* [binlog]: If 'handler::delete_all_rows()' was called and the storage engine does not inject the rows itself, we replicate statement-based; otherwise, 'ha_delete_row()' was used to delete specific rows which we might log row-based. - - Note that TRUNCATE TABLE is not transactional and should - therefore be treated as a DDL. */ int log_result= thd->binlog_query(query_type, thd->query(), thd->query_length(), - is_trans, FALSE, FALSE, errcode); + transactional_table, FALSE, FALSE, + errcode); if (log_result) { @@ -449,18 +395,12 @@ cleanup: } } } - if (save_binlog_row_based) - thd->set_current_stmt_binlog_format_row(); DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table); free_underlaid_joins(thd, select_lex); if (error < 0 || (thd->lex->ignore && !thd->is_error() && !thd->is_fatal_error)) { - /* - If a TRUNCATE TABLE was issued, the number of rows should be reported as - zero since the exact number is unknown. - */ - my_ok(thd, reset_auto_increment ? 0 : deleted); + my_ok(thd, deleted); DBUG_PRINT("info",("%ld records deleted",(long) deleted)); } DBUG_RETURN(error >= 0 || thd->is_error()); @@ -1062,227 +1002,3 @@ bool multi_delete::send_eof() return 0; } - -/*************************************************************************** - TRUNCATE TABLE -****************************************************************************/ - -/* - Row-by-row truncation if the engine does not support table recreation. - Probably a InnoDB table. -*/ - -static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) -{ - bool error; - DBUG_ENTER("mysql_truncate_by_delete"); - table_list->lock_type= TL_WRITE; - table_list->mdl_request.set_type(MDL_SHARED_WRITE); - mysql_init_select(thd->lex); - /* Delete all rows from table */ - error= mysql_delete(thd, table_list, NULL, NULL, HA_POS_ERROR, LL(0), TRUE); - /* - All effects of a TRUNCATE TABLE operation are rolled back if a row by row - deletion fails. Otherwise, operation is automatically committed at the end. - */ - if (error) - { - DBUG_ASSERT(thd->stmt_da->is_error()); - trans_rollback_stmt(thd); - trans_rollback(thd); - } - DBUG_RETURN(error); -} - - -/* - Optimize delete of all rows by doing a full generate of the table - This will work even if the .ISM and .ISD tables are destroyed - - dont_send_ok should be set if: - - We should always wants to generate the table (even if the table type - normally can't safely do this. - - We don't want an ok to be sent to the end user. - - We don't want to log the truncate command - - If we want to keep exclusive metadata lock on the table (obtained by - caller) on exit without errors. -*/ - -bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) -{ - HA_CREATE_INFO create_info; - char path[FN_REFLEN + 1]; - TABLE *table; - bool error= TRUE; - uint path_length; - /* - Is set if we're under LOCK TABLES, and used - to downgrade the exclusive lock after the - table was truncated. - */ - MDL_ticket *mdl_ticket= NULL; - bool has_mdl_lock= FALSE; - bool is_temporary_table= false; - DBUG_ENTER("mysql_truncate"); - - bzero((char*) &create_info,sizeof(create_info)); - - /* Remove tables from the HANDLER's hash. */ - mysql_ha_rm_tables(thd, table_list); - - /* If it is a temporary table, close and regenerate it */ - if (!dont_send_ok && (table= find_temporary_table(thd, table_list))) - { - is_temporary_table= true; - handlerton *table_type= table->s->db_type(); - TABLE_SHARE *share= table->s; - /* Note that a temporary table cannot be partitioned */ - if (!ha_check_storage_engine_flag(table_type, HTON_CAN_RECREATE)) - goto trunc_by_del; - - table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK); - - close_temporary_table(thd, table, 0, 0); // Don't free share - ha_create_table(thd, share->normalized_path.str, - share->db.str, share->table_name.str, &create_info, 1); - // We don't need to call invalidate() because this table is not in cache - if ((error= (int) !(open_temporary_table(thd, share->path.str, - share->db.str, - share->table_name.str, 1)))) - (void) rm_temporary_table(table_type, path); - else - thd->thread_specific_used= TRUE; - - free_table_share(share); - my_free((char*) table,MYF(0)); - /* - If we return here we will not have logged the truncation to the bin log - and we will not my_ok() to the client. - */ - goto end; - } - - path_length= build_table_filename(path, sizeof(path) - 1, table_list->db, - table_list->table_name, reg_ext, 0); - - if (!dont_send_ok) - { - enum legacy_db_type table_type; - /* - FIXME: Code of TRUNCATE breaks the meta-data - locking protocol since it tries to find out the table storage - engine and therefore accesses table in some way without holding - any kind of meta-data lock. - */ - mysql_frm_type(thd, path, &table_type); - if (table_type == DB_TYPE_UNKNOWN) - { - my_error(ER_NO_SUCH_TABLE, MYF(0), - table_list->db, table_list->table_name); - DBUG_RETURN(TRUE); - } -#ifdef WITH_PARTITION_STORAGE_ENGINE - /* - TODO: Add support for TRUNCATE PARTITION for NDB and other engines - supporting native partitioning - */ - if (table_type != DB_TYPE_PARTITION_DB && - thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) - { - my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); - DBUG_RETURN(TRUE); - } -#endif - if (!ha_check_storage_engine_flag(ha_resolve_by_legacy_type(thd, - table_type), - HTON_CAN_RECREATE) || - thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) - goto trunc_by_del; - - - if (thd->locked_tables_mode) - { - if (!(table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db, - table_list->table_name, FALSE))) - DBUG_RETURN(TRUE); - mdl_ticket= table->mdl_ticket; - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) - goto end; - close_all_tables_for_name(thd, table->s, FALSE); - } - else - { - MDL_request mdl_global_request, mdl_request; - MDL_request_list mdl_requests; - /* - Even though we could use the previous execution branch - here just as well, we must not try to open the table: - MySQL manual documents that TRUNCATE can be used to - repair a damaged table, i.e. a table that can not be - fully "opened". In particular MySQL manual says: - - As long as the table format file tbl_name.frm is valid, - the table can be re-created as an empty table with TRUNCATE - TABLE, even if the data or index files have become corrupted. - */ - - mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); - mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, - MDL_EXCLUSIVE); - mdl_requests.push_front(&mdl_request); - mdl_requests.push_front(&mdl_global_request); - - if (thd->mdl_context.acquire_locks(&mdl_requests, - thd->variables.lock_wait_timeout)) - DBUG_RETURN(TRUE); - - has_mdl_lock= TRUE; - mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, - table_list->table_name); - mysql_mutex_unlock(&LOCK_open); - } - } - - /* - Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this - crashes, replacement works. *(path + path_length - reg_ext_length)= - '\0'; - */ - path[path_length - reg_ext_length] = 0; - mysql_mutex_lock(&LOCK_open); - error= ha_create_table(thd, path, table_list->db, table_list->table_name, - &create_info, 1); - mysql_mutex_unlock(&LOCK_open); - query_cache_invalidate3(thd, table_list, 0); - -end: - if (!dont_send_ok) - { - if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd)) - thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); - /* - Even if we failed to reopen some tables, - the operation itself succeeded, write the binlog. - */ - if (!error) - { - /* In RBR, the statement is not binlogged if the table is temporary. */ - if (!is_temporary_table || !thd->is_current_stmt_binlog_format_row()) - error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - if (!error) - my_ok(thd); // This should return record count - } - if (has_mdl_lock) - thd->mdl_context.release_transactional_locks(); - if (mdl_ticket) - mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); - } - - DBUG_PRINT("exit", ("error: %d", error)); - DBUG_RETURN(error); - -trunc_by_del: - error= mysql_truncate_by_delete(thd, table_list); - DBUG_RETURN(error); -} diff --git a/sql/sql_delete.h b/sql/sql_delete.h index 0e09120f557..264991c220b 100644 --- a/sql/sql_delete.h +++ b/sql/sql_delete.h @@ -27,8 +27,6 @@ template <typename T> class SQL_I_List; int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds); bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order, ha_rows rows, ulonglong options, - bool reset_auto_increment); -bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok); + SQL_I_List<ORDER> *order, ha_rows rows, ulonglong options); #endif /* SQL_DELETE_INCLUDED */ diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index b9cc0ef477d..d6f2a472e05 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -131,13 +131,11 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) /* Non temporary table. */ tables->table->file->ha_index_or_rnd_end(); tables->table->open_by_handler= 0; - mysql_mutex_lock(&LOCK_open); if (close_thread_table(thd, &tables->table)) { /* Tell threads waiting for refresh that something has happened */ broadcast_refresh(); } - mysql_mutex_unlock(&LOCK_open); thd->mdl_context.release_lock(tables->mdl_request.ticket); } else if (tables->table) @@ -278,7 +276,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) See open_table() back-off comments for more details. */ backup_open_tables= thd->open_tables; - thd->open_tables= NULL; + thd->set_open_tables(NULL); mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* @@ -312,7 +310,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) if (error) { close_thread_tables(thd); - thd->open_tables= backup_open_tables; + thd->set_open_tables(backup_open_tables); thd->mdl_context.rollback_to_savepoint(mdl_savepoint); if (!reopen) my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); @@ -325,7 +323,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) DBUG_PRINT("exit",("ERROR")); DBUG_RETURN(TRUE); } - thd->open_tables= backup_open_tables; + thd->set_open_tables(backup_open_tables); if (hash_tables->mdl_request.ticket) { thd->mdl_context. @@ -559,7 +557,7 @@ retry: mysql_lock_tables() needs thd->open_tables to be set correctly to be able to handle aborts properly. */ - thd->open_tables= hash_tables->table; + thd->set_open_tables(hash_tables->table); sql_handler_lock_error.init(); @@ -575,7 +573,7 @@ retry: */ DBUG_ASSERT(hash_tables->table == thd->open_tables); /* Restore previous context. */ - thd->open_tables= backup_open_tables; + thd->set_open_tables(backup_open_tables); if (sql_handler_lock_error.need_reopen()) { @@ -853,6 +851,35 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables) /** + Close cursors of matching tables from the HANDLER's hash table. + + @param thd Thread identifier. + @param tables The list of tables to flush. +*/ + +void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables) +{ + DBUG_ENTER("mysql_ha_flush_tables"); + + for (TABLE_LIST *table_list= all_tables; table_list; + table_list= table_list->next_global) + { + TABLE_LIST *hash_tables= mysql_ha_find(thd, table_list); + /* Close all aliases of the same table. */ + while (hash_tables) + { + TABLE_LIST *next_local= hash_tables->next_local; + if (hash_tables->table) + mysql_ha_close_table(thd, hash_tables); + hash_tables= next_local; + } + } + + DBUG_VOID_RETURN; +} + + +/** Flush (close and mark for re-open) all tables that should be should be reopen. diff --git a/sql/sql_handler.h b/sql/sql_handler.h index 8666d5a8d7b..c5da3c4d468 100644 --- a/sql/sql_handler.h +++ b/sql/sql_handler.h @@ -28,6 +28,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables); bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *, List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows); void mysql_ha_flush(THD *thd); +void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables); void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables); void mysql_ha_cleanup(THD *thd); void mysql_ha_move_tickets_after_trans_sentinel(THD *thd); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index d40f0dcb410..24a418f8f25 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1814,7 +1814,6 @@ public: thd.security_ctx->user=thd.security_ctx->priv_user=(char*) delayed_user; thd.security_ctx->host=(char*) my_localhost; thd.current_tablenr=0; - thd.version=refresh_version; thd.command=COM_DELAYED_INSERT; thd.lex->current_select= 0; // for my_message_sql thd.lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock() @@ -3608,13 +3607,12 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); /* Here we open the destination table, on which we already have an exclusive metadata lock. */ - if (open_table(thd, create_table, thd->mem_root, - &ot_ctx_unused, MYSQL_OPEN_REOPEN)) + if (open_table(thd, create_table, thd->mem_root, &ot_ctx)) { mysql_mutex_lock(&LOCK_open); quick_rm_table(create_info->db_type, create_table->db, @@ -3627,9 +3625,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, } else { - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); - if (open_table(thd, create_table, thd->mem_root, &ot_ctx_unused, - MYSQL_OPEN_TEMPORARY_ONLY)) + Open_table_context ot_ctx(thd, MYSQL_OPEN_TEMPORARY_ONLY); + if (open_table(thd, create_table, thd->mem_root, &ot_ctx)) { /* This shouldn't happen as creation of temporary table should make diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 16b4c727689..5f8b1148dcb 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -443,8 +443,11 @@ void lex_end(LEX *lex) DBUG_PRINT("enter", ("lex: 0x%lx", (long) lex)); /* release used plugins */ - plugin_unlock_list(0, (plugin_ref*)lex->plugins.buffer, - lex->plugins.elements); + if (lex->plugins.elements) /* No function call and no mutex if no plugins. */ + { + plugin_unlock_list(0, (plugin_ref*)lex->plugins.buffer, + lex->plugins.elements); + } reset_dynamic(&lex->plugins); DBUG_VOID_RETURN; @@ -1986,6 +1989,7 @@ TABLE_LIST *st_select_lex_node::add_table_to_list (THD *thd, Table_ident *table, LEX_STRING *alias, ulong table_join_options, thr_lock_type flags, + enum_mdl_type mdl_type, List<Index_hint> *hints, LEX_STRING *option) { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 985edd42496..05af1237be8 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -502,6 +502,7 @@ public: LEX_STRING *alias, ulong table_options, thr_lock_type flags= TL_UNLOCK, + enum_mdl_type mdl_type= MDL_SHARED_READ, List<Index_hint> *hints= 0, LEX_STRING *option= 0); virtual void set_lock_for_tables(thr_lock_type lock_type) {} @@ -799,6 +800,7 @@ public: LEX_STRING *alias, ulong table_options, thr_lock_type flags= TL_UNLOCK, + enum_mdl_type mdl_type= MDL_SHARED_READ, List<Index_hint> *hints= 0, LEX_STRING *option= 0); TABLE_LIST* get_table_list(); @@ -1989,7 +1991,7 @@ struct LEX: public Query_tables_list bool autocommit; bool verbose, no_write_to_binlog; - bool tx_chain, tx_release; + enum enum_yes_no_unknown tx_chain, tx_release; /* Special JOIN::prepare mode: changing of query is prohibited. When creating a view, we need to just check its syntax omitting @@ -2266,6 +2268,7 @@ public: yacc_yyvs= NULL; m_set_signal_info.clear(); m_lock_type= TL_READ_DEFAULT; + m_mdl_type= MDL_SHARED_READ; } ~Yacc_state(); @@ -2277,6 +2280,7 @@ public: void reset_before_substatement() { m_lock_type= TL_READ_DEFAULT; + m_mdl_type= MDL_SHARED_READ; } /** @@ -2316,6 +2320,12 @@ public: */ thr_lock_type m_lock_type; + /** + The type of requested metadata lock for tables added to + the statement table list. + */ + enum_mdl_type m_mdl_type; + /* TODO: move more attributes from the LEX structure here. */ diff --git a/sql/sql_manager.cc b/sql/sql_manager.cc index e9c9402a89a..2189b1e124f 100644 --- a/sql/sql_manager.cc +++ b/sql/sql_manager.cc @@ -108,7 +108,7 @@ pthread_handler_t handle_manager(void *arg __attribute__((unused))) if (error == ETIMEDOUT || error == ETIME) { - flush_tables(); + tdc_flush_unused_tables(); error = 0; reset_flush_time = TRUE; } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index bdee13372d5..273eadf4205 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -49,6 +49,7 @@ // mysql_recreate_table, // mysql_backup_table, // mysql_restore_table +#include "sql_truncate.h" // mysql_truncate_table #include "sql_connect.h" // check_user, // decrease_user_connections, // thd_init_client_charset, check_mqh, @@ -264,7 +265,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS; + CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL | @@ -496,7 +497,6 @@ static void handle_bootstrap_impl(THD *thd) #endif /* EMBEDDED_LIBRARY */ thd_proc_info(thd, 0); - thd->version=refresh_version; thd->security_ctx->priv_user= thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME)); thd->security_ctx->priv_host[0]=0; @@ -1674,7 +1674,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, /* 'parent_lex' is used in init_query() so it must be before it. */ schema_select_lex->parent_lex= lex; schema_select_lex->init_query(); - if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ)) + if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ, + MDL_SHARED_READ)) DBUG_RETURN(1); lex->query_tables_last= query_tables_last; break; @@ -2612,7 +2613,7 @@ case SQLCOM_PREPARE: /* Set strategies: reset default or 'prepared' values. */ create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL; + create_table->lock_strategy= TABLE_LIST::OTLS_DOWNGRADE_IF_EXISTS; /* Close any open handlers for the table @@ -3350,9 +3351,8 @@ end_with_restore_list: ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); goto error; } - if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - goto error; - res= mysql_truncate(thd, first_table, 0); + if (! (res= mysql_truncate_table(thd, first_table))) + my_ok(thd); break; case SQLCOM_DELETE: { @@ -3365,8 +3365,7 @@ end_with_restore_list: MYSQL_DELETE_START(thd->query()); res = mysql_delete(thd, all_tables, select_lex->where, &select_lex->order_list, - unit->select_limit_cnt, select_lex->options, - FALSE); + unit->select_limit_cnt, select_lex->options); MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); break; } @@ -3572,16 +3571,13 @@ end_with_restore_list: thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) goto error; - init_mdl_requests(all_tables); - thd->variables.option_bits|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; { Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; - res= (open_and_lock_tables(thd, all_tables, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + res= (open_and_lock_tables(thd, all_tables, FALSE, 0, &lock_tables_prelocking_strategy) || thd->locked_tables_list.init_locked_tables(thd)); } @@ -4103,33 +4099,65 @@ end_with_restore_list: my_ok(thd); break; case SQLCOM_COMMIT: + { DBUG_ASSERT(thd->lock == NULL || thd->locked_tables_mode == LTM_LOCK_TABLES); + bool tx_chain= (lex->tx_chain == TVL_YES || + (thd->variables.completion_type == 1 && + lex->tx_chain != TVL_NO)); + bool tx_release= (lex->tx_release == TVL_YES || + (thd->variables.completion_type == 2 && + lex->tx_release != TVL_NO)); if (trans_commit(thd)) goto error; thd->mdl_context.release_transactional_locks(); /* Begin transaction with the same isolation level. */ - if (lex->tx_chain && trans_begin(thd)) + if (tx_chain) + { + if (trans_begin(thd)) goto error; + } + else + { + /* Reset the isolation level if no chaining transaction. */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + } /* Disconnect the current client connection. */ - if (lex->tx_release) + if (tx_release) thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; + } case SQLCOM_ROLLBACK: + { DBUG_ASSERT(thd->lock == NULL || thd->locked_tables_mode == LTM_LOCK_TABLES); + bool tx_chain= (lex->tx_chain == TVL_YES || + (thd->variables.completion_type == 1 && + lex->tx_chain != TVL_NO)); + bool tx_release= (lex->tx_release == TVL_YES || + (thd->variables.completion_type == 2 && + lex->tx_release != TVL_NO)); if (trans_rollback(thd)) goto error; thd->mdl_context.release_transactional_locks(); /* Begin transaction with the same isolation level. */ - if (lex->tx_chain && trans_begin(thd)) - goto error; + if (tx_chain) + { + if (trans_begin(thd)) + goto error; + } + else + { + /* Reset the isolation level if no chaining transaction. */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + } /* Disconnect the current client connection. */ - if (lex->tx_release) + if (tx_release) thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; + } case SQLCOM_RELEASE_SAVEPOINT: if (trans_release_savepoint(thd, lex->ident)) goto error; @@ -4632,12 +4660,22 @@ create_sp_error: if (trans_xa_commit(thd)) goto error; thd->mdl_context.release_transactional_locks(); + /* + We've just done a commit, reset transaction + isolation level to the session default. + */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; my_ok(thd); break; case SQLCOM_XA_ROLLBACK: if (trans_xa_rollback(thd)) goto error; thd->mdl_context.release_transactional_locks(); + /* + We've just done a rollback, reset transaction + isolation level to the session default. + */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; my_ok(thd); break; case SQLCOM_XA_RECOVER: @@ -6087,6 +6125,7 @@ bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *item,bool asc) - TL_OPTION_FORCE_INDEX : Force usage of index - TL_OPTION_ALIAS : an alias in multi table DELETE @param lock_type How table should be locked + @param mdl_type Type of metadata lock to acquire on the table. @param use_index List of indexed used in USE INDEX @param ignore_index List of indexed used in IGNORE INDEX @@ -6101,6 +6140,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, LEX_STRING *alias, ulong table_options, thr_lock_type lock_type, + enum_mdl_type mdl_type, List<Index_hint> *index_hints_arg, LEX_STRING *option) { @@ -6248,9 +6288,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, - (ptr->lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type); DBUG_RETURN(ptr); } diff --git a/sql/sql_parse.h b/sql/sql_parse.h index de0a3035e9a..df76df72e09 100644 --- a/sql/sql_parse.h +++ b/sql/sql_parse.h @@ -130,11 +130,6 @@ bool check_simple_select(); Item *negate_expression(THD *thd, Item *expr); bool check_stack_overrun(THD *thd, long margin, uchar *dummy); -bool begin_trans(THD *thd); -bool end_active_trans(THD *thd); -int end_trans(THD *thd, enum enum_mysql_completiontype completion); - - /* Variables */ extern const char* any_db; diff --git a/sql/sql_plist.h b/sql/sql_plist.h index eb239a63467..db85266be15 100644 --- a/sql/sql_plist.h +++ b/sql/sql_plist.h @@ -18,8 +18,10 @@ #include <my_global.h> -template <typename T, typename B, typename C> class I_P_List_iterator; +template <typename T, typename B, typename C, typename I> +class I_P_List_iterator; class I_P_List_null_counter; +template <typename T> class I_P_List_no_push_back; /** @@ -52,12 +54,19 @@ class I_P_List_null_counter; should be done. Instance of this class is also used as a place where information about number of list elements is stored. @sa I_P_List_null_counter, I_P_List_counter + @param I Policy class specifying whether I_P_List should support + efficient push_back() operation. Instance of this class + is used as place where we store information to support + this operation. + @sa I_P_List_no_push_back, I_P_List_fast_push_back. */ -template <typename T, typename B, typename C = I_P_List_null_counter> -class I_P_List : public C +template <typename T, typename B, + typename C = I_P_List_null_counter, + typename I = I_P_List_no_push_back<T> > +class I_P_List : public C, public I { - T *first; + T *m_first; /* Do not prohibit copying of I_P_List object to simplify their usage in @@ -65,31 +74,27 @@ class I_P_List : public C is a bad idea. */ public: - I_P_List() : first(NULL) { }; - inline void empty() { first= NULL; C::reset(); } - inline bool is_empty() const { return (first == NULL); } + I_P_List() : I(&m_first), m_first(NULL) {}; + inline void empty() { m_first= NULL; C::reset(); I::set_last(&m_first); } + inline bool is_empty() const { return (m_first == NULL); } inline void push_front(T* a) { - *B::next_ptr(a)= first; - if (first) - *B::prev_ptr(first)= B::next_ptr(a); - first= a; - *B::prev_ptr(a)= &first; + *B::next_ptr(a)= m_first; + if (m_first) + *B::prev_ptr(m_first)= B::next_ptr(a); + else + I::set_last(B::next_ptr(a)); + m_first= a; + *B::prev_ptr(a)= &m_first; C::inc(); } inline void push_back(T *a) { - insert_after(back(), a); - } - inline T *back() - { - T *t= front(); - if (t) - { - while (*B::next_ptr(t)) - t= *B::next_ptr(t); - } - return t; + T **last= I::get_last(); + *B::next_ptr(a)= *last; + *last= a; + *B::prev_ptr(a)= last; + I::set_last(B::next_ptr(a)); } inline void insert_after(T *pos, T *a) { @@ -105,6 +110,8 @@ public: T *old_next= *B::next_ptr(a); *B::prev_ptr(old_next)= B::next_ptr(a); } + else + I::set_last(B::next_ptr(a)); } } inline void remove(T *a) @@ -112,24 +119,31 @@ public: T *next= *B::next_ptr(a); if (next) *B::prev_ptr(next)= *B::prev_ptr(a); + else + I::set_last(*B::prev_ptr(a)); **B::prev_ptr(a)= next; C::dec(); } - inline T* front() { return first; } - inline const T *front() const { return first; } + inline T* front() { return m_first; } + inline const T *front() const { return m_first; } void swap(I_P_List<T, B, C> &rhs) { - swap_variables(T *, first, rhs.first); - if (first) - *B::prev_ptr(first)= &first; - if (rhs.first) - *B::prev_ptr(rhs.first)= &rhs.first; + swap_variables(T *, m_first, rhs.m_first); + I::swap(rhs); + if (m_first) + *B::prev_ptr(m_first)= &m_first; + else + I::set_last(&m_first); + if (rhs.m_first) + *B::prev_ptr(rhs.m_first)= &rhs.m_first; + else + I::set_last(&rhs.m_first); C::swap(rhs); } #ifndef _lint - friend class I_P_List_iterator<T, B, C>; + friend class I_P_List_iterator<T, B, C, I>; #endif - typedef I_P_List_iterator<T, B, C> Iterator; + typedef I_P_List_iterator<T, B, C, I> Iterator; }; @@ -137,18 +151,22 @@ public: Iterator for I_P_List. */ -template <typename T, typename B, typename C = I_P_List_null_counter> +template <typename T, typename B, + typename C = I_P_List_null_counter, + typename I = I_P_List_no_push_back<T> > class I_P_List_iterator { - const I_P_List<T, B, C> *list; + const I_P_List<T, B, C, I> *list; T *current; public: - I_P_List_iterator(const I_P_List<T, B, C> &a) : list(&a), current(a.first) {} - I_P_List_iterator(const I_P_List<T, B, C> &a, T* current_arg) : list(&a), current(current_arg) {} - inline void init(const I_P_List<T, B, C> &a) + I_P_List_iterator(const I_P_List<T, B, C, I> &a) + : list(&a), current(a.m_first) {} + I_P_List_iterator(const I_P_List<T, B, C, I> &a, T* current_arg) + : list(&a), current(current_arg) {} + inline void init(const I_P_List<T, B, C, I> &a) { list= &a; - current= a.first; + current= a.m_first; } inline T* operator++(int) { @@ -164,7 +182,7 @@ public: } inline void rewind() { - current= list->first; + current= list->m_first; } }; @@ -203,4 +221,40 @@ public: uint elements() const { return m_counter; } }; + +/** + A null insertion policy class for I_P_List to be used + in cases when push_back() operation is not necessary. +*/ + +template <typename T> class I_P_List_no_push_back +{ +protected: + I_P_List_no_push_back(T **a) {}; + void set_last(T **a) {} + /* + T** get_last() const method is intentionally left unimplemented + in order to prohibit usage of push_back() method in lists which + use this policy. + */ + void swap(I_P_List_no_push_back<T> &rhs) {} +}; + + +/** + An insertion policy class for I_P_List which can + be used when fast push_back() operation is required. +*/ + +template <typename T> class I_P_List_fast_push_back +{ + T **m_last; +protected: + I_P_List_fast_push_back(T **a) : m_last(a) { }; + void set_last(T **a) { m_last= a; } + T** get_last() const { return m_last; } + void swap(I_P_List_fast_push_back<T> &rhs) + { swap_variables(T**, m_last, rhs.m_last); } +}; + #endif diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index c87da592210..97c480ea0bd 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -261,11 +261,6 @@ static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref plugin static void intern_plugin_unlock(LEX *lex, plugin_ref plugin); static void reap_plugins(void); -#ifdef EMBEDDED_LIBRARY -/* declared in sql_base.cc */ -extern bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists); -#endif /* EMBEDDED_LIBRARY */ - static void report_error(int where_to, uint error, ...) { va_list args; @@ -1475,10 +1470,8 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) When building an embedded library, if the mysql.plugin table does not exist, we silently ignore the missing table */ - mysql_mutex_lock(&LOCK_open); if (check_if_table_exists(new_thd, &tables, &table_exists)) table_exists= FALSE; - mysql_mutex_unlock(&LOCK_open); if (!table_exists) goto end; #endif /* EMBEDDED_LIBRARY */ @@ -1519,7 +1512,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) if (error > 0) sql_print_error(ER(ER_GET_ERRNO), my_errno); end_read_record(&read_record_info); - new_thd->version--; // Force close to free memory + table->m_needs_reopen= TRUE; // Force close to free memory end: close_thread_tables(new_thd); /* Remember that we don't have a THD */ diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 296fcf301d3..09ae0acc6ad 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1690,7 +1690,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) for the prepare phase. */ create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::SHARED_MDL; + create_table->lock_strategy= TABLE_LIST::OTLS_NONE; if (select_lex->item_list.elements) { diff --git a/sql/sql_priv.h b/sql/sql_priv.h index 8601b10b9bf..604890ffbe5 100644 --- a/sql/sql_priv.h +++ b/sql/sql_priv.h @@ -211,6 +211,11 @@ enum enum_var_type class sys_var; +enum enum_yes_no_unknown +{ + TVL_YES, TVL_NO, TVL_UNKNOWN +}; + #ifdef MYSQL_SERVER #endif /* MYSQL_SERVER */ diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index ea95b59b0c2..130a99a374f 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -29,6 +29,7 @@ // start_waiting_global_read_lock #include "sql_base.h" // tdc_remove_table #include "sql_handler.h" // mysql_ha_rm_tables +#include "datadict.h" static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error); @@ -283,7 +284,7 @@ do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, build_table_filename(name, sizeof(name) - 1, ren_table->db, old_alias, reg_ext, 0); - frm_type= mysql_frm_type(thd, name, &table_type); + frm_type= dd_frm_type(thd, name, &table_type); switch (frm_type) { case FRMTYPE_TABLE: diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 99073ced693..f1d7e48ffcc 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -18,7 +18,6 @@ #include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ #include "sql_priv.h" -#include "debug_sync.h" #include "unireg.h" #include "sql_acl.h" // fill_schema_*_privileges #include "sql_select.h" // For select_describe @@ -28,7 +27,6 @@ // primary_key_name, // build_table_filename #include "repl_failsafe.h" -#include "sql_view.h" // mysql_frm_type #include "sql_parse.h" // check_access, check_table_access #include "sql_partition.h" // partition_element #include "sql_db.h" // check_db_dir_existence, load_db_opt_by_name @@ -50,8 +48,9 @@ #include "event_data_objects.h" #endif #include <my_dir.h> -#include "debug_sync.h" #include "lock.h" // MYSQL_OPEN_IGNORE_FLUSH +#include "debug_sync.h" +#include "datadict.h" // dd_frm_type() #define STR_OR_NIL(S) ((S) ? (S) : "<nil>") @@ -2366,7 +2365,7 @@ int make_table_list(THD *thd, SELECT_LEX *sel, Table_ident *table_ident; table_ident= new Table_ident(thd, *db_name, *table_name, 1); sel->init_query(); - if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ)) + if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ, MDL_SHARED_READ)) return 1; return 0; } @@ -2966,6 +2965,9 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, (can_deadlock ? MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0))); lex->sql_command= save_sql_command; + + DEBUG_SYNC(thd, "after_open_table_ignore_flush"); + /* get_all_tables() returns 1 on failure and 0 on success thus return only these and not the result code of ::process_table() @@ -3025,7 +3027,7 @@ static int fill_schema_table_names(THD *thd, TABLE *table, char path[FN_REFLEN + 1]; (void) build_table_filename(path, sizeof(path) - 1, db_name->str, table_name->str, reg_ext, 0); - switch (mysql_frm_type(thd, path, ¬_used)) { + switch (dd_frm_type(thd, path, ¬_used)) { case FRMTYPE_ERROR: table->field[3]->store(STRING_WITH_LEN("ERROR"), system_charset_info); @@ -3133,15 +3135,27 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, bool error; table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, MDL_SHARED_HIGH_PRIO); - while (!(error= - thd->mdl_context.try_acquire_lock(&table->mdl_request)) && - !table->mdl_request.ticket && !can_deadlock) + + if (can_deadlock) { - if ((error= - thd->mdl_context.wait_for_lock(&table->mdl_request, - thd->variables.lock_wait_timeout))) - break; + /* + When .FRM is being open in order to get data for an I_S table, + we might have some tables not only open but also locked. + E.g. this happens when a SHOW or I_S statement is run + under LOCK TABLES or inside a stored function. + By waiting for the conflicting metadata lock to go away we + might create a deadlock which won't entirely belong to the + MDL subsystem and thus won't be detectable by this subsystem's + deadlock detector. To avoid such situation, when there are + other locked tables, we prefer not to wait on a conflicting + lock. + */ + error= thd->mdl_context.try_acquire_lock(&table->mdl_request); } + else + error= thd->mdl_context.acquire_lock(&table->mdl_request, + thd->variables.lock_wait_timeout); + return error; } @@ -6568,7 +6582,7 @@ int make_schema_select(THD *thd, SELECT_LEX *sel, strlen(schema_table->table_name), 0); if (schema_table->old_format(thd, schema_table) || /* Handle old syntax */ !sel->add_table_to_list(thd, new Table_ident(thd, db, table, 0), - 0, 0, TL_READ)) + 0, 0, TL_READ, MDL_SHARED_READ)) { DBUG_RETURN(1); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index dce23d1c09c..902e7fa7b5f 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -27,8 +27,8 @@ // start_waiting_global_read_lock, // unlock_table_names, mysql_unlock_tables #include "strfunc.h" // find_type2, find_set -#include "sql_view.h" // mysql_frm_type, view_checksum, mysql_frm_type -#include "sql_delete.h" // mysql_truncate +#include "sql_view.h" // view_checksum +#include "sql_truncate.h" // regenerate_locked_table #include "sql_partition.h" // mem_alloc_error, // generate_partition_syntax, // partition_info @@ -52,6 +52,7 @@ #include "sql_show.h" #include "transaction.h" #include "keycaches.h" +#include "datadict.h" // dd_frm_type() #ifdef __WIN__ #include <io.h> @@ -84,19 +85,6 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, Alter_info *alter_info); -#ifndef DBUG_OFF - -/* Wait until we get a 'mysql_kill' signal */ - -static void wait_for_kill_signal(THD *thd) -{ - while (thd->killed == 0) - sleep(1); - // Reset signal and continue as if nothing happend - thd->killed= THD::NOT_KILLED; -} -#endif - /** @brief Helper function for explain_filename @@ -2125,7 +2113,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, ((access(path, F_OK) && ha_create_table_from_engine(thd, db, alias)) || (!drop_view && - mysql_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE))) + dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE))) { // Table was not found on disk and table can't be created from engine if (if_exists) @@ -2145,7 +2133,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ if (frm_db_type == DB_TYPE_UNKNOWN) { - mysql_frm_type(thd, path, &frm_db_type); + dd_frm_type(thd, path, &frm_db_type); DBUG_PRINT("info", ("frm_db_type %d from %s", frm_db_type, path)); } table_type= ha_resolve_by_legacy_type(thd, frm_db_type); @@ -4454,10 +4442,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); + Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK | + MYSQL_LOCK_IGNORE_TIMEOUT)); DBUG_ENTER("prepare_for_repair"); - uint reopen_for_repair_flags= (MYSQL_OPEN_IGNORE_FLUSH | - MYSQL_OPEN_HAS_MDL_LOCK); if (!(check_opt->sql_flags & TT_USEFRM)) DBUG_RETURN(0); @@ -4586,12 +4574,18 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, "Failed renaming data file"); goto end; } - if (mysql_truncate(thd, table_list, 1)) + if (dd_recreate_table(thd, table_list->db, table_list->table_name)) { error= send_check_errmsg(thd, table_list, "repair", "Failed generating table from .frm file"); goto end; } + /* + 'FALSE' for 'using_transactions' means don't postpone + invalidation till the end of a transaction, but do it + immediately. + */ + query_cache_invalidate3(thd, table_list, FALSE); if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) { error= send_check_errmsg(thd, table_list, "repair", @@ -4606,8 +4600,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, Now we should be able to open the partially repaired table to finish the repair in the handler later on. */ - if (open_table(thd, table_list, thd->mem_root, - &ot_ctx_unused, reopen_for_repair_flags)) + if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) { error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); @@ -4685,6 +4678,14 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, strxmov(table_name, db, ".", table->table_name, NullS); thd->open_options|= extra_open_options; table->lock_type= lock_type; + /* + To make code safe for re-execution we need to reset type of MDL + request as code below may change it. + To allow concurrent execution of read-only operations we acquire + weak metadata lock for them. + */ + table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); /* open only one table from local list of command */ { TABLE_LIST *save_next_global, *save_next_local; @@ -4706,8 +4707,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (view_operator_func == NULL) table->required_type=FRMTYPE_TABLE; - open_error= open_and_lock_tables(thd, table, TRUE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + open_error= open_and_lock_tables(thd, table, TRUE, 0); thd->no_warnings_for_error= 0; table->next_global= save_next_global; table->next_local= save_next_local; @@ -4864,18 +4864,30 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, /* purecov: end */ } - /* Close all instances of the table to allow repair to rename files */ - if (lock_type == TL_WRITE && table->table->s->version) + /* + Close all instances of the table to allow MyISAM "repair" + to rename files. + @todo: This code does not close all instances of the table. + It only closes instances in other connections, but if this + connection has LOCK TABLE t1 a READ, t1 b WRITE, + both t1 instances will be kept open. + There is no need to execute this branch for InnoDB, which does + repair by recreate. There is no need to do it for OPTIMIZE, + which doesn't move files around. + Hence, this code should be moved to prepare_for_repair(), + and executed only for MyISAM engine. + */ + if (lock_type == TL_WRITE && !table->table->s->tmp_table) { if (wait_while_table_is_used(thd, table->table, HA_EXTRA_PREPARE_FOR_RENAME)) goto err; - DBUG_EXECUTE_IF("wait_in_mysql_admin_table", - wait_for_kill_signal(thd); - if (thd->killed) - goto err;); /* Flush entries in the query cache involving this table. */ query_cache_invalidate3(thd, table->table, 0); + /* + XXX: hack: switch off open_for_modify to skip the + flush that is made later in the execution flow. + */ open_for_modify= 0; } @@ -5053,6 +5065,7 @@ send_result_message: /* Clear the ticket released in close_thread_tables(). */ table->mdl_request.ticket= NULL; DEBUG_SYNC(thd, "ha_admin_open_ltable"); + table->mdl_request.set_type(MDL_SHARED_WRITE); if ((table->table= open_ltable(thd, table, lock_type, 0))) { result_code= table->table->file->ha_analyze(thd, check_opt); @@ -5134,20 +5147,21 @@ send_result_message: } if (table->table) { - if (fatal_error) - table->table->s->version=0; // Force close of table - else if (open_for_modify) + if (table->table->s->tmp_table) { - if (table->table->s->tmp_table) + if (open_for_modify) table->table->file->info(HA_STATUS_CONST); - else - { - TABLE_LIST *save_next_global= table->next_global; - table->next_global= 0; - close_cached_tables(thd, table, FALSE, FALSE); - table->next_global= save_next_global; - } - /* May be something modified consequently we have to invalidate cache */ + } + else if (open_for_modify || fatal_error) + { + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->db, table->table_name); + mysql_mutex_unlock(&LOCK_open); + /* + May be something modified. Consequently, we have to + invalidate the query cache. + */ query_cache_invalidate3(thd, table->table, 0); } } @@ -5388,7 +5402,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); /* The condition avoids a crash as described in BUG#48506. Other @@ -5403,8 +5417,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, to work. The table will be closed by close_thread_table() at the end of this branch. */ - if (open_table(thd, table, thd->mem_root, &ot_ctx_unused, - MYSQL_OPEN_REOPEN)) + if (open_table(thd, table, thd->mem_root, &ot_ctx)) goto err; int result __attribute__((unused))= @@ -5416,14 +5429,12 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, goto err; DBUG_ASSERT(thd->open_tables == table->table); - mysql_mutex_lock(&LOCK_open); /* When opening the table, we ignored the locked tables (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table without risking to close some locked table. */ close_thread_table(thd, &thd->open_tables); - mysql_mutex_unlock(&LOCK_open); } } else // Case 1 @@ -5490,6 +5501,7 @@ mysql_discard_or_import_tablespace(THD *thd, not complain when we lock the table */ thd->tablespace_op= TRUE; + table_list->mdl_request.set_type(MDL_SHARED_WRITE); if (!(table=open_ltable(thd, table_list, TL_WRITE, 0))) { thd->tablespace_op=FALSE; @@ -6467,8 +6479,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, char reg_path[FN_REFLEN+1]; ha_rows copied,deleted; handlerton *old_db_type, *new_db_type, *save_old_db_type; - legacy_db_type table_type; - frm_type_enum frm_type; enum_alter_table_change_level need_copy_table= ALTER_TABLE_METADATA_ONLY; #ifdef WITH_PARTITION_STORAGE_ENGINE uint fast_alter_partition= 0; @@ -6548,85 +6558,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* Conditionally writes to binlog. */ DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, alter_info->tablespace_op)); - strxnmov(new_name_buff, sizeof (new_name_buff) - 1, mysql_data_home, "/", db, - "/", table_name, reg_ext, NullS); - (void) unpack_filename(new_name_buff, new_name_buff); - /* - If this is just a rename of a view, short cut to the - following scenario: 1) lock LOCK_open 2) do a RENAME - 2) unlock LOCK_open. - This is a copy-paste added to make sure - ALTER (sic:) TABLE .. RENAME works for views. ALTER VIEW is handled - as an independent branch in mysql_execute_command. The need - for a copy-paste arose because the main code flow of ALTER TABLE - ... RENAME tries to use open_ltable, which does not work for views - (open_ltable was never modified to merge table lists of child tables - into the main table list, like open_tables does). - This code is wrong and will be removed, please do not copy. - */ - frm_type= mysql_frm_type(thd, new_name_buff, &table_type); - /* Rename a view */ - /* Sic: there is a race here */ - if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME)) - { - /* - The following branch handles "ALTER VIEW v1 /no arguments/;" - This feature is not documented one. - However, before "OPTIMIZE TABLE t1;" was implemented, - ALTER TABLE with no alter_specifications was used to force-rebuild - the table. That's why this grammar is allowed. That's why we ignore - it for views. So just do nothing in such a case. - */ - if (!new_name) - { - my_ok(thd); - DBUG_RETURN(FALSE); - } - - /* - Avoid problems with a rename on a table that we have locked or - if the user is trying to to do this in a transcation context - */ - - if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - DBUG_RETURN(TRUE); - } - - if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - DBUG_RETURN(TRUE); - if (lock_table_names(thd, table_list)) - { - error= 1; - goto view_err; - } - - mysql_mutex_lock(&LOCK_open); - - if (!do_rename(thd, table_list, new_db, new_name, new_name, 1)) - { - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query(), thd->query_length(), - FALSE, TRUE, FALSE, 0); - if ((error= mysql_bin_log.write(&qinfo))) - goto view_err_unlock; - } - my_ok(thd); - } - -view_err_unlock: - mysql_mutex_unlock(&LOCK_open); - unlock_table_names(thd); - -view_err: - thd->global_read_lock.start_waiting_global_read_lock(thd); - DBUG_RETURN(error); - } - /* Code below can handle only base tables so ensure that we won't open a view. @@ -6637,8 +6568,7 @@ view_err: Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info); - error= open_and_lock_tables(thd, table_list, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + error= open_and_lock_tables(thd, table_list, FALSE, 0, &alter_prelocking_strategy); if (error) @@ -7241,14 +7171,14 @@ view_err: { if (table->s->tmp_table) { - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); + Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_LOCK_IGNORE_TIMEOUT)); TABLE_LIST tbl; bzero((void*) &tbl, sizeof(tbl)); tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; /* Table is in thd->temporary_tables */ - (void) open_table(thd, &tbl, thd->mem_root, &ot_ctx_unused, - MYSQL_OPEN_IGNORE_FLUSH); + (void) open_table(thd, &tbl, thd->mem_root, &ot_ctx); new_table= tbl.table; } else @@ -7527,7 +7457,7 @@ view_err: To do this we need to obtain a handler object for it. NO need to tamper with MERGE tables. The real open is done later. */ - Open_table_context ot_ctx_unused(thd, LONG_TIMEOUT); + Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN); TABLE *t_table; if (new_name != table_name || new_db != db) { @@ -7547,8 +7477,7 @@ view_err: */ table_list->mdl_request.ticket= mdl_ticket; } - if (open_table(thd, table_list, thd->mem_root, - &ot_ctx_unused, MYSQL_OPEN_REOPEN)) + if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) { goto err_with_mdl; } @@ -7559,9 +7488,7 @@ view_err: create_info); DBUG_ASSERT(thd->open_tables == t_table); - mysql_mutex_lock(&LOCK_open); close_thread_table(thd, &thd->open_tables); - mysql_mutex_unlock(&LOCK_open); table_list->table= 0; if (error) @@ -7940,6 +7867,10 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) table_list->table= NULL; /* Same applies to MDL ticket. */ table_list->mdl_request.ticket= NULL; + /* Set lock type which is appropriate for ALTER TABLE. */ + table_list->lock_type= TL_READ_NO_INSERT; + /* Same applies to MDL request. */ + table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE); bzero((char*) &create_info, sizeof(create_info)); create_info.row_type=ROW_TYPE_NOT_USED; diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 43d203e6498..48fd5f9dff8 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -45,7 +45,6 @@ static const char *lock_descriptions[] = /* TL_READ_HIGH_PRIORITY */ "High priority read lock", /* TL_READ_NO_INSERT */ "Read lock without concurrent inserts", /* TL_WRITE_ALLOW_WRITE */ "Write lock that allows other writers", - /* TL_WRITE_ALLOW_READ */ "Write lock, but allow reading", /* TL_WRITE_CONCURRENT_INSERT */ "Concurrent insert lock", /* TL_WRITE_DELAYED */ "Lock used by delayed insert", /* TL_WRITE_DEFAULT */ NULL, diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index c3266b5cbe2..2f084c369b6 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -489,8 +489,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) else { tables->table= open_n_lock_single_table(thd, tables, - TL_WRITE_ALLOW_READ, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + TL_READ_NO_INSERT, 0); if (! tables->table) goto end; tables->table->use_all_columns(); @@ -1666,7 +1665,8 @@ bool add_table_for_trigger(THD *thd, DBUG_RETURN(TRUE); *table= sp_add_to_query_tables(thd, lex, trg_name->m_db.str, - tbl_name.str, TL_IGNORE); + tbl_name.str, TL_IGNORE, + MDL_SHARED_NO_WRITE); DBUG_RETURN(*table ? FALSE : TRUE); } diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc new file mode 100644 index 00000000000..901ab8e987d --- /dev/null +++ b/sql/sql_truncate.cc @@ -0,0 +1,485 @@ +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_truncate.h" +#include "sql_priv.h" +#include "transaction.h" +#include "debug_sync.h" +#include "records.h" // READ_RECORD +#include "table.h" // TABLE +#include "sql_class.h" // THD +#include "sql_base.h" // open_and_lock_tables +#include "sql_table.h" // write_bin_log +#include "sql_handler.h" // mysql_ha_rm_tables +#include "datadict.h" // dd_recreate_table() +#include "lock.h" // MYSQL_OPEN_TEMPORARY_ONLY + + +/* + Delete all rows of a locked table. + + @param thd Thread context. + @param table_list Table list element for the table. + @param rows_deleted Whether rows might have been deleted. + + @retval FALSE Success. + @retval TRUE Error. +*/ + +static bool +delete_all_rows(THD *thd, TABLE *table) +{ + int error; + READ_RECORD info; + bool is_bulk_delete; + bool some_rows_deleted= FALSE; + bool save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); + DBUG_ENTER("delete_all_rows"); + + /* Replication of truncate table must be statement based. */ + thd->clear_current_stmt_binlog_format_row(); + + /* + Update handler statistics (e.g. table->file->stats.records). + Might be used by the storage engine to aggregate information + necessary to allow deletion. Currently, this seems to be + meaningful only to the archive storage engine, which uses + the info method to set the number of records. Although + archive does not support deletion, it becomes necessary in + order to return a error if the table is not empty. + */ + error= table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); + if (error && error != HA_ERR_WRONG_COMMAND) + { + table->file->print_error(error, MYF(0)); + goto end; + } + + /* + Attempt to delete all rows in the table. + If it is unsupported, switch to row by row deletion. + */ + if (! (error= table->file->ha_delete_all_rows())) + goto end; + + if (error != HA_ERR_WRONG_COMMAND) + { + /* + If a transactional engine fails in the middle of deletion, + we expect it to be able to roll it back. Some reasons + for the engine to fail would be media failure or corrupted + data dictionary (i.e. in case of a partitioned table). We + have sufficiently strong metadata locks to rule out any + potential deadlocks. + + If a non-transactional engine fails here (that would + not be MyISAM, since MyISAM does TRUNCATE by recreate), + and binlog is on, replication breaks, since nothing gets + written to the binary log. (XXX: is this a bug?) + */ + table->file->print_error(error, MYF(0)); + goto end; + } + + /* + A workaround for Bug#53696 "Performance schema engine violates the + PSEA API by calling my_error()". + */ + if (thd->is_error()) + goto end; + + /* Handler didn't support fast delete. Delete rows one by one. */ + + init_read_record(&info, thd, table, NULL, TRUE, TRUE, FALSE); + + /* + Start bulk delete. If the engine does not support it, go on, + it's not an error. + */ + is_bulk_delete= ! table->file->start_bulk_delete(); + + table->mark_columns_needed_for_delete(); + + while (!(error= info.read_record(&info)) && !thd->killed) + { + if ((error= table->file->ha_delete_row(table->record[0]))) + { + table->file->print_error(error, MYF(0)); + break; + } + + some_rows_deleted= TRUE; + } + + /* HA_ERR_END_OF_FILE */ + if (error == -1) + error= 0; + + /* Close down the bulk delete. */ + if (is_bulk_delete) + { + int bulk_delete_error= table->file->end_bulk_delete(); + if (bulk_delete_error && !error) + { + table->file->print_error(bulk_delete_error, MYF(0)); + error= bulk_delete_error; + } + } + + end_read_record(&info); + + /* + Regardless of the error status, the query must be written to the + binary log if rows of the table is non-transactional. + */ + if (some_rows_deleted && !table->file->has_transactions()) + { + thd->transaction.stmt.modified_non_trans_table= TRUE; + thd->transaction.all.modified_non_trans_table= TRUE; + } + + if (error || thd->killed) + goto end; + + /* Truncate resets the auto-increment counter. */ + error= table->file->ha_reset_auto_increment(0); + if (error) + { + if (error != HA_ERR_WRONG_COMMAND) + table->file->print_error(error, MYF(0)); + else + error= 0; + } + +end: + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); + + DBUG_RETURN(error); +} + + +/* + Close and recreate a temporary table. In case of success, + write truncate statement into the binary log if in statement + mode. + + @param thd Thread context. + @param table The temporary table. + + @retval FALSE Success. + @retval TRUE Error. +*/ + +static bool recreate_temporary_table(THD *thd, TABLE *table) +{ + bool error= TRUE; + TABLE_SHARE *share= table->s; + HA_CREATE_INFO create_info; + handlerton *table_type= table->s->db_type(); + DBUG_ENTER("recreate_temporary_table"); + + memset(&create_info, 0, sizeof(create_info)); + + table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK); + + /* Don't free share. */ + close_temporary_table(thd, table, FALSE, FALSE); + + /* + We must use share->normalized_path.str since for temporary tables it + differs from what dd_recreate_table() would generate based + on table and schema names. + */ + ha_create_table(thd, share->normalized_path.str, share->db.str, + share->table_name.str, &create_info, 1); + + if (open_temporary_table(thd, share->path.str, share->db.str, + share->table_name.str, 1)) + { + error= FALSE; + thd->thread_specific_used= TRUE; + } + else + rm_temporary_table(table_type, share->path.str); + + free_table_share(share); + my_free(table, MYF(0)); + + DBUG_RETURN(error); +} + + +/* + Handle opening and locking if a base table for truncate. + + @param[in] thd Thread context. + @param[in] table_ref Table list element for the table to + be truncated. + @param[out] hton_can_recreate Set to TRUE if table can be dropped + and recreated. + @param[out] ticket_downgrade Set if a lock must be downgraded after + truncate is done. + + @retval FALSE Success. + @retval TRUE Error. +*/ + +static bool open_and_lock_table_for_truncate(THD *thd, TABLE_LIST *table_ref, + bool *hton_can_recreate, + MDL_ticket **ticket_downgrade) +{ + TABLE *table= NULL; + MDL_ticket *mdl_ticket= NULL; + DBUG_ENTER("open_and_lock_table_for_truncate"); + + /* + Before doing anything else, acquire a metadata lock on the table, + or ensure we have one. We don't use open_and_lock_tables() + right away because we want to be able to truncate (and recreate) + corrupted tables, those that we can't fully open. + + MySQL manual documents that TRUNCATE can be used to repair a + damaged table, i.e. a table that can not be fully "opened". + In particular MySQL manual says: As long as the table format + file tbl_name.frm is valid, the table can be re-created as + an empty table with TRUNCATE TABLE, even if the data or index + files have become corrupted. + */ + if (thd->locked_tables_mode) + { + if (!(table= find_table_for_mdl_upgrade(thd->open_tables, table_ref->db, + table_ref->table_name, FALSE))) + DBUG_RETURN(TRUE); + + *hton_can_recreate= ha_check_storage_engine_flag(table->s->db_type(), + HTON_CAN_RECREATE); + } + else + { + /* + Even though we could use the previous execution branch here just as + well, we must not try to open the table: + */ + MDL_request mdl_global_request, mdl_request; + MDL_request_list mdl_requests; + + mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + mdl_request.init(MDL_key::TABLE, table_ref->db, table_ref->table_name, + MDL_SHARED_NO_READ_WRITE); + mdl_requests.push_front(&mdl_request); + mdl_requests.push_front(&mdl_global_request); + + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) + DBUG_RETURN(TRUE); + + mdl_ticket= mdl_request.ticket; + + if (dd_check_storage_engine_flag(thd, table_ref->db, table_ref->table_name, + HTON_CAN_RECREATE, hton_can_recreate)) + DBUG_RETURN(TRUE); + } + + DEBUG_SYNC(thd, "lock_table_for_truncate"); + + if (*hton_can_recreate) + { + /* + Acquire an exclusive lock. The storage engine can recreate the + table only if there are no references to it from anywhere, i.e. + no cached TABLE in the table cache. To remove the table from the + cache we need an exclusive lock. + */ + if (thd->locked_tables_mode) + { + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + DBUG_RETURN(TRUE); + *ticket_downgrade= table->mdl_ticket; + close_all_tables_for_name(thd, table->s, FALSE); + } + else + { + ulong timeout= thd->variables.lock_wait_timeout; + if (thd->mdl_context.upgrade_shared_lock_to_exclusive(mdl_ticket, timeout)) + DBUG_RETURN(TRUE); + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db, + table_ref->table_name); + mysql_mutex_unlock(&LOCK_open); + } + } + else + { + /* + Can't recreate, we must mechanically delete all rows in + the table. Our metadata lock guarantees that no transaction + is reading or writing into the table. Yet, to open a write + cursor we need a thr_lock lock. Use open_and_lock_tables() + to do the necessary job. + */ + + /* Allow to open base tables only. */ + table_ref->required_type= FRMTYPE_TABLE; + /* We don't need to load triggers. */ + DBUG_ASSERT(table_ref->trg_event_map == 0); + /* Work around partition parser rules using alter table's. */ + if (thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) + { + table_ref->lock_type= TL_WRITE; + table_ref->mdl_request.set_type(MDL_SHARED_WRITE); + } + /* Ensure proper lock types (e.g. from the parser). */ + DBUG_ASSERT(table_ref->lock_type == TL_WRITE); + DBUG_ASSERT(table_ref->mdl_request.type == MDL_SHARED_WRITE); + + /* + Open the table as it will handle some required preparations. + Ignore pending FLUSH TABLES since we don't want to release + the MDL lock taken above and otherwise there is no way to + wait for FLUSH TABLES in deadlock-free fashion. + */ + if (open_and_lock_tables(thd, table_ref, FALSE, + MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_OPEN_SKIP_TEMPORARY)) + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(FALSE); +} + + +/* + Optimized delete of all rows by doing a full generate of the table. + + @remark Will work even if the .MYI and .MYD files are destroyed. + In other words, it works as long as the .FRM is intact and + the engine supports re-create. + + @param thd Thread context. + @param table_ref Table list element for the table to be truncated. + + @retval FALSE Success. + @retval TRUE Error. +*/ + +bool mysql_truncate_table(THD *thd, TABLE_LIST *table_ref) +{ + TABLE *table; + bool error= TRUE, binlog_stmt; + MDL_ticket *mdl_ticket= NULL; + DBUG_ENTER("mysql_truncate_table"); + + /* Remove tables from the HANDLER's hash. */ + mysql_ha_rm_tables(thd, table_ref); + + /* If it is a temporary table, no need to take locks. */ + if ((table= find_temporary_table(thd, table_ref))) + { + /* In RBR, the statement is not binlogged if the table is temporary. */ + binlog_stmt= !thd->is_current_stmt_binlog_format_row(); + + /* Note that a temporary table cannot be partitioned. */ + if (ha_check_storage_engine_flag(table->s->db_type(), HTON_CAN_RECREATE)) + { + if ((error= recreate_temporary_table(thd, table))) + binlog_stmt= FALSE; /* No need to binlog failed truncate-by-recreate. */ + + DBUG_ASSERT(! thd->transaction.stmt.modified_non_trans_table); + } + else + { + /* + The engine does not support truncate-by-recreate. Open the + table and delete all rows. In such a manner this can in fact + open several tables if it's a temporary MyISAMMRG table. + */ + if (open_and_lock_tables(thd, table_ref, FALSE, + MYSQL_OPEN_TEMPORARY_ONLY)) + DBUG_RETURN(TRUE); + + error= delete_all_rows(thd, table_ref->table); + } + + /* + No need to invalidate the query cache, queries with temporary + tables are not in the cache. No need to write to the binary + log a failed row-by-row delete even if under RBR as the table + might not exist on the slave. + */ + } + else /* It's not a temporary table. */ + { + bool hton_can_recreate; + + if (open_and_lock_table_for_truncate(thd, table_ref, + &hton_can_recreate, &mdl_ticket)) + DBUG_RETURN(TRUE); + + if (hton_can_recreate) + { + /* + The storage engine can truncate the table by creating an + empty table with the same structure. + */ + error= dd_recreate_table(thd, table_ref->db, table_ref->table_name); + + if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd)) + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + + /* No need to binlog a failed truncate-by-recreate. */ + binlog_stmt= !error; + } + else + { + error= delete_all_rows(thd, table_ref->table); + + /* + Regardless of the error status, the query must be written to the + binary log if rows of a non-transactional table were deleted. + */ + binlog_stmt= !error || thd->transaction.stmt.modified_non_trans_table; + } + + query_cache_invalidate3(thd, table_ref, FALSE); + } + + /* DDL is logged in statement format, regardless of binlog format. */ + if (binlog_stmt) + error|= write_bin_log(thd, !error, thd->query(), thd->query_length()); + + /* + All effects of a TRUNCATE TABLE operation are rolled back if a row + by row deletion fails. Otherwise, it is automatically committed at + the end. + */ + if (error) + { + trans_rollback_stmt(thd); + trans_rollback(thd); + } + + /* + A locked table ticket was upgraded to a exclusive lock. After the + the query has been written to the binary log, downgrade the lock + to a shared one. + */ + if (mdl_ticket) + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + + DBUG_PRINT("exit", ("error: %d", error)); + DBUG_RETURN(test(error)); +} + diff --git a/sql/sql_truncate.h b/sql/sql_truncate.h new file mode 100644 index 00000000000..11c07c7187c --- /dev/null +++ b/sql/sql_truncate.h @@ -0,0 +1,23 @@ +#ifndef SQL_TRUNCATE_INCLUDED +#define SQL_TRUNCATE_INCLUDED +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +class THD; +struct TABLE_LIST; + +bool mysql_truncate_table(THD *thd, TABLE_LIST *table_ref); + +#endif diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index 9ec17a67533..3d197303fb1 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -248,7 +248,7 @@ void udf_init() if (error > 0) sql_print_error("Got unknown error: %d", my_errno); end_read_record(&read_record_info); - new_thd->version--; // Force close to free memory + table->m_needs_reopen= TRUE; // Force close to free memory end: close_thread_tables(new_thd); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 8cb4df36cd5..69abe70e863 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -32,6 +32,7 @@ #include "sp.h" #include "sp_head.h" #include "sp_cache.h" +#include "datadict.h" // dd_frm_type() #define MD5_BUFF_LENGTH 33 @@ -433,7 +434,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_strategy= TABLE_LIST::OPEN_STUB; - view->lock_strategy= TABLE_LIST::EXCLUSIVE_MDL; + view->lock_strategy= TABLE_LIST::OTLS_NONE; view->open_type= OT_BASE_ONLY; if (open_and_lock_tables(thd, lex->query_tables, TRUE, 0)) @@ -1663,7 +1664,7 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) view->db, view->table_name, reg_ext, 0); if (access(path, F_OK) || - FRMTYPE_VIEW != (type= mysql_frm_type(thd, path, ¬_used))) + FRMTYPE_VIEW != (type= dd_frm_type(thd, path, ¬_used))) { char name[FN_REFLEN]; my_snprintf(name, sizeof(name), "%s.%s", view->db, view->table_name); @@ -1742,54 +1743,6 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) /* - Check type of .frm if we are not going to parse it - - SYNOPSIS - mysql_frm_type() - path path to file - - RETURN - FRMTYPE_ERROR error - FRMTYPE_TABLE table - FRMTYPE_VIEW view -*/ - -frm_type_enum mysql_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) -{ - File file; - uchar header[10]; //"TYPE=VIEW\n" it is 10 characters - size_t error; - DBUG_ENTER("mysql_frm_type"); - - *dbt= DB_TYPE_UNKNOWN; - - if ((file= mysql_file_open(key_file_frm, - path, O_RDONLY | O_SHARE, MYF(0))) < 0) - DBUG_RETURN(FRMTYPE_ERROR); - error= mysql_file_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP)); - mysql_file_close(file, MYF(MY_WME)); - - if (error) - DBUG_RETURN(FRMTYPE_ERROR); - if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header))) - DBUG_RETURN(FRMTYPE_VIEW); - - /* - This is just a check for DB_TYPE. We'll return default unknown type - if the following test is true (arg #3). This should not have effect - on return value from this function (default FRMTYPE_TABLE) - */ - if (header[0] != (uchar) 254 || header[1] != 1 || - (header[2] != FRM_VER && header[2] != FRM_VER+1 && - (header[2] < FRM_VER+3 || header[2] > FRM_VER+4))) - DBUG_RETURN(FRMTYPE_TABLE); - - *dbt= (enum legacy_db_type) (uint) *(header + 3); - DBUG_RETURN(FRMTYPE_TABLE); // Is probably a .frm table -} - - -/* check of key (primary or unique) presence in updatable view SYNOPSIS diff --git a/sql/sql_view.h b/sql/sql_view.h index 7d06abb9068..c15ecffccb8 100644 --- a/sql/sql_view.h +++ b/sql/sql_view.h @@ -43,8 +43,6 @@ bool check_key_in_view(THD *thd, TABLE_LIST * view); bool insert_view_fields(THD *thd, List<Item> *list, TABLE_LIST *view); -frm_type_enum mysql_frm_type(THD *thd, char *path, enum legacy_db_type *dbt); - int view_checksum(THD *thd, TABLE_LIST *view); extern TYPELIB updatable_views_with_limit_typelib; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 2d5f7df6009..1c673f4ca42 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -696,7 +696,8 @@ static bool add_create_index_prepare (LEX *lex, Table_ident *table) lex->sql_command= SQLCOM_CREATE_INDEX; if (!lex->current_select->add_table_to_list(lex->thd, table, NULL, TL_OPTION_UPDATING, - TL_WRITE_ALLOW_READ)) + TL_READ_NO_INSERT, + MDL_SHARED_NO_WRITE)) return TRUE; lex->alter_info.reset(); lex->alter_info.flags= ALTER_ADD_INDEX; @@ -765,6 +766,7 @@ static bool add_create_index (LEX *lex, Key::Keytype type, enum index_hint_type index_hint; enum enum_filetype filetype; enum Foreign_key::fk_option m_fk_option; + enum enum_yes_no_unknown m_yes_no_unk; Diag_condition_item_name diag_condition_item_name; } @@ -1432,12 +1434,15 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); table_option opt_if_not_exists opt_no_write_to_binlog opt_temporary all_or_any opt_distinct opt_ignore_leaves fulltext_options spatial_type union_option - start_transaction_opts opt_chain opt_release + start_transaction_opts union_opt select_derived_init option_type2 opt_natural_language_mode opt_query_expansion opt_ev_status opt_ev_on_completion ev_on_completion opt_ev_comment ev_alter_on_schedule_completion opt_ev_rename_to opt_ev_sql_stmt +%type <m_yes_no_unk> + opt_chain opt_release + %type <m_fk_option> delete_option @@ -2022,7 +2027,7 @@ create: lex->sql_command= SQLCOM_CREATE_TABLE; if (!lex->select_lex.add_table_to_list(thd, $5, NULL, TL_OPTION_UPDATING, - TL_WRITE)) + TL_WRITE, MDL_EXCLUSIVE)) MYSQL_YYABORT; lex->alter_info.reset(); lex->col_list.empty(); @@ -4212,7 +4217,8 @@ create2: lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; src_table= lex->select_lex.add_table_to_list(thd, $2, NULL, 0, - TL_READ); + TL_READ, + MDL_SHARED_READ); if (! src_table) MYSQL_YYABORT; /* CREATE TABLE ... LIKE is not allowed for views. */ @@ -4226,7 +4232,8 @@ create2: lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; src_table= lex->select_lex.add_table_to_list(thd, $3, NULL, 0, - TL_READ); + TL_READ, + MDL_SHARED_READ); if (! src_table) MYSQL_YYABORT; /* CREATE TABLE ... LIKE is not allowed for views. */ @@ -6153,7 +6160,8 @@ alter: lex->duplicates= DUP_ERROR; if (!lex->select_lex.add_table_to_list(thd, $4, NULL, TL_OPTION_UPDATING, - TL_WRITE_ALLOW_READ)) + TL_READ_NO_INSERT, + MDL_SHARED_NO_WRITE)) MYSQL_YYABORT; lex->col_list.empty(); lex->select_lex.init_order(); @@ -6845,6 +6853,8 @@ checksum: { LEX *lex=Lex; lex->sql_command = SQLCOM_CHECKSUM; + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list opt_checksum_type {} @@ -6864,6 +6874,8 @@ repair: lex->no_write_to_binlog= $2; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list opt_mi_repair_type {} @@ -6893,6 +6905,8 @@ analyze: lex->no_write_to_binlog= $2; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list {} @@ -6919,6 +6933,8 @@ check: lex->sql_command = SQLCOM_CHECK; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list opt_mi_check_type {} @@ -6951,6 +6967,8 @@ optimize: lex->no_write_to_binlog= $2; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list {} @@ -6999,9 +7017,9 @@ table_to_table: LEX *lex=Lex; SELECT_LEX *sl= lex->current_select; if (!sl->add_table_to_list(lex->thd, $1,NULL,TL_OPTION_UPDATING, - TL_IGNORE) || + TL_IGNORE, MDL_EXCLUSIVE) || !sl->add_table_to_list(lex->thd, $3,NULL,TL_OPTION_UPDATING, - TL_IGNORE)) + TL_IGNORE, MDL_EXCLUSIVE)) MYSQL_YYABORT; } ; @@ -7032,7 +7050,8 @@ keycache_list: assign_to_keycache: table_ident cache_keys_spec { - if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -7042,6 +7061,7 @@ assign_to_keycache_parts: table_ident adm_partition cache_keys_spec { if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -7077,6 +7097,7 @@ preload_keys: table_ident cache_keys_spec opt_ignore_leaves { if (!Select->add_table_to_list(YYTHD, $1, NULL, $3, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -7086,6 +7107,7 @@ preload_keys_parts: table_ident adm_partition cache_keys_spec opt_ignore_leaves { if (!Select->add_table_to_list(YYTHD, $1, NULL, $4, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -9217,6 +9239,7 @@ table_factor: if (!($$= Select->add_table_to_list(YYTHD, $2, $3, Select->get_table_join_options(), YYPS->m_lock_type, + YYPS->m_mdl_type, Select->pop_index_hints()))) MYSQL_YYABORT; Select->add_joined_table($$); @@ -9288,7 +9311,7 @@ table_factor: MYSQL_YYABORT; if (!($$= sel->add_table_to_list(lex->thd, new Table_ident(unit), $5, 0, - TL_READ))) + TL_READ, MDL_SHARED_READ))) MYSQL_YYABORT; sel->add_joined_table($$); @@ -10123,13 +10146,17 @@ do: */ drop: - DROP opt_temporary table_or_tables if_exists table_list opt_restrict + DROP opt_temporary table_or_tables if_exists { LEX *lex=Lex; lex->sql_command = SQLCOM_DROP_TABLE; lex->drop_temporary= $2; lex->drop_if_exists= $4; + YYPS->m_lock_type= TL_UNLOCK; + YYPS->m_mdl_type= MDL_EXCLUSIVE; } + table_list opt_restrict + {} | DROP INDEX_SYM ident ON table_ident {} { LEX *lex=Lex; @@ -10142,7 +10169,8 @@ drop: lex->alter_info.drop_list.push_back(ad); if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, TL_OPTION_UPDATING, - TL_WRITE_ALLOW_READ)) + TL_READ_NO_INSERT, + MDL_SHARED_NO_WRITE)) MYSQL_YYABORT; } | DROP DATABASE if_exists ident @@ -10212,12 +10240,16 @@ drop: { Lex->sql_command = SQLCOM_DROP_USER; } - | DROP VIEW_SYM if_exists table_list opt_restrict + | DROP VIEW_SYM if_exists { LEX *lex= Lex; lex->sql_command= SQLCOM_DROP_VIEW; lex->drop_if_exists= $3; + YYPS->m_lock_type= TL_UNLOCK; + YYPS->m_mdl_type= MDL_EXCLUSIVE; } + table_list opt_restrict + {} | DROP EVENT_SYM if_exists sp_name { Lex->drop_if_exists= $3; @@ -10258,7 +10290,10 @@ table_list: table_name: table_ident { - if (!Select->add_table_to_list(YYTHD, $1, NULL, TL_OPTION_UPDATING)) + if (!Select->add_table_to_list(YYTHD, $1, NULL, + TL_OPTION_UPDATING, + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } ; @@ -10273,7 +10308,8 @@ table_alias_ref: { if (!Select->add_table_to_list(YYTHD, $1, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } ; @@ -10555,6 +10591,8 @@ delete: lex->sql_command= SQLCOM_DELETE; mysql_init_select(lex); YYPS->m_lock_type= TL_WRITE_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_WRITE; + lex->ignore= 0; lex->select_lex.init_order(); } @@ -10565,9 +10603,11 @@ single_multi: FROM table_ident { if (!Select->add_table_to_list(YYTHD, $2, NULL, TL_OPTION_UPDATING, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; YYPS->m_lock_type= TL_READ_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_READ; } where_clause opt_order_clause delete_limit_clause {} @@ -10575,6 +10615,7 @@ single_multi: { mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_READ; } FROM join_table_list where_clause { @@ -10585,6 +10626,7 @@ single_multi: { mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_READ; } USING join_table_list where_clause { @@ -10608,7 +10650,8 @@ table_wild_one: ti, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } | ident '.' ident opt_wild @@ -10620,7 +10663,8 @@ table_wild_one: ti, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } ; @@ -10642,7 +10686,7 @@ opt_delete_option: ; truncate: - TRUNCATE_SYM opt_table_sym table_name + TRUNCATE_SYM opt_table_sym { LEX* lex= Lex; lex->sql_command= SQLCOM_TRUNCATE; @@ -10650,7 +10694,11 @@ truncate: lex->select_lex.options= 0; lex->select_lex.sql_cache= SELECT_LEX::SQL_CACHE_UNSPECIFIED; lex->select_lex.init_order(); + YYPS->m_lock_type= TL_WRITE; + YYPS->m_mdl_type= MDL_SHARED_WRITE; } + table_name + {} ; opt_table_sym: @@ -11134,7 +11182,15 @@ flush: flush_options: table_or_tables - { Lex->type|= REFRESH_TABLES; } + { + Lex->type|= REFRESH_TABLES; + /* + Set type of metadata and table locks for + FLUSH TABLES table_list WITH READ LOCK. + */ + YYPS->m_lock_type= TL_READ_NO_INSERT; + YYPS->m_mdl_type= MDL_EXCLUSIVE; + } opt_table_list {} opt_with_read_lock {} | flush_options_list @@ -11298,7 +11354,7 @@ load: { LEX *lex=Lex; if (!Select->add_table_to_list(YYTHD, $12, NULL, TL_OPTION_UPDATING, - $4)) + $4, MDL_SHARED_WRITE)) MYSQL_YYABORT; lex->field_list.empty(); lex->update_list.empty(); @@ -13004,10 +13060,14 @@ table_lock: table_ident opt_table_alias lock_option { thr_lock_type lock_type= (thr_lock_type) $3; - if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type)) + bool lock_for_write= (lock_type >= TL_WRITE_ALLOW_WRITE); + if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type, + (lock_for_write ? + MDL_SHARED_NO_READ_WRITE : + MDL_SHARED_READ))) MYSQL_YYABORT; /* If table is to be write locked, protect from a impending GRL. */ - if (lock_type >= TL_WRITE_ALLOW_WRITE) + if (lock_for_write) Lex->protect_against_global_read_lock= TRUE; } ; @@ -13543,16 +13603,16 @@ opt_work: opt_chain: /* empty */ - { $$= (YYTHD->variables.completion_type == 1); } - | AND_SYM NO_SYM CHAIN_SYM { $$=0; } - | AND_SYM CHAIN_SYM { $$=1; } + { $$= TVL_UNKNOWN; } + | AND_SYM NO_SYM CHAIN_SYM { $$= TVL_NO; } + | AND_SYM CHAIN_SYM { $$= TVL_YES; } ; opt_release: /* empty */ - { $$= (YYTHD->variables.completion_type == 2); } - | RELEASE_SYM { $$=1; } - | NO_SYM RELEASE_SYM { $$=0; } + { $$= TVL_UNKNOWN; } + | RELEASE_SYM { $$= TVL_YES; } + | NO_SYM RELEASE_SYM { $$= TVL_NO; } ; opt_savepoint: @@ -13565,7 +13625,9 @@ commit: { LEX *lex=Lex; lex->sql_command= SQLCOM_COMMIT; - lex->tx_chain= $3; + /* Don't allow AND CHAIN RELEASE. */ + MYSQL_YYABORT_UNLESS($3 != TVL_YES || $4 != TVL_YES); + lex->tx_chain= $3; lex->tx_release= $4; } ; @@ -13575,7 +13637,9 @@ rollback: { LEX *lex=Lex; lex->sql_command= SQLCOM_ROLLBACK; - lex->tx_chain= $3; + /* Don't allow AND CHAIN RELEASE. */ + MYSQL_YYABORT_UNLESS($3 != TVL_YES || $4 != TVL_YES); + lex->tx_chain= $3; lex->tx_release= $4; } | ROLLBACK_SYM opt_work @@ -13762,6 +13826,7 @@ query_expression_option: if (check_simple_select()) MYSQL_YYABORT; YYPS->m_lock_type= TL_READ_HIGH_PRIORITY; + YYPS->m_mdl_type= MDL_SHARED_READ; Select->options|= SELECT_HIGH_PRIORITY; } | DISTINCT { Select->options|= SELECT_DISTINCT; } @@ -13891,7 +13956,10 @@ view_tail: LEX *lex= thd->lex; lex->sql_command= SQLCOM_CREATE_VIEW; /* first table in list is target VIEW name */ - if (!lex->select_lex.add_table_to_list(thd, $3, NULL, TL_OPTION_UPDATING)) + if (!lex->select_lex.add_table_to_list(thd, $3, NULL, + TL_OPTION_UPDATING, + TL_IGNORE, + MDL_EXCLUSIVE)) MYSQL_YYABORT; } view_list_opt AS view_select @@ -14031,7 +14099,8 @@ trigger_tail: if (!lex->select_lex.add_table_to_list(YYTHD, $9, (LEX_STRING*) 0, TL_OPTION_UPDATING, - TL_IGNORE)) + TL_READ_NO_INSERT, + MDL_SHARED_NO_WRITE)) MYSQL_YYABORT; } ; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 856f4c6f771..7eb9a72273b 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2029,24 +2029,38 @@ static bool check_tx_isolation(sys_var *self, THD *thd, set_var *var) return FALSE; } -/* - If one doesn't use the SESSION modifier, the isolation level - is only active for the next command. -*/ -static bool fix_tx_isolation(sys_var *self, THD *thd, enum_var_type type) + +bool Sys_var_tx_isolation::session_update(THD *thd, set_var *var) { - if (type == OPT_SESSION) - thd->session_tx_isolation= (enum_tx_isolation)thd->variables.tx_isolation; - return false; + if (var->type == OPT_SESSION && Sys_var_enum::session_update(thd, var)) + return TRUE; + if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction()) + { + /* + Update the isolation level of the next transaction. + I.e. if one did: + COMMIT; + SET SESSION ISOLATION LEVEL ... + BEGIN; <-- this transaction has the new isolation + Note, that in case of: + COMMIT; + SET TRANSACTION ISOLATION LEVEL ... + SET SESSION ISOLATION LEVEL ... + BEGIN; <-- the session isolation level is used, not the + result of SET TRANSACTION statement. + */ + thd->tx_isolation= (enum_tx_isolation) var->save_result.ulonglong_value; + } + return FALSE; } + // NO_CMD_LINE - different name of the option -static Sys_var_enum Sys_tx_isolation( +static Sys_var_tx_isolation Sys_tx_isolation( "tx_isolation", "Default transaction isolation level", SESSION_VAR(tx_isolation), NO_CMD_LINE, tx_isolation_names, DEFAULT(ISO_REPEATABLE_READ), - NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_isolation), - ON_UPDATE(fix_tx_isolation)); + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_tx_isolation)); static Sys_var_ulonglong Sys_tmp_table_size( "tmp_table_size", diff --git a/sql/sys_vars.h b/sql/sys_vars.h index c4855ef4fd3..fbc48573487 100644 --- a/sql/sys_vars.h +++ b/sql/sys_vars.h @@ -1599,6 +1599,22 @@ public: { return type != STRING_RESULT; } }; + +class Sys_var_tx_isolation: public Sys_var_enum +{ +public: + Sys_var_tx_isolation(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt, + const char *values[], uint def_val, PolyLock *lock, + enum binlog_status_enum binlog_status_arg, + on_check_function on_check_func) + :Sys_var_enum(name_arg, comment, flag_args, off, size, getopt, + values, def_val, lock, binlog_status_arg, on_check_func) + {} + virtual bool session_update(THD *thd, set_var *var); +}; + /**************************************************************************** Used templates ****************************************************************************/ diff --git a/sql/table.cc b/sql/table.cc index 3143db78520..dbd657bee67 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2770,9 +2770,10 @@ bool check_db_name(LEX_STRING *org_name) returns 1 on error */ -bool check_table_name(const char *name, uint length, bool check_for_path_chars) +bool check_table_name(const char *name, size_t length, bool check_for_path_chars) { - uint name_length= 0; // name length in symbols + // name length in symbols + size_t name_length= 0; const char *end= name+length; if (!length || length > NAME_LEN) return 1; @@ -2805,18 +2806,19 @@ bool check_table_name(const char *name, uint length, bool check_for_path_chars) name_length++; } #if defined(USE_MB) && defined(USE_MB_IDENT) - return (last_char_is_space || name_length > NAME_CHAR_LEN) ; + return last_char_is_space || (name_length > NAME_CHAR_LEN); #else - return 0; + return FALSE; #endif } bool check_column_name(const char *name) { - uint name_length= 0; // name length in symbols + // name length in symbols + size_t name_length= 0; bool last_char_is_space= TRUE; - + while (*name) { #if defined(USE_MB) && defined(USE_MB_IDENT) @@ -2841,7 +2843,7 @@ bool check_column_name(const char *name) name_length++; } /* Error if empty or too long column name */ - return last_char_is_space || (uint) name_length > NAME_CHAR_LEN; + return last_char_is_space || (name_length > NAME_CHAR_LEN); } @@ -4676,13 +4678,6 @@ void TABLE_LIST::reinit_before_use(THD *thd) parent_embedding->nested_join->join_list.head() == embedded); mdl_request.ticket= NULL; - /* - Since we manipulate with the metadata lock type in open_table(), - we need to reset it to the parser default, to restore things back - to first-execution state. - */ - mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); } /* diff --git a/sql/table.h b/sql/table.h index cef547ae830..fb312dac132 100644 --- a/sql/table.h +++ b/sql/table.h @@ -20,6 +20,7 @@ #include "sql_plist.h" #include "sql_list.h" /* Sql_alloc */ #include "mdl.h" +#include "datadict.h" #ifndef MYSQL_CLIENT @@ -305,14 +306,6 @@ enum tmp_table_type NO_TMP_TABLE, NON_TRANSACTIONAL_TMP_TABLE, TRANSACTIONAL_TMP_TABLE, INTERNAL_TMP_TABLE, SYSTEM_TMP_TABLE }; - -enum frm_type_enum -{ - FRMTYPE_ERROR= 0, - FRMTYPE_TABLE, - FRMTYPE_VIEW -}; - enum release_type { RELEASE_NORMAL, RELEASE_WAIT_FOR_DROP }; typedef struct st_filesort_info @@ -597,7 +590,6 @@ struct TABLE_SHARE enum enum_ha_unused unused2; uint ref_count; /* How many TABLE objects uses this */ - uint open_count; /* Number of tables in open list */ uint blob_ptr_size; /* 4 or 8 */ uint key_block_size; /* create key_block_size, if used */ uint null_bytes, last_null_bit_pos; @@ -747,7 +739,7 @@ struct TABLE_SHARE /* Must all TABLEs be reopened? */ - inline bool needs_reopen() + inline bool needs_reopen() const { return version != refresh_version; } @@ -1600,20 +1592,21 @@ struct TABLE_LIST OPEN_STUB } open_strategy; /** - Indicates the locking strategy for the object being opened: - whether the associated metadata lock is shared or exclusive. + Indicates the locking strategy for the object being opened. */ enum { - /* Take a shared metadata lock before the object is opened. */ - SHARED_MDL= 0, /* - Take a exclusive metadata lock before the object is opened. - If opening is successful, downgrade to a shared lock. + Take metadata lock specified by 'mdl_request' member before + the object is opened. Do nothing after that. + */ + OTLS_NONE= 0, + /* + Take (exclusive) metadata lock specified by 'mdl_request' member + before object is opened. If opening is successful, downgrade to + a shared lock. */ - EXCLUSIVE_DOWNGRADABLE_MDL, - /* Take a exclusive metadata lock before the object is opened. */ - EXCLUSIVE_MDL + OTLS_DOWNGRADE_IF_EXISTS } lock_strategy; /* For transactional locking. */ int lock_timeout; /* NOWAIT or WAIT [X] */ @@ -2039,7 +2032,7 @@ void update_create_info_from_table(HA_CREATE_INFO *info, TABLE *form); bool check_and_convert_db_name(LEX_STRING *db, bool preserve_lettercase); bool check_db_name(LEX_STRING *db); bool check_column_name(const char *name); -bool check_table_name(const char *name, uint length, bool check_for_path_chars); +bool check_table_name(const char *name, size_t length, bool check_for_path_chars); int rename_file_ext(const char * from,const char * to,const char * ext); char *get_field(MEM_ROOT *mem, Field *field); bool get_field(MEM_ROOT *mem, Field *field, class String *res); diff --git a/sql/transaction.cc b/sql/transaction.cc index 5047de1ccdc..78551d6fcf7 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -96,7 +96,18 @@ bool trans_begin(THD *thd, uint flags) DBUG_ASSERT(!thd->locked_tables_mode); - if (trans_commit_implicit(thd)) + if (thd->in_multi_stmt_transaction_mode() || + (thd->variables.option_bits & OPTION_TABLE_LOCK)) + { + thd->variables.option_bits&= ~OPTION_TABLE_LOCK; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= test(ha_commit_trans(thd, TRUE)); + } + + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + + if (res) DBUG_RETURN(TRUE); /* @@ -182,6 +193,14 @@ bool trans_commit_implicit(THD *thd) thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); thd->transaction.all.modified_non_trans_table= FALSE; + /* + Upon implicit commit, reset the current transaction + isolation level. We do not care about + @@session.completion_type since it's documented + to not have any effect on implicit commit. + */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + DBUG_RETURN(res); } @@ -234,7 +253,11 @@ bool trans_commit_stmt(THD *thd) DBUG_ENTER("trans_commit_stmt"); int res= FALSE; if (thd->transaction.stmt.ha_list) + { res= ha_commit_trans(thd, FALSE); + if (! thd->in_active_multi_stmt_transaction()) + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + } if (res) /* @@ -265,6 +288,8 @@ bool trans_rollback_stmt(THD *thd) ha_rollback_trans(thd, FALSE); if (thd->transaction_rollback_request && !thd->in_sub_stmt) ha_rollback_trans(thd, TRUE); + if (! thd->in_active_multi_stmt_transaction()) + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; } RUN_HOOK(transaction, after_rollback, (thd, FALSE)); diff --git a/sql/tztime.cc b/sql/tztime.cc index b23456b5465..79f3b83553e 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1692,7 +1692,11 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) } for (TABLE_LIST *tl= tz_tables; tl; tl= tl->next_global) + { tl->table->use_all_columns(); + /* Force close at the end of the function to free memory. */ + tl->table->m_needs_reopen= TRUE; + } /* Now we are going to load leap seconds descriptions that are shared @@ -1781,7 +1785,6 @@ end_with_setting_default_tz: end_with_close: if (time_zone_tables_exist) { - thd->version--; /* Force close to free memory */ close_thread_tables(thd); thd->mdl_context.release_transactional_locks(); } diff --git a/storage/heap/CMakeLists.txt b/storage/heap/CMakeLists.txt index 32359759abc..2c0b65b0cc4 100755 --- a/storage/heap/CMakeLists.txt +++ b/storage/heap/CMakeLists.txt @@ -23,3 +23,9 @@ SET(HEAP_SOURCES _check.c _rectest.c hp_block.c hp_clear.c hp_close.c hp_create hp_rrnd.c hp_rsame.c hp_scan.c hp_static.c hp_update.c hp_write.c) MYSQL_ADD_PLUGIN(heap ${HEAP_SOURCES} STORAGE_ENGINE MANDATORY RECOMPILE_FOR_EMBEDDED) + +ADD_EXECUTABLE(hp_test1 hp_test1.c) +TARGET_LINK_LIBRARIES(hp_test1 mysys heap dbug strings) + +ADD_EXECUTABLE(hp_test2 hp_test2.c) +TARGET_LINK_LIBRARIES(hp_test2 mysys heap dbug strings) diff --git a/storage/heap/ha_heap.cc b/storage/heap/ha_heap.cc index 3abffc7087f..541650bd5e8 100644 --- a/storage/heap/ha_heap.cc +++ b/storage/heap/ha_heap.cc @@ -29,6 +29,10 @@ static handler *heap_create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root); +static int +heap_prepare_hp_create_info(TABLE *table_arg, bool internal_table, + HP_CREATE_INFO *hp_create_info); + int heap_panic(handlerton *hton, ha_panic_function flag) { @@ -96,43 +100,48 @@ const char **ha_heap::bas_ext() const int ha_heap::open(const char *name, int mode, uint test_if_locked) { - if ((test_if_locked & HA_OPEN_INTERNAL_TABLE) || - (!(file= heap_open(name, mode)) && my_errno == ENOENT)) + internal_table= test(test_if_locked & HA_OPEN_INTERNAL_TABLE); + if (internal_table || (!(file= heap_open(name, mode)) && my_errno == ENOENT)) { - HA_CREATE_INFO create_info; - internal_table= test(test_if_locked & HA_OPEN_INTERNAL_TABLE); - bzero(&create_info, sizeof(create_info)); + HP_CREATE_INFO create_info; + my_bool created_new_share; + int rc; file= 0; - if (!create(name, table, &create_info)) + if (heap_prepare_hp_create_info(table, internal_table, &create_info)) + goto end; + create_info.pin_share= TRUE; + + rc= heap_create(name, &create_info, &internal_share, &created_new_share); + my_free((uchar*) create_info.keydef, MYF(0)); + if (rc) + goto end; + + implicit_emptied= test(created_new_share); + if (internal_table) + file= heap_open_from_share(internal_share, mode); + else + file= heap_open_from_share_and_register(internal_share, mode); + + if (!file) { - file= internal_table ? - heap_open_from_share(internal_share, mode) : - heap_open_from_share_and_register(internal_share, mode); - if (!file) - { - /* Couldn't open table; Remove the newly created table */ - mysql_mutex_lock(&THR_LOCK_heap); - hp_free(internal_share); - mysql_mutex_unlock(&THR_LOCK_heap); - } - implicit_emptied= 1; + heap_release_share(internal_share, internal_table); + goto end; } } + ref_length= sizeof(HEAP_PTR); - if (file) - { - /* Initialize variables for the opened table */ - set_keys_for_scanning(); - /* - We cannot run update_key_stats() here because we do not have a - lock on the table. The 'records' count might just be changed - temporarily at this moment and we might get wrong statistics (Bug - #10178). Instead we request for update. This will be done in - ha_heap::info(), which is always called before key statistics are - used. + /* Initialize variables for the opened table */ + set_keys_for_scanning(); + /* + We cannot run update_key_stats() here because we do not have a + lock on the table. The 'records' count might just be changed + temporarily at this moment and we might get wrong statistics (Bug + #10178). Instead we request for update. This will be done in + ha_heap::info(), which is always called before key statistics are + used. */ - key_stat_version= file->s->key_stat_version-1; - } + key_stat_version= file->s->key_stat_version-1; +end: return (file ? 0 : 1); } @@ -624,18 +633,20 @@ ha_rows ha_heap::records_in_range(uint inx, key_range *min_key, } -int ha_heap::create(const char *name, TABLE *table_arg, - HA_CREATE_INFO *create_info) +static int +heap_prepare_hp_create_info(TABLE *table_arg, bool internal_table, + HP_CREATE_INFO *hp_create_info) { uint key, parts, mem_per_row= 0, keys= table_arg->s->keys; uint auto_key= 0, auto_key_type= 0; ha_rows max_rows; HP_KEYDEF *keydef; HA_KEYSEG *seg; - int error; TABLE_SHARE *share= table_arg->s; bool found_real_auto_increment= 0; + bzero(hp_create_info, sizeof(*hp_create_info)); + for (key= parts= 0; key < keys; key++) parts+= table_arg->key_info[key].key_parts; @@ -715,29 +726,45 @@ int ha_heap::create(const char *name, TABLE *table_arg, } } mem_per_row+= MY_ALIGN(share->reclength + 1, sizeof(char*)); - max_rows = (ha_rows) (table_arg->in_use->variables.max_heap_table_size / - (ulonglong) mem_per_row); if (table_arg->found_next_number_field) { keydef[share->next_number_index].flag|= HA_AUTO_KEY; found_real_auto_increment= share->next_number_key_offset == 0; } + hp_create_info->auto_key= auto_key; + hp_create_info->auto_key_type= auto_key_type; + hp_create_info->max_table_size=current_thd->variables.max_heap_table_size; + hp_create_info->with_auto_increment= found_real_auto_increment; + hp_create_info->internal_table= internal_table; + + max_rows= (ha_rows) (hp_create_info->max_table_size / mem_per_row); + if (share->max_rows && share->max_rows < max_rows) + max_rows= share->max_rows; + + hp_create_info->max_records= (ulong) max_rows; + hp_create_info->min_records= (ulong) share->min_rows; + hp_create_info->keys= share->keys; + hp_create_info->reclength= share->reclength; + hp_create_info->keydef= keydef; + return 0; +} + + +int ha_heap::create(const char *name, TABLE *table_arg, + HA_CREATE_INFO *create_info) +{ + int error; + my_bool created; HP_CREATE_INFO hp_create_info; - hp_create_info.auto_key= auto_key; - hp_create_info.auto_key_type= auto_key_type; + + error= heap_prepare_hp_create_info(table_arg, internal_table, + &hp_create_info); + if (error) + return error; hp_create_info.auto_increment= (create_info->auto_increment_value ? create_info->auto_increment_value - 1 : 0); - hp_create_info.max_table_size=current_thd->variables.max_heap_table_size; - hp_create_info.with_auto_increment= found_real_auto_increment; - hp_create_info.internal_table= internal_table; - max_rows = (ha_rows) (hp_create_info.max_table_size / mem_per_row); - error= heap_create(name, - keys, keydef, share->reclength, - (ulong) ((share->max_rows < max_rows && - share->max_rows) ? - share->max_rows : max_rows), - (ulong) share->min_rows, &hp_create_info, &internal_share); - my_free((uchar*) keydef, MYF(0)); + error= heap_create(name, &hp_create_info, &internal_share, &created); + my_free((uchar*) hp_create_info.keydef, MYF(0)); DBUG_ASSERT(file == 0); return (error); } diff --git a/storage/heap/hp_create.c b/storage/heap/hp_create.c index 85e632e5aad..cf0f5d5ba6d 100644 --- a/storage/heap/hp_create.c +++ b/storage/heap/hp_create.c @@ -21,24 +21,30 @@ static void init_block(HP_BLOCK *block,uint reclength,ulong min_records, /* Create a heap table */ -int heap_create(const char *name, uint keys, HP_KEYDEF *keydef, - uint reclength, ulong max_records, ulong min_records, - HP_CREATE_INFO *create_info, HP_SHARE **res) +int heap_create(const char *name, HP_CREATE_INFO *create_info, + HP_SHARE **res, my_bool *created_new_share) { uint i, j, key_segs, max_length, length; HP_SHARE *share= 0; HA_KEYSEG *keyseg; + HP_KEYDEF *keydef= create_info->keydef; + uint reclength= create_info->reclength; + uint keys= create_info->keys; + ulong min_records= create_info->min_records; + ulong max_records= create_info->max_records; DBUG_ENTER("heap_create"); if (!create_info->internal_table) { mysql_mutex_lock(&THR_LOCK_heap); - if ((share= hp_find_named_heap(name)) && share->open_count == 0) + share= hp_find_named_heap(name); + if (share && share->open_count == 0) { hp_free(share); share= 0; } - } + } + *created_new_share= (share == NULL); if (!share) { @@ -200,7 +206,11 @@ int heap_create(const char *name, uint keys, HP_KEYDEF *keydef, share->delete_on_close= 1; } if (!create_info->internal_table) + { + if (create_info->pin_share) + ++share->open_count; mysql_mutex_unlock(&THR_LOCK_heap); + } *res= share; DBUG_RETURN(0); diff --git a/storage/heap/hp_open.c b/storage/heap/hp_open.c index feafa5d5cf1..ef2ce15f9b3 100644 --- a/storage/heap/hp_open.c +++ b/storage/heap/hp_open.c @@ -74,12 +74,33 @@ HP_INFO *heap_open_from_share_and_register(HP_SHARE *share, int mode) { info->open_list.data= (void*) info; heap_open_list= list_add(heap_open_list,&info->open_list); + /* Unpin the share, it is now pinned by the file. */ + share->open_count--; } mysql_mutex_unlock(&THR_LOCK_heap); DBUG_RETURN(info); } +/** + Dereference a HEAP share and free it if it's not referenced. + We don't check open_count for internal tables since they + are always thread-local, i.e. referenced by a single thread. +*/ +void heap_release_share(HP_SHARE *share, my_bool internal_table) +{ + /* Couldn't open table; Remove the newly created table */ + if (internal_table) + hp_free(share); + else + { + mysql_mutex_lock(&THR_LOCK_heap); + if (--share->open_count == 0) + hp_free(share); + mysql_mutex_unlock(&THR_LOCK_heap); + } +} + /* Open heap table based on name diff --git a/storage/heap/hp_test1.c b/storage/heap/hp_test1.c index 911e3a285a2..535db60e237 100644 --- a/storage/heap/hp_test1.c +++ b/storage/heap/hp_test1.c @@ -38,6 +38,7 @@ int main(int argc, char **argv) HA_KEYSEG keyseg[4]; HP_CREATE_INFO hp_create_info; HP_SHARE *tmp_share; + my_bool unused; MY_INIT(argv[0]); filename= "test1"; @@ -45,6 +46,11 @@ int main(int argc, char **argv) bzero(&hp_create_info, sizeof(hp_create_info)); hp_create_info.max_table_size= 1024L*1024L; + hp_create_info.keys= 1; + hp_create_info.keydef= keyinfo; + hp_create_info.reclength= 30; + hp_create_info.max_records= (ulong) flag*100000L; + hp_create_info.min_records= 10UL; keyinfo[0].keysegs=1; keyinfo[0].seg=keyseg; @@ -55,13 +61,12 @@ int main(int argc, char **argv) keyinfo[0].seg[0].charset= &my_charset_latin1; keyinfo[0].seg[0].null_bit= 0; keyinfo[0].flag = HA_NOSAME; - + deleted=0; bzero((uchar*) flags,sizeof(flags)); printf("- Creating heap-file\n"); - if (heap_create(filename,1,keyinfo,30,(ulong) flag*100000L,10L, - &hp_create_info, &tmp_share) || + if (heap_create(filename, &hp_create_info, &tmp_share, &unused) || !(file= heap_open(filename, 2))) goto err; printf("- Writing records:s\n"); diff --git a/storage/heap/hp_test2.c b/storage/heap/hp_test2.c index 8216c7360b4..5b0c8d8685d 100644 --- a/storage/heap/hp_test2.c +++ b/storage/heap/hp_test2.c @@ -15,16 +15,6 @@ /* Test av isam-databas: stor test */ -#ifndef USE_MY_FUNC /* We want to be able to dbug this !! */ -#define USE_MY_FUNC -#endif -#ifdef DBUG_OFF -#undef DBUG_OFF -#endif -#ifndef SAFEMALLOC -#define SAFEMALLOC -#endif - #include "heapdef.h" /* Because of hp_find_block */ #include <signal.h> @@ -65,15 +55,21 @@ int main(int argc, char *argv[]) HEAP_PTR UNINIT_VAR(position); HP_CREATE_INFO hp_create_info; CHARSET_INFO *cs= &my_charset_latin1; + my_bool unused; MY_INIT(argv[0]); /* init my_sys library & pthreads */ filename= "test2"; filename2= "test2_2"; file=file2=0; get_options(argc,argv); - + bzero(&hp_create_info, sizeof(hp_create_info)); hp_create_info.max_table_size= 1024L*1024L; + hp_create_info.keys= keys; + hp_create_info.keydef= keyinfo; + hp_create_info.reclength= reclength; + hp_create_info.max_records= (ulong) flag*100000L; + hp_create_info.min_records= (ulong) recant/2; write_count=update=opt_delete=0; key_check=0; @@ -125,8 +121,7 @@ int main(int argc, char *argv[]) bzero((char*) key3,sizeof(key3)); printf("- Creating heap-file\n"); - if (heap_create(filename,keys,keyinfo,reclength,(ulong) flag*100000L, - (ulong) recant/2, &hp_create_info, &tmp_share) || + if (heap_create(filename, &hp_create_info, &tmp_share, &unused) || !(file= heap_open(filename, 2))) goto err; signal(SIGINT,endprog); @@ -563,8 +558,10 @@ int main(int argc, char *argv[]) heap_close(file2); printf("- Creating output heap-file 2\n"); - if (heap_create(filename2, 1, keyinfo, reclength, 0L, 0L, &hp_create_info, - &tmp_share) || + hp_create_info.keys= 1; + hp_create_info.max_records= 0; + hp_create_info.min_records= 0; + if (heap_create(filename2, &hp_create_info, &tmp_share, &unused) || !(file2= heap_open_from_share_and_register(tmp_share, 2))) goto err; diff --git a/storage/myisam/mi_dbug.c b/storage/myisam/mi_dbug.c index 45882eda6af..e450e81cecb 100644 --- a/storage/myisam/mi_dbug.c +++ b/storage/myisam/mi_dbug.c @@ -164,7 +164,19 @@ void _mi_print_key(FILE *stream, register HA_KEYSEG *keyseg, } /* print_key */ -#ifdef EXTRA_DEBUG +#ifdef EXTRA_DEBUG +/** + Check if the named table is in the open list. + + @param[in] name table path as in MYISAM_SHARE::unique_file_name + @param[in] where verbal description of caller + + @retval TRUE table is in open list + @retval FALSE table is not in open list + + @note This function takes THR_LOCK_myisam. Do not call it when + this mutex is locked by this thread already. +*/ my_bool check_table_is_closed(const char *name, const char *where) { @@ -173,6 +185,7 @@ my_bool check_table_is_closed(const char *name, const char *where) DBUG_ENTER("check_table_is_closed"); (void) fn_format(filename,name,"",MI_NAME_IEXT,4+16+32); + mysql_mutex_lock(&THR_LOCK_myisam); for (pos=myisam_open_list ; pos ; pos=pos->next) { MI_INFO *info=(MI_INFO*) pos->data; @@ -181,12 +194,14 @@ my_bool check_table_is_closed(const char *name, const char *where) { if (share->last_version) { + mysql_mutex_unlock(&THR_LOCK_myisam); fprintf(stderr,"Warning: Table: %s is open on %s\n", name,where); DBUG_PRINT("warning",("Table: %s is open on %s", name,where)); DBUG_RETURN(1); } } } + mysql_mutex_unlock(&THR_LOCK_myisam); DBUG_RETURN(0); } #endif /* EXTRA_DEBUG */ |