Bug #76588 Metadata lock is NOT released when SELECT completes in case of autocommit=0
Submitted: 3 Apr 2015 12:56 Modified: 7 Apr 2015 15:22
Reporter: Valeriy Kravchuk Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Locking Severity:S4 (Feature request)
Version:5.5. 5.6.23, 5.7.6 OS:Any
Assigned to: CPU Architecture:Any
Tags: autocommit, Metadata lock, prepare

[3 Apr 2015 12:56] Valeriy Kravchuk
Description:
Manual (http://dev.mysql.com/doc/refman/5.7/en/metadata-locking.html) says:

"To ensure transaction serializability, the server must not permit one session to perform a data definition language (DDL) statement on a table that is used in an uncompleted explicitly or implicitly started transaction in another session. The server achieves this by acquiring metadata locks on tables used within a transaction and deferring release of those locks until the transaction ends."

but later on the same page it says:

"Metadata locks acquired during a PREPARE statement are released once the statement has been prepared, even if preparation occurs within a multiple-statement transaction."

There are open questions here:

1. If PREPARE can safely release metadata lock, why "read only" multiple-statement transaction doing normal SELECT can NOT do the same? See test case below.

2. It seems PREPARE allows to p[rocees with some DDL statements (like TRUNCATE), but not with others (like ALTER TABLE ... ADD COLUMN ...). Why is that so?

3. Can we have metadata lock released as soon as we are done with the structure of the data set and proceed with fetching rows, at the isolation level that does NOT reqire locking rows in the table for SELECT at least?

How to repeat:
Let's use 5.7 to see metadata locks. In one session do:

[openxs@centos 5.7]$ bin/mysql --no-defaults -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.6-m16 MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'global_instrumentation';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0

mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0

and then continued with the test similar to yours:

mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select @@autocommit, @@tx_isolation;
+--------------+----------------+
| @@autocommit | @@tx_isolation |
+--------------+----------------+
| 1 | READ-COMMITTED |
+--------------+----------------+
1 row in set (0.01 sec)

mysql> set session autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@autocommit, @@tx_isolation;
+--------------+----------------+
| @@autocommit | @@tx_isolation |
+--------------+----------------+
| 0 | READ-COMMITTED |
+--------------+----------------+
1 row in set (0.00 sec)

mysql> select * from t limit 1;
+----+------+
| id | val |
+----+------+
| 1 | 1 |
+----+------+
1 row in set (0.00 sec)

Then in the other:

[openxs@centos 5.7]$ bin/mysql --no-defaults -uroot -proot test
mysql: [Warning] Using a password on the command line interface can be insecure.
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.7.6-m16 MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> truncate table t;

It hangs. In the original session:

mysql> select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: t
OBJECT_INSTANCE_BEGIN: 140450128308592
LOCK_TYPE: SHARED_READ
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:5585
OWNER_THREAD_ID: 27
OWNER_EVENT_ID: 17
*************************** 2. row ***************************
OBJECT_TYPE: GLOBAL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140450195436144
LOCK_TYPE: INTENTION_EXCLUSIVE
LOCK_DURATION: STATEMENT
LOCK_STATUS: GRANTED
SOURCE: sql_base.cc:5224
OWNER_THREAD_ID: 30
OWNER_EVENT_ID: 8
*************************** 3. row ***************************
OBJECT_TYPE: SCHEMA
OBJECT_SCHEMA: test
OBJECT_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140450195434272
LOCK_TYPE: INTENTION_EXCLUSIVE
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_base.cc:5209
OWNER_THREAD_ID: 30
OWNER_EVENT_ID: 8
*************************** 4. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: t
OBJECT_INSTANCE_BEGIN: 140450195434368
LOCK_TYPE: EXCLUSIVE
LOCK_DURATION: TRANSACTION
LOCK_STATUS: PENDING
SOURCE: sql_parse.cc:5585
OWNER_THREAD_ID: 30
OWNER_EVENT_ID: 8
*************************** 5. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: performance_schema
OBJECT_NAME: metadata_locks
OBJECT_INSTANCE_BEGIN: 140450128262384
LOCK_TYPE: SHARED_READ
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:5585
OWNER_THREAD_ID: 27
OWNER_EVENT_ID: 18
5 rows in set (0.00 sec)

Note SHARED_READ lock set on table t and EXCLUSIVE pending on the same table t above. Truncate is blocked as DDL:

mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO FROM performance_schema.metadata_locks INNER JOIN performance_schema.threads ON THREAD_ID = OWNER_THREAD_ID WHERE PROCESSLIST_ID <> CONNECTION_ID();
+-------------+---------------+-------------+---------------------+-------------+-----------+----------------+------------------+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_STATUS | THREAD_ID | PROCESSLIST_ID | PROCESSLIST_INFO |
+-------------+---------------+-------------+---------------------+-------------+-----------+----------------+------------------+
| GLOBAL | NULL | NULL | INTENTION_EXCLUSIVE | GRANTED | 30 | 8 | truncate table t |
| SCHEMA | test | NULL | INTENTION_EXCLUSIVE | GRANTED | 30 | 8 | truncate table t |
| TABLE | test | t | EXCLUSIVE | PENDING | 30 | 8 | truncate table t |
+-------------+---------------+-------------+---------------------+-------------+-----------+----------------+------------------+
3 rows in set (0.00 sec)

mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+---------------------------------+------------------+
| 5 | root | localhost | test | Query | 0 | starting | show processlist |
| 8 | root | localhost | test | Query | 50 | Waiting for table metadata lock | truncate table t |
+----+------+-----------+------+---------+------+---------------------------------+------------------+
2 rows in set (0.00 sec)

As soon as I complete transaction:

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

Truncate completes and we see no blocked sessions:

mysql> SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO FROM performance_schema.metadata_locks INNER JOIN performance_schema.threads ON THREAD_ID = OWNER_THREAD_ID WHERE PROCESSLIST_ID <> CONNECTION_ID();
Empty set (0.01 sec)

mysql> select * from t;
Empty set (0.00 sec)

Other test case to consider is:

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> prepare stmt from 'select sleep(20) from tt';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

Now, in other session try to run TRUNCATE (it works). With the same setup and few rows in the table, try to run ALTER in the second session after PREPARE is done in the first one:

alter table tt add column(c2 int);

It hangs, and we see a wait on metadata lock:

mysql> show processlist;
+----+------+----------------+------+---------+------+--------------------------
-------+-----------------------------------+
| Id | User | Host           | db   | Command | Time | State
       | Info                              |
+----+------+----------------+------+---------+------+--------------------------
-------+-----------------------------------+
|  2 | root | localhost:3872 | test | Query   |    0 | init
       | show processlist                  |
|  3 | root | localhost:3876 | test | Query   |    8 | Waiting for table metadat
a lock | alter table tt add column(c2 int) |
+----+------+----------------+------+---------+------+--------------------------
-------+-----------------------------------+
2 rows in set (0.00 sec)

So, probably different kinds of metadata locks are involved when ALTER is run vs TRUNCATE.

Suggested fix:
Please, consider implementing the same behaviour with regards to metadata locks for SELECT (in READ-COMMITTED isolation level at least, where users do not exect to see any locks, and even do not expect transaction to be started with SELECT) as we have for PREPARE stmt FROM 'same SELECT'.

Please, explain all the details of current metadata locks implementation in the manual in away that leads to no doubts on when the locks are set and released.
[3 Apr 2015 18:02] MySQL Verification Team
Valeriy,

Try some DML with latest 5.7. It should work. 

Let us know the result.
[3 Apr 2015 18:12] Valeriy Kravchuk
As you can see from "How to repeat", I've tested with 5.7.6. I've even provided the output form metadata_locks table in P_S that does not exist in versions before 5.7. TRUNCATE is blocked, metadata locks are not released after SELECT completes.
[6 Apr 2015 14:09] MySQL Verification Team
Valerii,

I have entirely understood your requests in this bug. 

Your first request is that behavior for SELECT statement(s) has the same demands on (metadata) locks as PREPARE statement used for preparing the exactly same SELECT statement(s).

Your second request is a demand for a thorough explanation in the manual on the behavior and interactions between various MDL locks.

Have I understood your bug report correctly ???

Thanks in advance.
[7 Apr 2015 6:43] Valeriy Kravchuk
My first request is to relax metadata locking for SELECT (at least at READ COMMITTED or lower isolation level) and release locks after we had got all the details about columns for the result set, in a same way as these locks are released after PREPARE completes for a prepared statement. But I'd like to release them even more than it happens for prepared statements, as EXECUTE for prepared statement takes metadata locks again. That would help some applications that do not expect any locks help for a long time by SELECT (or read only transactions, for that matter).

My second request is exactly the one you described. I'd like to see all metadata locks set by all statements to be described in details in the manual. What lock is set, when, when it is released (we see it's not that regular, PREPARE is already an exception) and what it prevents. More or less at the level we have for InnoDB locks, or even better.
[7 Apr 2015 15:22] MySQL Verification Team
A fully verified feature request and a documentation improvement request also.
[15 Jun 2016 5:08] MySQL Verification Team
>
>  why "read only" multiple-statement transaction doing normal SELECT can NOT do the same?
>

Actually,  your trx isn't readonly. It might still do changes later.
But now I tested it with START TRANSACTION READ ONLY;  and the behavior is the same ;-(
[31 Jan 2018 7:06] MySQL Verification Team
In the prepared statement case, it may throw error and need to be re-prepared according to

https://dev.mysql.com/doc/refman/5.5/en/statement-repreparation.html

Now if normal select in a transaction would error out similarly I think people would not be happy!