mariadb/mysql-test/include/handler.inc
Dmitry Lenev afd15c43a9 Implement new type-of-operation-aware metadata locks.
Add a wait-for graph based deadlock detector to the
MDL subsystem.

Fixes bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock" and
bug #37346 "innodb does not detect deadlock between update and
alter table".

The first bug manifested itself as an unwarranted abort of a
transaction with ER_LOCK_DEADLOCK error by a concurrent ALTER
statement, when this transaction tried to repeat use of a
table, which it has already used in a similar fashion before
ALTER started.

The second bug showed up as a deadlock between table-level
locks and InnoDB row locks, which was "detected" only after
innodb_lock_wait_timeout timeout.

A transaction would start using the table and modify a few
rows.
Then ALTER TABLE would come in, and start copying rows
into a temporary table. Eventually it would stumble on
the modified records and get blocked on a row lock.
The first transaction would try to do more updates, and get
blocked on thr_lock.c lock.
This situation of circular wait would only get resolved
by a timeout.

Both these bugs stemmed from inadequate solutions to the
problem of deadlocks occurring between different
locking subsystems.

In the first case we tried to avoid deadlocks between metadata
locking and table-level locking subsystems, when upgrading shared
metadata lock to exclusive one.
Transactions holding the shared lock on the table and waiting for
some table-level lock used to be aborted too aggressively.

We also allowed ALTER TABLE to start in presence of transactions
that modify the subject table. ALTER TABLE acquires
TL_WRITE_ALLOW_READ lock at start, and that block all writes
against the table (naturally, we don't want any writes to be lost
when switching the old and the new table). TL_WRITE_ALLOW_READ
lock, in turn, would block the started transaction on thr_lock.c
lock, should they do more updates. This, again, lead to the need
to abort such transactions.

The second bug occurred simply because we didn't have any
mechanism to detect deadlocks between the table-level locks
in thr_lock.c and row-level locks in InnoDB, other than
innodb_lock_wait_timeout.

This patch solves both these problems by moving lock conflicts
which are causing these deadlocks into the metadata locking
subsystem, thus making it possible to avoid or detect such
deadlocks inside MDL.

To do this we introduce new type-of-operation-aware metadata
locks, which allow MDL subsystem to know not only the fact that
transaction has used or is going to use some object but also what
kind of operation it has carried out or going to carry out on the
object.

This, along with the addition of a special kind of upgradable
metadata lock, allows ALTER TABLE to wait until all
transactions which has updated the table to go away.
This solves the second issue.
Another special type of upgradable metadata lock is acquired
by LOCK TABLE WRITE. This second lock type allows to solve the
first issue, since abortion of table-level locks in event of
DDL under LOCK TABLES becomes also unnecessary.

Below follows the list of incompatible changes introduced by
this patch:

- From now on, ALTER TABLE and CREATE/DROP TRIGGER SQL (i.e. those
  statements that acquire TL_WRITE_ALLOW_READ lock)
  wait for all transactions which has *updated* the table to
  complete.

- From now on, LOCK TABLES ... WRITE, REPAIR/OPTIMIZE TABLE
  (i.e. all statements which acquire TL_WRITE table-level lock) wait
  for all transaction which *updated or read* from the table
  to complete.
  As a consequence, innodb_table_locks=0 option no longer applies
  to LOCK TABLES ... WRITE.

- DROP DATABASE, DROP TABLE, RENAME TABLE no longer abort
  statements or transactions which use tables being dropped or
  renamed, and instead wait for these transactions to complete.

- Since LOCK TABLES WRITE now takes a special metadata lock,
  not compatible with with reads or writes against the subject table
  and transaction-wide, thr_lock.c deadlock avoidance algorithm
  that used to ensure absence of deadlocks between LOCK TABLES
  WRITE and other statements is no longer sufficient, even for
  MyISAM. The wait-for graph based deadlock detector of MDL
  subsystem may sometimes be necessary and is involved. This may
  lead to ER_LOCK_DEADLOCK error produced for multi-statement
  transactions even if these only use MyISAM:

  session 1:         session 2:
  begin;

  update t1 ...      lock table t2 write, t1 write;
                     -- gets a lock on t2, blocks on t1

  update t2 ...
  (ER_LOCK_DEADLOCK)

- Finally,  support of LOW_PRIORITY option for LOCK TABLES ... WRITE
  was abandoned.
  LOCK TABLE ... LOW_PRIORITY WRITE from now on has the same
  priority as the usual LOCK TABLE ... WRITE.
  SELECT HIGH PRIORITY no longer trumps LOCK TABLE ... WRITE  in
  the wait queue.

- We do not take upgradable metadata locks on implicitly
  locked tables. So if one has, say, a view v1 that uses
  table t1, and issues:
  LOCK TABLE v1 WRITE;
  FLUSH TABLE t1; -- (or just 'FLUSH TABLES'),
  an error is produced.
  In order to be able to perform DDL on a table under LOCK TABLES,
  the table must be locked explicitly in the LOCK TABLES list.
2010-02-01 14:43:06 +03:00

1493 lines
39 KiB
PHP

# include/handler.inc
#
# The variables
# $engine_type -- storage engine to be tested
# $other_engine_type -- storage engine <> $engine_type
# $other_handler_engine_type -- storage engine <> $engine_type, if possible
# 1. $other_handler_engine_type must support handler
# 2. $other_handler_engine_type must point to an all
# time available storage engine
# 2006-08 MySQL 5.1 MyISAM and MEMORY only
# have to be set before sourcing this script.
-- source include/not_embedded.inc
#
# test of HANDLER ...
#
# Last update:
# 2006-07-31 ML test refactored (MySQL 5.1)
# code of t/handler.test and t/innodb_handler.test united
# main testing code put into include/handler.inc
#
eval SET SESSION STORAGE_ENGINE = $engine_type;
--disable_warnings
drop table if exists t1,t3,t4,t5;
--enable_warnings
create table t1 (a int, b char(10), key a(a), key b(a,b));
insert into t1 values
(17,"ddd"),(18,"eee"),(19,"fff"),(19,"yyy"),
(14,"aaa"),(15,"bbb"),(16,"ccc"),(16,"xxx"),
(20,"ggg"),(21,"hhh"),(22,"iii");
handler t1 open as t2;
-- error 1064
handler t2 read a=(SELECT 1);
handler t2 read a first;
handler t2 read a next;
handler t2 read a next;
handler t2 read a prev;
handler t2 read a last;
handler t2 read a prev;
handler t2 read a prev;
handler t2 read a first;
handler t2 read a prev;
handler t2 read a last;
handler t2 read a prev;
handler t2 read a next;
handler t2 read a next;
handler t2 read a=(15);
handler t2 read a=(16);
--error 1070
handler t2 read a=(19,"fff");
handler t2 read b=(19,"fff");
handler t2 read b=(19,"yyy");
handler t2 read b=(19);
--error 1109
handler t1 read a last;
handler t2 read a=(11);
handler t2 read a>=(11);
handler t2 read a=(18);
handler t2 read a>=(18);
handler t2 read a>(18);
handler t2 read a<=(18);
handler t2 read a<(18);
handler t2 read a first limit 5;
handler t2 read a next limit 3;
handler t2 read a prev limit 10;
handler t2 read a>=(16) limit 4;
handler t2 read a>=(16) limit 2,2;
handler t2 read a last limit 3;
handler t2 read a=(19);
handler t2 read a=(19) where b="yyy";
handler t2 read first;
handler t2 read next;
handler t2 read next;
--error 1064
handler t2 read last;
handler t2 close;
handler t1 open;
handler t1 read a next; # this used to crash as a bug#5373
handler t1 read a next;
handler t1 close;
handler t1 open;
handler t1 read a prev; # this used to crash as a bug#5373
handler t1 read a prev;
handler t1 close;
handler t1 open as t2;
handler t2 read first;
eval alter table t1 engine = $engine_type;
--error 1109
handler t2 read first;
#
# DROP TABLE / ALTER TABLE
#
handler t1 open as t2;
drop table t1;
create table t1 (a int);
insert into t1 values (17);
--error 1109
handler t2 read first;
handler t1 open as t2;
eval alter table t1 engine=$other_engine_type;
--error 1109
handler t2 read first;
drop table t1;
#
# Test case for the bug #787
#
create table t1 (a int);
insert into t1 values (1),(2),(3),(4),(5),(6);
delete from t1 limit 2;
handler t1 open;
handler t1 read first;
handler t1 read first limit 1,1;
handler t1 read first limit 2,2;
delete from t1 limit 3;
handler t1 read first;
drop table t1;
#
# Test for #751
#
create table t1(a int, index(a));
insert into t1 values (1), (2), (3);
handler t1 open;
--error 1054
handler t1 read a=(W);
--error 1210
handler t1 read a=(a);
drop table t1;
#
# BUG#2304
#
create table t1 (a char(5));
insert into t1 values ("Ok");
handler t1 open as t;
handler t read first;
use mysql;
handler t read first;
handler t close;
handler test.t1 open as t;
handler t read first;
handler t close;
use test;
drop table t1;
#
# BUG#3649
#
create table t1 ( a int, b int, INDEX a (a) );
insert into t1 values (1,2), (2,1);
handler t1 open;
handler t1 read a=(1) where b=2;
handler t1 read a=(1) where b=3;
handler t1 read a=(1) where b=1;
handler t1 close;
drop table t1;
#
# Check if two database names beginning the same are seen as different.
#
# This database begins like the usual 'test' database.
#
--disable_warnings
drop database if exists test_test;
--enable_warnings
create database test_test;
use test_test;
create table t1(table_id char(20) primary key);
insert into t1 values ('test_test.t1');
insert into t1 values ('');
handler t1 open;
handler t1 read first limit 9;
create table t2(table_id char(20) primary key);
insert into t2 values ('test_test.t2');
insert into t2 values ('');
handler t2 open;
handler t2 read first limit 9;
#
# This is the usual 'test' database.
#
use test;
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1(table_id char(20) primary key);
insert into t1 values ('test.t1');
insert into t1 values ('');
--error 1066
handler t1 open;
#
# Check accesibility of all the tables.
#
use test;
--error 1064
handler test.t1 read first limit 9;
--error 1064
handler test_test.t1 read first limit 9;
handler t1 read first limit 9;
--error 1064
handler test_test.t2 read first limit 9;
handler t2 read first limit 9;
#
# Cleanup.
#
--error 1064
handler test_test.t1 close;
handler t1 close;
drop table test_test.t1;
--error 1064
handler test_test.t2 close;
handler t2 close;
drop table test_test.t2;
drop database test_test;
#
use test;
--error 1064
handler test.t1 close;
--error 1109
handler t1 close;
drop table test.t1;
#
# BUG#4335
#
--disable_warnings
drop database if exists test_test;
drop table if exists t1;
drop table if exists t2;
drop table if exists t3;
--enable_warnings
create database test_test;
use test_test;
create table t1 (c1 char(20));
insert into t1 values ('test_test.t1');
create table t3 (c1 char(20));
insert into t3 values ('test_test.t3');
handler t1 open;
handler t1 read first limit 9;
handler t1 open h1;
handler h1 read first limit 9;
use test;
create table t1 (c1 char(20));
create table t2 (c1 char(20));
create table t3 (c1 char(20));
insert into t1 values ('t1');
insert into t2 values ('t2');
insert into t3 values ('t3');
--error 1066
handler t1 open;
--error 1066
handler t2 open t1;
--error 1066
handler t3 open t1;
handler t1 read first limit 9;
--error 1064
handler test.t1 close;
--error 1066
handler test.t1 open h1;
--error 1066
handler test_test.t1 open h1;
handler test_test.t3 open h3;
handler test.t1 open h2;
handler t1 read first limit 9;
handler h1 read first limit 9;
handler h2 read first limit 9;
handler h3 read first limit 9;
handler h2 read first limit 9;
--error 1064
handler test.h1 close;
handler t1 close;
handler h1 close;
handler h2 close;
--error 1109
handler t1 read first limit 9;
--error 1109
handler h1 read first limit 9;
--error 1109
handler h2 read first limit 9;
handler h3 read first limit 9;
handler h3 read first limit 9;
use test_test;
handler h3 read first limit 9;
--error 1064
handler test.h3 read first limit 9;
handler h3 close;
use test;
drop table t3;
drop table t2;
drop table t1;
drop database test_test;
#
# Test if fix for BUG#4286 correctly closes handler tables.
#
create table t1 (c1 char(20));
insert into t1 values ("t1");
handler t1 open as h1;
handler h1 read first limit 9;
create table t2 (c1 char(20));
insert into t2 values ("t2");
handler t2 open as h2;
handler h2 read first limit 9;
create table t3 (c1 char(20));
insert into t3 values ("t3");
handler t3 open as h3;
handler h3 read first limit 9;
create table t4 (c1 char(20));
insert into t4 values ("t4");
handler t4 open as h4;
handler h4 read first limit 9;
create table t5 (c1 char(20));
insert into t5 values ("t5");
handler t5 open as h5;
handler h5 read first limit 9;
# close first
eval alter table t1 engine=$other_handler_engine_type;
--error 1109
handler h1 read first limit 9;
handler h2 read first limit 9;
handler h3 read first limit 9;
handler h4 read first limit 9;
handler h5 read first limit 9;
# close last
eval alter table t5 engine=$other_handler_engine_type;
--error 1109
handler h1 read first limit 9;
handler h2 read first limit 9;
handler h3 read first limit 9;
handler h4 read first limit 9;
--error 1109
handler h5 read first limit 9;
# close middle
eval alter table t3 engine=$other_handler_engine_type;
--error 1109
handler h1 read first limit 9;
handler h2 read first limit 9;
--error 1109
handler h3 read first limit 9;
handler h4 read first limit 9;
--error 1109
handler h5 read first limit 9;
handler h2 close;
handler h4 close;
# close all depending handler tables
handler t1 open as h1_1;
handler t1 open as h1_2;
handler t1 open as h1_3;
handler h1_1 read first limit 9;
handler h1_2 read first limit 9;
handler h1_3 read first limit 9;
eval alter table t1 engine=$engine_type;
--error 1109
handler h1_1 read first limit 9;
--error 1109
handler h1_2 read first limit 9;
--error 1109
handler h1_3 read first limit 9;
drop table t1;
drop table t2;
drop table t3;
drop table t4;
drop table t5;
#
# Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
#
create table t1 (c1 int);
insert into t1 values (1);
# client 1
handler t1 open;
handler t1 read first;
# client 2
connect (con2,localhost,root,,);
connection con2;
--exec echo send the below to another connection, do not wait for the result
send optimize table t1;
--sleep 1
# client 1
--exec echo proceed with the normal connection
connection default;
handler t1 read next;
handler t1 close;
# client 2
--exec echo read the result from the other connection
connection con2;
reap;
# client 1
--exec echo proceed with the normal connection
connection default;
drop table t1;
CREATE TABLE t1 ( no1 smallint(5) NOT NULL default '0', no2 int(10) NOT NULL default '0', PRIMARY KEY (no1,no2));
INSERT INTO t1 VALUES (1,274),(1,275),(2,6),(2,8),(4,1),(4,2);
HANDLER t1 OPEN;
HANDLER t1 READ `primary` = (1, 1000);
HANDLER t1 READ `primary` PREV;
DROP TABLE t1;
# End of 4.1 tests
#
# Addendum to Bug#14397 - OPTIMIZE TABLE with an open HANDLER causes a crash
# Show that DROP TABLE can no longer deadlock against
# FLUSH TABLES WITH READ LOCK. This is a 5.0 issue.
#
create table t1 (c1 int);
insert into t1 values (14397);
flush tables with read lock;
# The thread with the global read lock cannot drop the table itself:
--error 1223
drop table t1;
#
# client 2
# We need a second connection to try the drop.
# The drop waits for the global read lock to go away.
# Without the addendum fix it locked LOCK_open before entering the wait loop.
connection con2;
--exec echo send the below to another connection, do not wait for the result
send drop table t1;
--sleep 1
#
# client 1
# Now we need something that wants LOCK_open. A simple table access which
# opens the table does the trick.
--exec echo proceed with the normal connection
connection default;
# This would hang on LOCK_open without the 5.0 addendum fix.
select * from t1;
# Release the read lock. This should make the DROP go through.
unlock tables;
#
# client 2
# Read the result of the drop command.
connection con2;
--exec echo read the result from the other connection
reap;
#
# client 1
# Now back to normal operation. The table should not exist any more.
--exec echo proceed with the normal connection
connection default;
--error 1146
select * from t1;
# Just to be sure and not confuse the next test case writer.
drop table if exists t1;
#
# Bug#25856 - HANDLER table OPEN in one connection lock DROP TABLE in another one
#
--disable_warnings
drop table if exists t1;
--enable_warnings
eval create table t1 (a int) ENGINE=$other_engine_type;
--echo --> client 2
connection con2;
--error 1031
handler t1 open;
--echo --> client 1
connection default;
drop table t1;
disconnect con2;
#
# Bug#30632 HANDLER read failure causes hang
#
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1 (a int);
handler t1 open as t1_alias;
--error 1176
handler t1_alias read a next;
--error 1054
handler t1_alias READ a next where inexistent > 0;
--error 1176
handler t1_alias read a next;
--error 1054
handler t1_alias READ a next where inexistent > 0;
handler t1_alias close;
drop table t1;
#
# Bug#21587 FLUSH TABLES causes server crash when used with HANDLER statements
#
--disable_warnings
drop table if exists t1,t2;
--enable_warnings
create table t1 (c1 int);
create table t2 (c1 int);
insert into t1 values (1);
insert into t2 values (2);
--echo connection: default
handler t1 open;
handler t1 read first;
connect (flush,localhost,root,,);
connection flush;
--echo connection: flush
--send flush tables;
connect (waiter,localhost,root,,);
connection waiter;
--echo connection: waiter
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Flushing tables";
--source include/wait_condition.inc
connection default;
--echo connection: default
handler t2 open;
handler t2 read first;
handler t1 read next;
handler t1 close;
handler t2 close;
connection flush;
reap;
connection default;
drop table t1,t2;
disconnect flush;
#
# Bug#31409 RENAME TABLE causes server crash or deadlock when used with HANDLER statements
#
--disable_warnings
drop table if exists t1, t0;
--enable_warnings
create table t1 (c1 int);
--echo connection: default
handler t1 open;
handler t1 read first;
connect (flush,localhost,root,,);
connection flush;
--echo connection: flush
--send rename table t1 to t0;
connection waiter;
--echo connection: waiter
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and info = "rename table t1 to t0";
--source include/wait_condition.inc
connection default;
--echo connection: default
--echo #
--echo # RENAME placed two pending locks and waits.
--echo # When HANDLER t0 OPEN does open_tables(), it calls
--echo # mysql_ha_flush(), which in turn closes the open HANDLER for t1.
--echo # RENAME TABLE gets unblocked. If it gets scheduled quickly
--echo # and manages to complete before open_tables()
--echo # of HANDLER t0 OPEN, open_tables() and therefore the whole
--echo # HANDLER t0 OPEN succeeds. Otherwise open_tables()
--echo # notices a pending or active exclusive metadata lock on t2
--echo # and the whole HANDLER t0 OPEN fails with ER_LOCK_DEADLOCK
--echo # error.
--echo #
--error 0, ER_LOCK_DEADLOCK
handler t0 open;
--error 0, ER_UNKNOWN_TABLE
handler t0 close;
--echo connection: flush
connection flush;
reap;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--error ER_UNKNOWN_TABLE
handler t1 close;
connection default;
drop table t0;
connection flush;
disconnect flush;
--source include/wait_until_disconnected.inc
connection waiter;
disconnect waiter;
--source include/wait_until_disconnected.inc
connection default;
#
# Bug#30882 Dropping a temporary table inside a stored function may cause a server crash
#
# Test HANDLER statements in conjunction with temporary tables. While the temporary table
# is open by a HANDLER, no other statement can access it.
#
--disable_warnings
drop table if exists t1;
--enable_warnings
create temporary table t1 (a int, b char(1), key a(a), key b(a,b));
insert into t1 values (0,"a"),(1,"b"),(2,"c"),(3,"d"),(4,"e"),
(5,"f"),(6,"g"),(7,"h"),(8,"i"),(9,"j");
select a,b from t1;
handler t1 open as a1;
handler a1 read a first;
handler a1 read a next;
handler a1 read a next;
--error ER_CANT_REOPEN_TABLE
select a,b from t1;
handler a1 read a prev;
handler a1 read a prev;
handler a1 read a=(6) where b="g";
handler a1 close;
select a,b from t1;
handler t1 open as a2;
handler a2 read a first;
handler a2 read a last;
handler a2 read a prev;
handler a2 close;
drop table t1;
#
# Bug#31397 Inconsistent drop table behavior of handler tables.
#
--disable_warnings
drop table if exists t1,t2;
--enable_warnings
create table t1 (a int);
handler t1 open as t1_alias;
drop table t1;
create table t1 (a int);
handler t1 open as t1_alias;
flush tables;
drop table t1;
create table t1 (a int);
handler t1 open as t1_alias;
handler t1_alias close;
drop table t1;
create table t1 (a int);
handler t1 open as t1_alias;
handler t1_alias read first;
drop table t1;
--error ER_UNKNOWN_TABLE
handler t1_alias read next;
# Test that temporary tables associated with handlers are properly dropped.
create table t1 (a int);
create temporary table t2 (a int, key(a));
handler t1 open as a1;
handler t2 open as a2;
handler a2 read a first;
drop table t1, t2;
--error ER_UNKNOWN_TABLE
handler a2 read a next;
--error ER_UNKNOWN_TABLE
handler a1 close;
# Alter table drop handlers
create table t1 (a int, key(a));
create table t2 like t1;
handler t1 open as a1;
handler t2 open as a2;
handler a1 read a first;
handler a2 read a first;
alter table t1 add b int;
--error ER_UNKNOWN_TABLE
handler a1 close;
handler a2 close;
drop table t1, t2;
# Rename table drop handlers
create table t1 (a int, key(a));
handler t1 open as a1;
handler a1 read a first;
rename table t1 to t2;
--error ER_UNKNOWN_TABLE
handler a1 read a first;
drop table t2;
# Optimize table drop handlers
create table t1 (a int, key(a));
create table t2 like t1;
handler t1 open as a1;
handler t2 open as a2;
handler a1 read a first;
handler a2 read a first;
optimize table t1;
--error ER_UNKNOWN_TABLE
handler a1 close;
handler a2 close;
drop table t1, t2;
# Flush tables causes handlers reopen
create table t1 (a int, b char(1), key a(a), key b(a,b));
insert into t1 values (0,"a"),(1,"b"),(2,"c"),(3,"d"),(4,"e"),
(5,"f"),(6,"g"),(7,"h"),(8,"i"),(9,"j");
handler t1 open;
handler t1 read a first;
handler t1 read a next;
flush tables;
handler t1 read a next;
handler t1 read a next;
flush tables with read lock;
handler t1 read a next;
unlock tables;
drop table t1;
--error ER_UNKNOWN_TABLE
handler t1 read a next;
#
# Bug#41110: crash with handler command when used concurrently with alter table
# Bug#41112: crash in mysql_ha_close_table/get_lock_data with alter table
#
connect(con1,localhost,root,,);
connect(con2,localhost,root,,);
connection default;
--disable_warnings
drop table if exists t1;
--enable_warnings
--echo # First test case which is supposed trigger the execution
--echo # path on which problem was discovered.
create table t1 (a int);
insert into t1 values (1);
handler t1 open;
connection con1;
lock table t1 write;
send alter table t1 engine=memory;
connection con2;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and info = "alter table t1 engine=memory";
--source include/wait_condition.inc
connection default;
--error ER_ILLEGAL_HA
handler t1 read a next;
handler t1 close;
connection con1;
--reap
unlock tables;
drop table t1;
--echo # Now test case which was reported originally but which no longer
--echo # triggers execution path which has caused the problem.
connection default;
create table t1 (a int, key(a));
insert into t1 values (1);
handler t1 open;
connection con1;
send alter table t1 engine=memory;
connection con2;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and info = "alter table t1 engine=memory";
--source include/wait_condition.inc
connection default;
--echo # Since S metadata lock was already acquired at HANDLER OPEN time
--echo # and TL_READ lock requested by HANDLER READ is compatible with
--echo # ALTER's TL_WRITE_ALLOW_READ the below statement should succeed
--echo # without waiting. The old version of table should be used in it.
handler t1 read a next;
handler t1 close;
connection con1;
drop table t1;
disconnect con1;
--source include/wait_until_disconnected.inc
connection con2;
disconnect con2;
--source include/wait_until_disconnected.inc
connection default;
#
# Bug#44151 using handler commands on information_schema tables crashes server
#
USE information_schema;
--error ER_WRONG_USAGE
HANDLER COLUMNS OPEN;
USE test;
--echo #
--echo # Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL.
--echo #
--disable_warnings
drop table if exists t1, t2, t3;
--enable_warnings
create table t1 (a int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
create table t2 (a int, key a (a)) select * from t1;
create temporary table t3 (a int, key a (a)) select * from t2;
handler t1 open;
handler t2 open;
handler t3 open;
--echo #
--echo # LOCK TABLES implicitly closes all handlers.
--echo #
lock table t3 read;
--echo #
--echo # No HANDLER sql is available under lock tables anyway.
--echo #
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t1 open;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t1 read next;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t2 close;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t3 open;
--echo # After UNLOCK TABLES no handlers are around, they were
--echo # implicitly closed.
unlock tables;
drop temporary table t3;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--error ER_UNKNOWN_TABLE
handler t2 close;
--error ER_UNKNOWN_TABLE
handler t3 read next;
--echo #
--echo # Other operations also implicitly close handler:
--echo #
--echo # TRUNCATE
--echo #
handler t1 open;
truncate table t1;
--error ER_UNKNOWN_TABLE
handler t1 read next;
handler t1 open;
--echo #
--echo # CREATE TRIGGER
--echo #
create trigger t1_ai after insert on t1 for each row set @a=1;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # DROP TRIGGER
--echo #
handler t1 open;
drop trigger t1_ai;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # ALTER TABLE
--echo #
handler t1 open;
alter table t1 add column b int;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # ANALYZE TABLE
--echo #
handler t1 open;
analyze table t1;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # OPTIMIZE TABLE
--echo #
handler t1 open;
optimize table t1;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # REPAIR TABLE
--echo #
handler t1 open;
repair table t1;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # DROP TABLE, naturally.
--echo #
handler t1 open;
drop table t1;
--error ER_UNKNOWN_TABLE
handler t1 read next;
create table t1 (a int, b int, key a (a)) select a from t2;
--echo #
--echo # RENAME TABLE, naturally
--echo #
handler t1 open;
rename table t1 to t3;
--error ER_UNKNOWN_TABLE
handler t1 read next;
--echo #
--echo # CREATE TABLE (even with IF NOT EXISTS clause,
--echo # and the table exists).
--echo #
handler t2 open;
create table if not exists t2 (a int);
--error ER_UNKNOWN_TABLE
handler t2 read next;
rename table t3 to t1;
drop table t2;
--echo #
--echo # FLUSH TABLE doesn't close the table but loses the position
--echo #
handler t1 open;
handler t1 read a prev;
flush table t1;
handler t1 read a prev;
handler t1 close;
--echo #
--echo # FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE.
--echo #
handler t1 open;
handler t1 read a prev;
flush tables with read lock;
handler t1 read a prev;
handler t1 close;
unlock tables;
--echo #
--echo # Explore the effect of HANDLER locks on concurrent DDL
--echo #
handler t1 open;
--echo # Establishing auxiliary connections con1, con2, con3
connect(con1, localhost, root,,);
connect(con2, localhost, root,,);
connect(con3, localhost, root,,);
--echo # --> connection con1;
connection con1;
--echo # Sending:
--send drop table t1
--echo # We can't use connection 'default' as wait_condition will
--echo # autoclose handlers.
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop table t1' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1';
--source include/wait_condition.inc
--echo # --> connection default
connection default;
handler t1 read a prev;
handler t1 read a prev;
handler t1 close;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop table t1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Explore the effect of HANDLER locks in parallel with SELECT
--echo #
create table t1 (a int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
begin;
select * from t1;
handler t1 open;
handler t1 read a prev;
handler t1 read a prev;
handler t1 close;
--echo # --> connection con1;
connection con1;
--echo # Sending:
--send drop table t1
--echo # --> connection con2
connection con2;
--echo # Waiting for 'drop table t1' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1';
--source include/wait_condition.inc
--echo # --> connection default
connection default;
--echo # We can still use the table, it's part of the transaction
select * from t1;
--echo # Such are the circumstances that t1 is a part of transaction,
--echo # thus we can reopen it in the handler
handler t1 open;
--echo # We can commit the transaction, it doesn't close the handler
--echo # and doesn't let DROP to proceed.
commit;
handler t1 read a prev;
handler t1 read a prev;
handler t1 read a prev;
handler t1 close;
--echo # --> connection con1
connection con1;
--echo # Now drop can proceed
--echo # Reaping 'drop table t1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Demonstrate that HANDLER locks and transaction locks
--echo # reside in the same context, and we don't back-off
--echo # when have transaction or handler locks.
--echo #
create table t1 (a int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
create table t0 (a int, key a (a));
insert into t0 (a) values (1), (2), (3), (4), (5);
begin;
select * from t1;
--echo # --> connection con2
connection con2;
--echo # Sending:
send rename table t0 to t3, t1 to t0, t3 to t1;
--echo # --> connection con1
connection con1;
--echo # Waiting for 'rename table ...' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='rename table t0 to t3, t1 to t0, t3 to t1';
--source include/wait_condition.inc
--echo # --> connection default
connection default;
--error ER_LOCK_DEADLOCK
handler t0 open;
--error ER_LOCK_DEADLOCK
select * from t0;
handler t1 open;
commit;
handler t1 close;
--echo # --> connection con2
connection con2;
--echo # Reaping 'rename table ...'...
--reap
--echo # --> connection default
connection default;
handler t1 open;
handler t1 read a prev;
handler t1 close;
drop table t0;
--echo #
--echo # Originally there was a deadlock error in this test.
--echo # With implementation of deadlock detector
--echo # we no longer deadlock, but block and wait on a lock.
--echo # The HANDLER is auto-closed as soon as the connection
--echo # sees a pending conflicting lock against it.
--echo #
create table t2 (a int, key a (a));
handler t1 open;
--echo # --> connection con1
connection con1;
lock tables t2 read;
--echo # --> connection con2
connection con2;
--echo # Sending 'drop table t2'...
--send drop table t2
--echo # --> connection con1
connection con1;
--echo # Waiting for 'drop table t2' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2';
--source include/wait_condition.inc
--echo # --> connection default
connection default;
--echo # Sending 'select * from t2'
send select * from t2;
--echo # --> connection con1
connection con1;
--echo # Waiting for 'select * from t2' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='select * from t2';
unlock tables;
--echo # --> connection con2
connection con2;
--echo # Reaping 'drop table t2'...
--reap
--echo # --> connection default
connection default;
--echo # Reaping 'select * from t2'
--error ER_NO_SUCH_TABLE
reap;
handler t1 close;
--echo #
--echo # ROLLBACK TO SAVEPOINT releases transactional locks,
--echo # but has no effect on open HANDLERs
--echo #
create table t2 like t1;
create table t3 like t1;
begin;
--echo # Have something before the savepoint
select * from t3;
savepoint sv;
handler t1 open;
handler t1 read a first;
handler t1 read a next;
select * from t2;
--echo # --> connection con1
connection con1;
--echo # Sending:
--send drop table t1
--echo # --> connection con2
connection con2;
--echo # Sending:
--send drop table t2
--echo # --> connection default
connection default;
--echo # Let DROP TABLE statements sync in. We must use
--echo # a separate connection for that, because otherwise SELECT
--echo # will auto-close the HANDLERs, becaues there are pending
--echo # exclusive locks against them.
--echo # --> connection con3
connection con3;
--echo # Waiting for 'drop table t1' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1';
--source include/wait_condition.inc
--echo # Waiting for 'drop table t2' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2';
--source include/wait_condition.inc
--echo # Demonstrate that t2 lock was released and t2 was dropped
--echo # after ROLLBACK TO SAVEPOINT
--echo # --> connection default
connection default;
rollback to savepoint sv;
--echo # --> connection con2
connection con2;
--echo # Reaping 'drop table t2'...
--reap
--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler
--echo # lock.
--echo # --> connection default
connection default;
handler t1 read a next;
handler t1 read a next;
--echo # Demonstrate that the drop will go through as soon as we close the
--echo # HANDLER
handler t1 close;
--echo # connection con1
connection con1;
--echo # Reaping 'drop table t1'...
--reap
--echo # --> connection default
connection default;
commit;
drop table t3;
--echo #
--echo # A few special cases when using SAVEPOINT/ROLLBACK TO
--echo # SAVEPOINT and HANDLER.
--echo #
--echo # Show that rollback to the savepoint taken in the beginning
--echo # of the transaction doesn't release mdl lock on
--echo # the HANDLER that was opened later.
--echo #
create table t1 (a int, key a(a));
insert into t1 (a) values (1), (2), (3), (4), (5);
create table t2 like t1;
begin;
savepoint sv;
handler t1 open;
handler t1 read a first;
handler t1 read a next;
select * from t2;
--echo # --> connection con1
connection con1;
--echo # Sending:
--send drop table t1
--echo # --> connection con2
connection con2;
--echo # Sending:
--send drop table t2
--echo # --> connection default
connection default;
--echo # Let DROP TABLE statements sync in. We must use
--echo # a separate connection for that, because otherwise SELECT
--echo # will auto-close the HANDLERs, becaues there are pending
--echo # exclusive locks against them.
--echo # --> connection con3
connection con3;
--echo # Waiting for 'drop table t1' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1';
--source include/wait_condition.inc
--echo # Waiting for 'drop table t2' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2';
--source include/wait_condition.inc
--echo # Demonstrate that t2 lock was released and t2 was dropped
--echo # after ROLLBACK TO SAVEPOINT
--echo # --> connection default
connection default;
rollback to savepoint sv;
--echo # --> connection con2
connection con2;
--echo # Reaping 'drop table t2'...
--reap
--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler
--echo # lock.
--echo # --> connection default
connection default;
handler t1 read a next;
handler t1 read a next;
--echo # Demonstrate that the drop will go through as soon as we close the
--echo # HANDLER
handler t1 close;
--echo # connection con1
connection con1;
--echo # Reaping 'drop table t1'...
--reap
--echo # --> connection default
connection default;
commit;
--echo #
--echo # Show that rollback to the savepoint taken in the beginning
--echo # of the transaction works properly (no valgrind warnins, etc),
--echo # even though it's done after the HANDLER mdl lock that was there
--echo # at the beginning is released and added again.
--echo #
create table t1 (a int, key a(a));
insert into t1 (a) values (1), (2), (3), (4), (5);
create table t2 like t1;
create table t3 like t1;
insert into t3 (a) select a from t1;
begin;
handler t1 open;
savepoint sv;
handler t1 read a first;
select * from t2;
handler t1 close;
handler t3 open;
handler t3 read a first;
rollback to savepoint sv;
--echo # --> connection con1
connection con1;
drop table t1, t2;
--echo # Sending:
--send drop table t3
--echo # Let DROP TABLE statement sync in.
--echo # --> connection con2
connection con2;
--echo # Waiting for 'drop table t3' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t3';
--source include/wait_condition.inc
--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler
--echo # lock.
--echo # --> connection default
connection default;
handler t3 read a next;
--echo # Demonstrate that the drop will go through as soon as we close the
--echo # HANDLER
handler t3 close;
--echo # connection con1
connection con1;
--echo # Reaping 'drop table t3'...
--reap
--echo # --> connection default
connection default;
commit;
--echo #
--echo # If we have to wait on an exclusive locks while having
--echo # an open HANDLER, ER_LOCK_DEADLOCK is reported.
--echo #
create table t1 (a int, key a(a));
create table t2 like t1;
handler t1 open;
--echo # --> connection con1
connection con1;
lock table t1 write, t2 write;
--echo # --> connection default
connection default;
send drop table t2;
--echo # --> connection con2
connection con2;
--echo # Waiting for 'drop table t2' to get blocked...
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2';
--source include/wait_condition.inc
--echo # --> connection con1
connection con1;
--error ER_LOCK_DEADLOCK
drop table t1;
unlock tables;
--echo # --> connection default
connection default;
reap;
--echo # Demonstrate that there is no deadlock with FLUSH TABLE,
--echo # even though it is waiting for the other table to go away
create table t2 like t1;
--echo # Sending:
--send flush table t2
--echo # --> connection con2
connection con2;
drop table t1;
--echo # --> connection con1
connection con1;
unlock tables;
--echo # --> connection default
connection default;
--echo # Reaping 'flush table t2'...
--reap
drop table t2;
--echo #
--echo # Bug #46224 HANDLER statements within a transaction might
--echo # lead to deadlocks
--echo #
create table t1 (a int, key a(a));
insert into t1 values (1), (2);
--echo # --> connection default
connection default;
begin;
select * from t1;
handler t1 open;
--echo # --> connection con1
connection con1;
--echo # Sending:
--send lock tables t1 write
--echo # --> connection con2
connection con2;
--echo # Check that 'lock tables t1 write' waits until transaction which
--echo # has read from the table commits.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and info = "lock tables t1 write";
--source include/wait_condition.inc
--echo # --> connection default
connection default;
--echo # The below 'handler t1 read ...' should not be blocked as
--echo # 'lock tables t1 write' has not succeeded yet.
handler t1 read a next;
--echo # Unblock 'lock tables t1 write'.
commit;
--echo # --> connection con1
connection con1;
--echo # Reap 'lock tables t1 write'.
--reap
--echo # --> connection default
connection default;
--echo # Sending:
--send handler t1 read a next
--echo # --> connection con1
connection con1;
--echo # Waiting for 'handler t1 read a next' to get blocked...
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "handler t1 read a next";
--source include/wait_condition.inc
--echo # The below 'drop table t1' should be able to proceed without
--echo # waiting as it will force HANDLER to be closed.
drop table t1;
unlock tables;
--echo # --> connection default
connection default;
--echo # Reaping 'handler t1 read a next'...
--error ER_NO_SUCH_TABLE
--reap
handler t1 close;
--echo # --> connection con1
connection con1;
disconnect con1;
--source include/wait_until_disconnected.inc
--echo # --> connection con2
connection con2;
disconnect con2;
--source include/wait_until_disconnected.inc
--echo # --> connection con3
connection con3;
disconnect con3;
--source include/wait_until_disconnected.inc
connection default;
--echo #
--echo # A temporary table test.
--echo # Check that we don't loose positions of HANDLER opened
--echo # against a temporary table.
--echo #
create table t1 (a int, b int, key a (a));
insert into t1 (a) values (1), (2), (3), (4), (5);
create temporary table t2 (a int, b int, key a (a));
insert into t2 (a) select a from t1;
handler t1 open;
handler t1 read a next;
handler t2 open;
handler t2 read a next;
flush table t1;
handler t2 read a next;
--echo # Sic: the position is lost
handler t1 read a next;
select * from t1;
--echo # Sic: the position is not lost
handler t2 read a next;
--error ER_CANT_REOPEN_TABLE
select * from t2;
handler t2 read a next;
drop table t1;
drop temporary table t2;
--echo #
--echo # A test for lock_table_names()/unlock_table_names() function.
--echo # It should work properly in presence of open HANDLER.
--echo #
create table t1 (a int, b int, key a (a));
create table t2 like t1;
create table t3 like t1;
create table t4 like t1;
handler t1 open;
handler t2 open;
rename table t4 to t5, t3 to t4, t5 to t3;
handler t1 read first;
handler t2 read first;
drop table t1, t2, t3, t4;
--echo #
--echo # A test for FLUSH TABLES WITH READ LOCK and HANDLER statements.
--echo #
set autocommit=0;
create table t1 (a int, b int, key a (a));
insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
create table t2 like t1;
insert into t2 (a, b) select a, b from t1;
create table t3 like t1;
insert into t3 (a, b) select a, b from t1;
commit;
flush tables with read lock;
handler t1 open;
lock table t1 read;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
handler t1 read next;
--echo # This implicitly leaves LOCK TABLES but doesn't drop the GLR
--error ER_NO_SUCH_TABLE
lock table not_exists_write read;
--echo # We still have the read lock.
--error ER_CANT_UPDATE_WITH_READLOCK
drop table t1;
handler t1 open;
select a from t2;
handler t1 read next;
flush tables with read lock;
handler t2 open;
flush tables with read lock;
handler t1 read next;
select a from t3;
handler t2 read next;
handler t1 close;
rollback;
handler t2 close;
--error ER_CANT_UPDATE_WITH_READLOCK
drop table t1;
commit;
flush tables;
--error ER_CANT_UPDATE_WITH_READLOCK
drop table t1;
unlock tables;
drop table t1;
set autocommit=default;
drop table t2, t3;
--echo #
--echo # HANDLER statement and operation-type aware metadata locks.
--echo # Check that when we clone a ticket for HANDLER we downrade
--echo # the lock.
--echo #
--echo # Establish an auxiliary connection con1.
connect (con1,localhost,root,,);
--echo # -> connection default
connection default;
create table t1 (a int, b int, key a (a));
insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5);
begin;
insert into t1 (a, b) values (6, 6);
handler t1 open;
handler t1 read a last;
insert into t1 (a, b) values (7, 7);
handler t1 read a last;
commit;
--echo # -> connection con1
connection con1;
--echo # Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE.
lock table t1 write;
unlock tables;
--echo # -> connection default
connection default;
handler t1 read a prev;
handler t1 close;
--echo # Cleanup.
drop table t1;
--echo # -> connection con1
connection con1;
disconnect con1;
--source include/wait_until_disconnected.inc
--echo # -> connection default
connection default;