mysql - 包装在事务中时,Mysql DDL 查询卡在 Waiting for table metadata lock
问题描述
这是一种随机行为(重新启动 mysql 会话后未再次发生),当我将 DDL 查询包装在事务 T1 中并同时select
在同一张表上使用查询启动另一个事务 T2 时,DDL 查询卡住等待表预期的元数据锁定,以及任何其他选择查询等待 DDL 查询完成。但是在提交 T2 之后,DDL 应该获得元数据锁定并完成,但它仍然等待表元数据锁定状态。
连接 1 查询:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE merchants ADD COLUMN temp6 varchar(255);
连接 2 查询:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from merchants where account_id=null;
Empty set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
查询顺序为:
- 连接1:开始;
- 连接2:开始;
- 连接 2 : select * from 商户 where account_id=null;
- 连接 1 : ALTER TABLE 商家添加列 temp6 varchar(255);
- 连接2:提交;
Server version: 8.0.13 MySQL Community Server - GPL
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)
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 | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
| SCHEMA | merchant_onboarding_dev | NULL | INTENTION_EXCLUSIVE | GRANTED | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
| TABLE | merchant_onboarding_dev | merchants | SHARED_UPGRADABLE | GRANTED | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
| BACKUP LOCK | NULL | NULL | INTENTION_EXCLUSIVE | GRANTED | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
| TABLESPACE | NULL | merchant_onboarding_dev/merchants | INTENTION_EXCLUSIVE | GRANTED | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
| TABLE | merchant_onboarding_dev | #sql-63_57 | EXCLUSIVE | GRANTED | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
| TABLE | merchant_onboarding_dev | merchants | EXCLUSIVE | PENDING | 126 | 87 | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
+-------------+-------------------------+-----------------------------------+---------------------+-------------+-----------+----------------+-----------------------------------------------------+
7 rows in set (0.00 sec)
mysql> show full processlist;
+----+-----------------+-----------------+-------------------------+---------+--------+---------------------------------+-----------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+-----------------+-------------------------+---------+--------+---------------------------------+-----------------------------------------------------+
| 4 | event_scheduler | localhost | NULL | Daemon | 115824 | Waiting on empty queue | NULL |
| 62 | root | localhost:59116 | merchant_onboarding_dev | Sleep | 107 | | NULL |
| 63 | root | localhost:59117 | merchant_onboarding_dev | Sleep | 107 | | NULL |
| 65 | root | localhost:59119 | NULL | Sleep | 1 | | NULL |
| 79 | root | localhost | merchant_onboarding_dev | Query | 0 | starting | show full processlist |
| 81 | root | localhost | merchant_onboarding_dev | Sleep | 838 | | NULL |
| 83 | root | localhost | merchant_onboarding_dev | Sleep | 821 | | NULL |
| 87 | root | localhost | merchant_onboarding_dev | Query | 842 | Waiting for table metadata lock | ALTER TABLE merchants ADD COLUMN temp6 varchar(255) |
+----+-----------------+-----------------+-------------------------+---------+--------+---------------------------------+-----------------------------------------------------+
8 rows in set (0.00 sec)
mysql> SHOW ENGINE INNODB STATUS;
=====================================
2019-04-08 02:30:45 0x700006ef3000 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 20 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 242 srv_active, 0 srv_shutdown, 73418 srv_idle
srv_master_thread log flush and writes: 0
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 87
OS WAIT ARRAY INFO: signal count 516
RW-shared spins 722, rounds 723, OS waits 1
RW-excl spins 1110, rounds 3537, OS waits 28
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 1.00 RW-shared, 3.19 RW-excl, 0.00 RW-sx
------------
TRANSACTIONS
------------
Trx id counter 15753
Purge done for trx's n:o < 15749 undo n:o < 0 state: running but idle
History list length 11
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 281479647891952, not started
mysql tables in use 1, locked 1
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479647891040, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479647893776, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479647892864, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479647890128, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479647889216, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
950 OS file reads, 52924 OS file writes, 40911 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 3 buffer(s)
Hash table size 34679, node heap has 7 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 1 buffer(s)
Hash table size 34679, node heap has 2 buffer(s)
Hash table size 34679, node heap has 4 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 89894761
Log buffer assigned up to 89894761
Log buffer completed up to 89894761
Log written up to 89894761
Log flushed up to 89894761
Added dirty pages up to 89894761
Pages flushed up to 89894761
Last checkpoint at 89894761
42886 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 1037251
Buffer pool size 8191
Free buffers 3332
Database pages 4835
Old database pages 1764
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 634, not young 1
0.00 youngs/s, 0.00 non-youngs/s
Pages read 911, created 3927, written 8771
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 4835, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=99, Main thread ID=0x700006689000 , state=sleeping
Number of rows inserted 176434, updated 4249, deleted 10814, read 2383183
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
我无法理解的是为什么即使持有锁的事务提交,DDL 查询也会继续等待表元数据锁。
解决方案
在 MySQL 中,大多数 DDL 语句实际上会生成一个隐式提交。该ALTER TABLE
声明确实属于这一类。从文档中:
本节中列出的语句(以及它们的任何同义词)隐式结束当前会话中的任何活动事务,就好像您在执行语句之前已经完成了 COMMIT 一样。
大多数这些语句在执行后也会导致隐式提交。
这个内部文档更详细地介绍了......事情往往会变得混乱:
此外,一些 DDL 语句会发出临时事务提交:例如,
ALTER TABLE
在将数据从原始表复制到内部临时表后发出提交。
因此,当您执行时ALTER TABLE
,MySQL 会隐式尝试在语句中提交,但由于另一个事务正在进行,所以不能。该文档并未准确涵盖此用例,但我怀疑服务器没有正确处理这种情况,并产生您所看到的奇怪行为。文档本身指出这种类型的行为定义不正确。
底线:在运行ALTER TABLE
语句时,您不能依赖事务来处理并发。因此,您不想在ALTER
表忙于其他事务时访问表:这是不安全的。在运行这样的语句之前,你要确保表上没有设置锁。是的,在处理类似生产的数据库时,这肯定会带来痛苦的限制,但看起来这就是 MySQL 的做法......
推荐阅读
- reactjs - 使用反应在 django 上获取 http://localhost:8000/manifest.json 404(未找到)错误
- mysql - 将 100 万行快速插入 MySQL 服务器
- swift - 尝试插入非属性列表对象
- jenkins - 电子邮件分机:如果通过计时器开始构建,则有条件地向主题添加信息
- spring-boot - @ServerEndpoint 在 Spring Boot v2.3.7 上不起作用
- flutter - VScode 文档格式化程序不适用于 Dart/Flutter
- typescript - 如何根据nestJS或Typescript中的参数重置缓存值?
- javascript - 无法在我的侧边栏中展开菜单项以做出反应
- reinforcement-learning - 马尔可夫决策过程中的建模动作使用限制
- regex - 检查字符串如何以 XSLT 中的数字计数开头