(mysql)table metadata lock原因及解决

背景

在项目的一次需求中,需要对一个表增加字段,然而在执行增加字段的sql语句时,卡住了很久都没提交到Mysql完成,而此时对外接口服务请求也卡住了,这时中断卡住的alter table 语句,服务慢慢恢复正常,如果不搞清楚这个问题的根源,不敢增加字段,因为会直接影响到服务

排查

通过show processlist 查看到在alter table语句执行卡住过程中,累计了大量状态为 Waiting for table metadata lock 的记录

然后查看当前的事务状态 执行 select * from information_schema.innodb_trx\G

mysql> select * from information_schema.innodb_trx\G
*************************** 1. row ***************************
                    trx_id: 421408771164000
                 trx_state: RUNNING
               trx_started: 2019-07-02 14:27:09
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 11688
    ....

发现了其中一条已经运行了很久的事务,我怀疑跟这个运行很久的而且没有提交的事务有关。

测试还原

在本地mysql开多个终端测试

session 1: 开启事务,执行select 语句,但不提交事务

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

mysql>select*from t1;+------+| c1   |+------+|1|+------+1 row inset(0.00 sec)

session 2:执行增加字段sql

mysql> alter table t1 add c2 int;

执行被阻塞了

mysql> show processlist;+----+------+-----------+------+---------+------+---------------------------------+---------------------------+|Id|User|Host| db   |Command|Time|State|Info|+----+------+-----------+------+---------+------+---------------------------------+---------------------------+|27| root | localhost | test |Query|141|Waitingfor table metadata lock | alter table t1 add c2 int ||29| root | localhost | test |Query|0| starting                        | show processlist          ||30| root | localhost | test |Sleep|210||NULL|+----+------+-----------+------+---------+------+---------------------------------+---------------------------+

可以看到alter table语句的状态为Waiting for table metadata lock

session 3 : 再次查询t1表

mysql>select*from t1;

也被阻塞了

mysql> show processlist;+----+------+-----------+------+---------+------+---------------------------------+---------------------------+|Id|User|Host| db   |Command|Time|State|Info|+----+------+-----------+------+---------+------+---------------------------------+---------------------------+|27| root | localhost | test |Query|141|Waitingfor table metadata lock | alter table t1 add c2 int ||28| root | localhost | test |Query|8|Waitingfor table metadata lock | select * from t1          ||29| root | localhost | test |Query|0| starting                        | show processlist          ||30| root | localhost | test |Sleep|210||NULL|+----+------+-----------+------+---------+------+---------------------------------+-------------------

select * from t1 再次查询t1表也是 Waiting for table metadata lock状态,说明由于 metadata lock的存在,会导致后面正常的查询都会因为等待锁而阻塞

再查看当前事务运行状态:

mysql> select * from information_schema.innodb_trx\G
*************************** 1. row ***************************
               trx_id: 421408771166760
               trx_state: RUNNING
               trx_started: 2019-08-02 15:34:41
               trx_mysql_thread_id: 30

可以看到,session1的事务由于还没提交,所以这里能看到它的状态还是running

这时我们commit session1的事务,看看效果

session 1:

mysql>select*from t1;+------+| c1   |+------+|1|+------+1 row inset(0.00 sec)

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

session 2:

mysql> alter table t1 add c2 int;Query OK,0 rows affected (30.51 sec)
Records:0  Duplicates:0  Warnings:0

session 3:

mysql>select*from t1;+------+| c1   |+------+|1|+------+1 row inset(7.56 sec)

可以看到session1的事务提交后,session2 和session3 都正常执行了, 他们完成的时间分别是30秒和7秒

项目 autocommit 的设置

通过上面的还原测试,可以知道是由于事务没有提交而给表加了锁,导致后面alter语句因为等待锁而阻塞,从而影响后面的正常请求。

那说明我们的项目是默认开启了事务吗?

继续排查,项目是使用flask-sqlchemy的插件来管理mysql接入,然后查了下文档

在实例化sqlchemy的时候,会创建一个用于跟Mysql交互的session对象,看看源码

# db是这样使用的
db =SQLAlchemy()
db.__init__(app)....# 看看SQLAlchemy里面的session是怎么创建的classSQLAlchemy(object):def__init__(self, app=None, use_native_unicode=True, session_options=None,
                 metadata=None, query_class=BaseQuery, model_class=Model,
                 engine_options=None):...self.session =self.create_session(session_options)...defcreate_session(self, options):...return orm.sessionmaker(class_=SignallingSession, db=self,**options)# session 使用到是SignallingSession 这个类 classSignallingSession(SessionBase):...def__init__(self, db, autocommit=False, autoflush=True,**options):...

从 SignallingSession类的定义看来,autocommit=False,说明默认都给所有的sql执行开启事务,也就是说,哪怕是纯select语句,不需要加锁的select,我们的项目默认也需要开启事务,这对于Mysql MVCC的版本控制来说,是没必要的。

解决办法:就是在实例化SQLAlchemy的时候,给一个参数,修改的session的autocommit=True:

db =SQLAlchemy(session_options={"autocommit":True})
db.__init__(app)

关于 table metadata lock

来自官网的介绍:

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.

意思就是为了保证事务的串行执行,而启用的一个锁,这个锁只会在事务结束的时候释放,因此在事务提交或回滚钱,任何对这个表做的DDL操作,都是会阻塞的

If the server acquires metadata locks for a statement that is syntactically valid but fails during execution, it does not release the locks early. Lock release is still deferred to the end of the transaction because the failed statement is written to the binary log and the locks protect log consistency.

这个 Metadata lock 是MySQL在5.5.3版本后引入了,为的是防止5.5.3以前的一个bug的出现:

当一个会话在主库执行DML操作还没提交时,另一个会话对同一个对象执行了DDL操作如drop table,而由于MySQL的binlog是基于事务提交的先后顺序进行记录的,因此在从库上应用时,就出现Q了先drop table,然后再向table中insert的情况,导致从库应用出错。

总结

  • 为了事务的串行话,和数据一致性, Mysql会对打开事务进行DML的表加上table metadata lock, 在事务提交前,其他的DDL操作会阻塞

  • 对于主要是查询数据的项目来说,默认不开启事务即可,如果确实需要,程序上手动开启事务

  • 需要使用到事务时,也要尽量缩小事务的运行时间,一个事务中不要包含太多的语句

  • 程序上对任何错误异常状况一定要捕捉后,回滚事务,否则事务脱离程序,只能等事务自己超时,手动关闭事务或者重启服务释放锁了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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、付费专栏及课程。

余额充值