Mysql Waiting for table metadata lock问题处理

Waiting for table metadata lock问题处理

在使用mysql的时候,我们有时会碰到Waiting for table metadata lock的锁等待。但是这个锁等待比较特殊,在innodb_lock_waitshow engine innodb status表里面都查不到。

该锁的影响

+-------+-----------------+-----------+------+---------+--------+---------------------------------+-----------------------------------------------------------------------+
| ID    | USER            | HOST      | DB   | COMMAND | TIME   | STATE                           | INFO                                                                  |
+-------+-----------------+-----------+------+---------+--------+---------------------------------+-----------------------------------------------------------------------+
|     1 | event_scheduler | localhost | NULL | Daemon  | 365869 | Waiting on empty queue          | NULL                                                                  |
| 13006 | root            | localhost | NULL | Query   |     35 | Waiting for table metadata lock | alter table test.t add column s int                                   |
| 13040 | root            | localhost | NULL | Query   |      0 | executing                       | select * from information_schema.processlist where command != 'sleep' |
| 13044 | root            | localhost | NULL | Query   |      4 | Waiting for table metadata lock | update test.t set b = 10 where a = 3                                  |
| 13043 | root            | localhost | NULL | Query   |     21 | Waiting for table metadata lock | select * from test.t                                                  |
+-------+-----------------+-----------+------+---------+--------+---------------------------------+-----------------------------------------------------------------------+

可以看到,如果一个表产生了Waiting for table metadata lock,那么针对该表的任何操作都会被锁住,包括select,这对生产会产生巨大的影响。我们可以通过配置参数lock_wait_timeout来减少这种锁可以等待的时长。不过最重要的还是减少这种锁等待的产生。

lock_wait_timeout

This variable specifies the timeout in seconds for attempts to acquire metadata locks. The permissible values range from 1 to 31536000 (1 year). The default is 31536000.

这个参数控制的是这个metadata锁等待可以等待的最长时间,如果超过就会报等待超时,默认是31536000秒,也就是一年,可以动态修改。

处理方法

方法一:INNODB_TRX

SELECT
    t.PROCESSLIST_ID,
    t.PROCESSLIST_USER,
    t.PROCESSLIST_HOST,
    t.PROCESSLIST_DB,
    t.PROCESSLIST_STATE,
    t.PROCESSLIST_COMMAND,
    t.PROCESSLIST_TIME,
    t.PROCESSLIST_INFO,
    e.CURRENT_SCHEMA,
    group_concat(e.SQL_TEXT separator '\n') as sql_text
FROM
    `performance_schema`.threads t ,
    `information_schema`.INNODB_TRX trx ,
    `performance_schema`.events_statements_history e 
WHERE
 t.thread_id = e.thread_id and 
 t.PROCESSLIST_ID = trx.trx_mysql_thread_id
group by t.THREAD_ID desc

如果从INNODB_TRX只能查到一个会话,那就算运气很好了,肯定是这个造成的。

那么如果碰到多于一个的情况呢,可以通过PROCESSLIST_INFO字段里面的sql来判断出来是哪一个。但是有些情况下,持有metadata锁的会话是在sleep状态下的。也就是说造成持有这个锁的语句已经执行过了,但是由于没有提交或者回滚,导致会话还是持有着这个锁。如果碰到这种情况,PROCESSLIST_INFO字段就可能是空的了,那就只能通过判断会话已经执行过的语句来猜了。

当然猜也不是瞎猜,是有根据的猜。mysql有一个events_statements_history表,可以通过连接这个表来查看会话执行过什么语句。如果有涉及到等待锁的表的语句就能大概猜出来是哪一个了。比如下面这个结果

+----------------+------------------+------------------+----------------+-------------------+---------------------+------------------+------------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+
| PROCESSLIST_ID | PROCESSLIST_USER | PROCESSLIST_HOST | PROCESSLIST_DB | PROCESSLIST_STATE | PROCESSLIST_COMMAND | PROCESSLIST_TIME | PROCESSLIST_INFO | CURRENT_SCHEMA | sql_text                                                                                                                                                     |
+----------------+------------------+------------------+----------------+-------------------+---------------------+------------------+------------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+
|          12268 | root             | localhost        | NULL           | NULL              | Sleep               |              435 | NULL             | NULL           | select @@version_comment limit 1;select USER();begin;select * from t;select * from test.t;update t set b = 4 where a = 3;update test.t set b = 4 where a = 3 |
+----------------+------------------+------------------+----------------+-------------------+---------------------+------------------+------------------+----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+

最后的字段显示了这个会话执行过了什么语句。12268会话就执行过一个select,两个update等等语句。这很可能说明12268会话持有t表的metadata锁。一般来说杀了这个会话就可以解决问题了。

但是这个events_statements_history有一个限制,他不会存储所有执行过的语句,而是存储最新执行过的N个语句。这个N是由performance_schema_events_statements_history_size控制的,该参数是只读的,只能重启mysql生效,默认是10。如果执行过的语句超过了这个限制,那么很有可能是看不到对这个表进行修改的语句的,这个时候就需要猜了。

方法二:metadata_locks表

从mysql5.7开始,有了performance_schema.metadata_locks表,用于显示等待和持有metadata锁的会话信息。有效的简化了处理metadata锁等待的方法。

5.7版本该特性不是默认开启的,需要手动启动。8.0开始是默认开启的不需要配置。

在线开启方法
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME = 'wait/lock/metadata/sql/mdl';
在线关闭方法
UPDATE performance_schema.setup_instruments
SET ENABLED = 'NO', TIMED = 'NO'
WHERE NAME = 'wait/lock/metadata/sql/mdl';
永久开启方法

在配置文件的mysqld区块里面添加

performance-schema-instrument='wait/lock/metadata/sql/mdl=ON'

开启了后,可通过执行sql

SELECT
    t1.OBJECT_SCHEMA,
     t1.OBJECT_NAME,
    t1.LOCK_TYPE,
    t1.LOCK_STATUS,
    t2.PROCESSLIST_ID,
    t2.PROCESSLIST_USER,
    t2.PROCESSLIST_HOST,
    t2.PROCESSLIST_DB,
    t2.PROCESSLIST_COMMAND,
    t2.PROCESSLIST_STATE,
    t2.PROCESSLIST_INFO
FROM
    `performance_schema`.metadata_locks t1,
    `performance_schema`.threads t2
WHERE
    t1.owner_thread_id = t2.thread_id
AND t1.OBJECT_SCHEMA = 'test'
AND t1.OBJECT_NAME = 't'

只有开启特性以后出现的持有或者等待metadata锁的会话才会被记录。

+---------------+-------------+-------------------+-------------+----------------+------------------+------------------+----------------+---------------------+---------------------------------+--------------------------------+
| OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE         | LOCK_STATUS | PROCESSLIST_ID | PROCESSLIST_USER | PROCESSLIST_HOST | PROCESSLIST_DB | PROCESSLIST_COMMAND | PROCESSLIST_STATE               | PROCESSLIST_INFO               |
+---------------+-------------+-------------------+-------------+----------------+------------------+------------------+----------------+---------------------+---------------------------------+--------------------------------+
| test          | t           | SHARED_WRITE      | GRANTED     |          12268 | root             | localhost        | test           | Sleep               | NULL                            | NULL                           |
| test          | t           | SHARED_READ       | GRANTED     |          12268 | root             | localhost        | test           | Sleep               | NULL                            | NULL                           |
| test          | t           | SHARED_UPGRADABLE | GRANTED     |          12378 | root             | localhost        | test           | Query               | Waiting for table metadata lock | alter table t add column f int |
| test          | t           | EXCLUSIVE         | PENDING     |          12378 | root             | localhost        | test           | Query               | Waiting for table metadata lock | alter table t add column f int |
+---------------+-------------+-------------------+-------------+----------------+------------------+------------------+----------------+---------------------+---------------------------------+--------------------------------+

上面的结果中LOCK_STATUS字段表示连接对于matadata锁的持有状态,GRANTED表示持有,PENDING表示等待。很容易可以看出来12268会话持有了锁,而12378会话正在等待这个锁。通过kill 12268语句,或者让12268提交回滚都能解决这个问题。

总结

如果你的mysql还是5.6,那么只能按照先方法一来处理。如果你的mysql是5.7版本,可以先执行select * from performance_schema.setup_instruments WHERE NAME = ‘wait/lock/metadata/sql/mdl’;来查看是不是开启了metadata_locks表的特性,如果开启了可以直接使用方法二来处理。那如果是mysql8了,那么恭喜你,可以直接使用方法二来处理,简单快捷。一般及时提交或者尽量优化sql,缩短sql的执行时间,就可以减少Waiting for table metadata lock的出现次数了。

版权声明:本文为CSDN博主「jaysea123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/jaysea123/article/details/108073854

MySQL 8中的“waiting for table metadata lock”错误通常表示在运行某个查询或DML语句时,另一个进程已经占据了需要访问的数据表或索引,导致当前进程无法获取到对应的元数据锁,从而无法继续执行。 元数据锁是MySQL中的一种排他锁,用于保护数据表的元数据信息(例如表结构、索引信息等)。当一个进程需要查询或修改数据表的元数据时,它会请求获得对应的元数据锁,以保证自己能够独占这些元数据信息的访问。但是,如果另一个进程已经占据了相应的锁,当前进程就需要等待锁的释放才能继续执行,这就是“waiting for table metadata lock”的错误原因。 为了避免这个问题,我们可以尝试采取以下措施: 1. 使用更高的隔离级别(如repeatable read或serializable)来保证访问数据表时的可重复读性,从而减少对元数据的频繁访问和锁冲突的机会。 2. 减少对数据表结构的频繁修改操作,避免在高并发环境中进行DDL语句(如ALTER TABLE)的执行。 3. 尽量避免将多个操作合并成一条长SQL语句,尽可能将多个小操作拆分成多条短SQL语句,从而减少对数据表的锁定时间。 4. 使用更高效的数据库架构设计(如分库分表、索引优化等),将数据的读写操作分配到更多的物理资源中,从而减少对单个数据表的锁定时间。 在实际进行MySQL数据库开发或运维时,我们需要注意以上几点,以避免“waiting for table metadata lock”错误的发生。如果不幸遇到此类问题,我们需要通过查看进程列表、慢查询日志、错误日志或排查sql执行计划等方式,以找出造成锁冲突的根本原因。针对不同的错误原因,我们可以采取不同的解决方法,从而有效避免MySQL 8中的元数据锁问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值