【MySQL】metadata lock问题
来源:SegmentFault
时间:2023-02-16 15:26:54 481浏览 收藏
大家好,今天本人给大家带来文章《【MySQL】metadata lock问题》,文中内容主要涉及到MySQL,如果你对数据库方面的知识点感兴趣,那就请各位朋友继续看下去吧~希望能真正帮到你们,谢谢!
一、Metadata lock
MySQL使用DML来管理对数据库对象的并发访问,并确保数据一致性。DML不仅适用于表,还适用于模式和存储程序(过程、函数、触发器和计划的事件)
1.1 MDL简述
为了在并发环境下维护表元数据的数据一致性,在表上有活动事务(显式或隐式)的时候,不可以对元数据进行写入操作。因此从MySQL5.5版本开始引入了MDL锁,来保护表的元数据信息,用于解决或者保证DDL操作与DML操作之间的一致性。
元数据锁的获取不依赖于使用的引擎,无论使用的是设置autocommit=0的MyISAM引擎还是用begin或start transaction语句显示声名的事务,连接都会获取元数据锁。一旦出现Metadata Lock Wait等待现象,后续所有对该表的访问都会阻塞在该等待上,导致连接堆积,业务受影响。
1.2 MDL解决的问题
Metadata lock 是MySQL在5.5.3版本后引入了,为的是防止5.5.3以前的一个bug的出现:
当一个会话在主库执行DML操作还没提交时,另一个会话对同一个对象执行了DDL操作如drop table,而由于MySQL的binlog是基于事务提交的先后顺序进行记录的,因此在slave上应用时,就出现了先drop table,然后再向table中insert的情况,导致从库应用出错。
对于引入MDL,其主要解决了2个问题:
一个是事务隔离问题,比如在可重复隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求;
另外一个是数据复制的问题,比如会话A执行了多条更新语句期间,另外一个会话B做了表结构变更并且先提交,就会导致slave在重做时,先重做alter,再重做update时就会出现复制错误的现象。所以在对表进行上述操作时,如果表上有活动事务(未提交或回滚),请求写入的会话会等待在Metadata lock wait 。
二、常见MDL锁场景:
①当前有执行DML操作(DML未执行完成)时,执行DDL操作
② 当前有对表的长时间查询或使用mysqldump/mysqlpump时,执行DDL会被堵住
③ 显示或者隐式开启事务后未提交或回滚,比如查询完成后未提交或者回滚,DDL会被堵住
④ 表上有失败的查询事务,比如查询不存在的列,语句失败返回,但是事务没有提交,此时DDL仍然会被堵住
三、例子
mysql版本:5.6.29
隔离级别:READ COMMITTED
3.1 场景1
(1) 现象模拟
事务1 | 事务2 | 事务3 |
---|---|---|
begin; | ||
select * from base_code; | ||
- | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码';——执行被阻塞 | |
- | - | select * from base_code;——执行被阻塞 |
(2) show processlist查看结果如下:
mysql> show processlist; +-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+ | 1 | event_scheduler | localhost | NULL | Daemon | 20309270 | Waiting on empty queue | NULL | | 98456 | root | localhost | lcl_abc | Sleep | 85 | | NULL | | 98459 | root | localhost | lcl_abc | Query | 79 | Waiting for table metadata lock | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码' | | 98461 | root | localhost | lcl_abc | Query | 51 | Waiting for table metadata lock | select * from base_code | | 98462 | root | localhost | NULL | Query | 0 | init | show processlist | +-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+ 5 rows in set (0.00 sec)
select * from base_code; 再次查询base_code表也是 Waiting for table metadata lock状态,说明由于 metadata lock的存在,会导致后面正常的查询都会因为等待锁而阻塞。
如果先执行事务3,是可以查询的。执行完事务2,造成阻塞后,才会阻塞后续所有的操作。
(3) 查看当前事务运行状态:
mysql> select * from information_schema.innodb_trx\G *************************** 1. row *************************** trx_id: 339395 trx_state: RUNNING trx_started: 2020-04-14 16:51:43 trx_requested_lock_id: NULL trx_wait_started: NULL trx_weight: 0 trx_mysql_thread_id: 98456 trx_query: NULL trx_operation_state: NULL trx_tables_in_use: 0 trx_tables_locked: 0 trx_lock_structs: 0 trx_lock_memory_bytes: 488 trx_rows_locked: 0 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: READ COMMITTED trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 10000 trx_is_read_only: 0 trx_autocommit_non_locking: 0 1 row in set (0.17 sec)
可以看到,事务1由于还没提交,所以这里能看到它的状态还是running.
但是这里我们看不到正在执行的语句,不知道到底是什么语句导致的。
(4) 查看该事务对应的进程
mysql> select * from information_schema.processlist where id=98456; +-------+------+-----------+---------+---------+------+-------+------+ | ID | USER | HOST | DB | COMMAND | TIME | STATE | INFO | +-------+------+-----------+---------+---------+------+-------+------+ | 98456 | root | localhost | lcl_abc | Sleep | 1107 | | NULL | +-------+------+-----------+---------+---------+------+-------+------+ 1 row in set (0.00 sec)
只能根据trx_mysql_thread_id看到未提交的事务的process id,看一下processlist,INFO内也没有具体内容。
此时可以通过performance_schema.events_statements_current来查看到对应的sql,包括已经执行完,但没有提交的。
mysql> SELECT b.processlist_id, c.db, a.sql_text, c.command, c.time, c.state FROM performance_schema.events_statements_current a JOIN performance_schema.threads b USING(thread_id) JOIN information_schema.processlist c ON b.processlist_id = c.id WHERE a.sql_text NOT LIKE '%performance%'; +----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+ | processlist_id | db | sql_text | command | time | state | +----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+ | 98459 | lcl_abc | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码' | Query | 636 | Waiting for table metadata lock | | 98461 | lcl_abc | select * from base_code | Query | 632 | Waiting for table metadata lock | | 98456 | lcl_abc | select * from base_code | Sleep | 639 | | +----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+ 3 rows in set (0.05 sec)
(5) 提交或关闭造成DML锁的进程
提交或者kill 98456后,可以看到事务2、事务3立马执行完了
mysql> alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码'; Query OK, 7 rows affected (21 min 58.00 sec) Records: 7 Duplicates: 0 Warnings: 0
3.2 场景2:
事务1,开启事务,执行语句报错,其他语句获取到的锁在这个事务提交或回滚之前,仍然不会释放掉
(1) 现象模拟
事务1 | 事务2 | 事务3 |
---|---|---|
begin; | ||
update base_code set num2=1 where id=1;——ERROR 1054 (42S22): Unknown column 'num2' in 'field list' | ||
- | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码';——执行被阻塞 | |
- | - | select * from base_code;——执行被阻塞 |
由于num2字段不存在,事务1执行报错,导致update执行失败,但是没有提交该事务,此时依然会造成alter语句阻塞,以后后续的select阻塞。
(2)show processlist查看结果如下:
mysql> show processlist; +-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+ | 1 | event_scheduler | localhost | NULL | Daemon | 20314642 | Waiting on empty queue | NULL | | 98456 | root | localhost | lcl_abc | Sleep | 1030 | | NULL | | 98459 | root | localhost | lcl_abc | Query | 983 | Waiting for table metadata lock | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码' | | 98461 | root | localhost | lcl_abc | Query | 3 | Waiting for table metadata lock | select * from base_code | | 98462 | root | localhost | NULL | Query | 0 | init | show processlist | +-------+-----------------+-----------+---------+---------+----------+---------------------------------+------------------------------------------------------------------------------------+ 5 rows in set (0.00 sec)
(3) 查看当前事务运行状态:
在information_schema.innodb_trx中也没有任何进行中的事务.需查询表performance_schema.events_statements_current,该表可以看到对应的sql,包括已经执行完,但没有提交的
缺陷:一个事务可能有一组sql组成,这个方法只能看到这个事务最后执行的是什么SQL,无法看到全部。(假如事务1,执行完update后又执行了一个select,则events_statements_current表中只能看到最后执行的select语句)
mysql> SELECT b.processlist_id, c.db, a.sql_text, c.command, c.time, c.state FROM performance_schema.events_statements_current a JOIN performance_schema.threads b USING(thread_id) JOIN information_schema.processlist c ON b.processlist_id = c.id WHERE a.sql_text NOT LIKE '%performance%'; +----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+ | processlist_id | db | sql_text | command | time | state | +----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+ | 98459 | lcl_abc | alter table base_code modify column code varchar(64) DEFAULT NULL COMMENT '编码' | Query | 636 | Waiting for table metadata lock | | 98461 | lcl_abc | select * from base_code | Query | 632 | Waiting for table metadata lock | | 98463 | lcl_abc | update base_code set num2=1 where id=1 | Sleep | 639 | | +----------------+---------+------------------------------------------------------------------------------------+---------+------+---------------------------------+ 3 rows in set (0.05 sec)
四、参数
可以通过 lock_wait_timeout 变量来指定超时时间,默认是31536000秒(一年),所以锁住的查询永远不会终止。
五、总结
- 为了事务的串行话,和数据一致性, Mysql会对打开事务进行DML的表加上table metadata lock,在事务提交前,其他的DDL操作会阻塞
- 对于主要是查询数据的项目来说,默认不开启事务即可,如果确实需要,程序上手动开启事务
- 需要使用到事务时,也要尽量缩小事务的运行时间,一个事务中不要包含太多的语句
- 程序上对任何错误异常状况一定要捕捉后,回滚事务,否则事务脱离程序,只能等事务自己超时,手动关闭事务或者重启服务释放锁了
六、查找未提交事务的sql的方法
(1) 表performance_schema.events_statements_current
缺陷:一个事务可能有一组sql组成,这个方法只能看到这个事务最后执行的是什么SQL,无法看到全部。(假如事务1,执行完update后又执行了一个select,则events_statements_current表中只能看到最后执行的select语句)
(2) general_log
即使事务没有提交,一样会写到general_log.
缺陷:一般情况下general_log不大可能打开.
(3) commit后,查看binlog
假如后面应用层最终commit了,那么会在binlog里记录,可以根据当时的tread_id去binlog查看
缺陷:不会记录select、执行失败的语句。
好了,本文到此结束,带大家了解了《【MySQL】metadata lock问题》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多数据库知识!
-
499 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
101 收藏
-
236 收藏
-
165 收藏
-
188 收藏
-
207 收藏
-
189 收藏
-
366 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 典雅的电话
- 这篇技术文章真是及时雨啊,好细啊,写的不错,已加入收藏夹了,关注作者大大了!希望作者大大能多写数据库相关的文章。
- 2023-05-13 06:31:59
-
- 缓慢的大山
- 这篇文章内容真是及时雨啊,太详细了,很有用,收藏了,关注up主了!希望up主能多写数据库相关的文章。
- 2023-03-10 07:28:50
-
- 拉长的鲜花
- 太详细了,收藏了,感谢师傅的这篇技术贴,我会继续支持!
- 2023-03-04 18:34:42
-
- 眼睛大的镜子
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢作者大大分享技术文章!
- 2023-02-27 19:59:32