当前位置: 首页 > 后端技术 > Python

记得一个Mysql上线事故的元数据锁的详细解释

时间:2023-03-26 11:45:32 Python

背景在一个项目需求中,需要在一个表中增加一个字段。但是在执行添加字段的sql语句时,卡了很久,没有提交给Mysql完成。这时候对外接口服务请求也卡住了。这时候卡住的altertable语句中断,服务慢慢恢复正常。如果问题根源不明确,就不敢加字段,因为会直接影响服务。通过showprocesslist查看,发现在执行altertable语句的过程中,积累了大量状态为Waitingfortablemetadatalock的记录,然后查看当前事务状态,执行select*frominformation_schema.innodb_trxGmysql>select*来自information_schema.innodb_trx\G*******************************1.行*****************************trx_id:421408771164000trx_state:正在运行trx_started:2019-07-0214:27:09trx_requested_lock_id:NULLtrx_wait_started:NULLtrx_weight:0trx_mysql_thread_id:11688....我发现其中一个长期运行的事务怀疑与这个长期运行且未提交的事务有关。测试恢复在本地mysql中打开多个终端测试会话1:启动事务,执行select语句,但不提交事务mysql>begin;QueryOK,0rowsaffected(0.00sec)mysql>select*fromt1;+------+|c1|+------+|1|+------+1rowinset(0.00sec)session2:Executetoaddfieldsqlmysql>altertablet1addc2int;通过阻塞执行mysql>showprocesslist;+----+-----+---------+-----+---------+-----+--------------------------------+-------------------------+|编号|用户|主持人|分贝|命令|时间|状态|信息|+----+-----+----------+-----+--------+-----+--------------------------------+------------------------+|27|根|本地主机|测试|查询|141|等待表元数据锁|改变表t1添加c2int||29|根|本地主机|测试|查询|0|开始|显示进程列表||30|根|本地主机|测试|睡觉|210||空|+----+------+----------+--------+--------+------+------------------------------+------------------------+可以看到altertablestatement状态为Waitingfortablemetadatalocksession3:Queryt1tablemysql>select*fromt1again;也阻止了mysql>showprocesslist;+----+------+------------+------+--------+------+--------------------------------+------------------------+|Id|用户|主机|db|命令|时间|状态|信息|+----+-----+------------+-----+--------+------+--------------------------------+---------------------------+|27|根|本地主机|测试|查询|141|等待表元数据锁|改变表t1添加c2int||28|根|本地主机|测试|查询|8|等待表元数据锁|从t1中选择*||29|根|本地主机|测试|查询|0|开始|显示进程列表|||空|+----+-----+------------+-----+--------+-------+--------------------------------+-----------------select*fromt1再次查询t1表,同样处于Waitingfortablemetadatalock状态,说明由于元数据锁的存在,后续的正??常查询会因为等待锁而阻塞,然后查看当前事务状态:mysql>select*frominformation_schema.innodb_trx\G******************************1.行******************************trx_id:421408771166760trx_state:正在运行trx_started:2019-08-0215:34:41trx_mysql_thread_id:30可以看到session1的事务还没有提交,所以这里可以看到它的状态还在运行,我们提交session1的事务看效果session1:mysql>select*fromt1;+------+|c1|+------+|1|+------+1rowinset(0.00sec)mysql>commit;QueryOK,0rowsaffected(0.00sec)session2:mysql>altertablet1addc2int;QueryOK,0rowsaffected(30.51sec)Records:0Duplicates:0Warnings:0session3:mysql>select*fromt1;+------+|c1|+------+|1|+------+1行set(7.56sec),可以看到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):...returnorm.sessionmaker(class_=SignallingSession,db=self,**options)#session使用SignallingSessionclassSignallingSession(SessionBase):...def__init__(self,db,autocommit=False,autoflush=True,**options):...从SignallingSession类的定义来看,autocommit=False意味着默认情况下,为所有SQL执行启用事务。也就是说,即使是不需要加锁select的纯select语句,我们的项目也需要默认开启事务。对于MysqlMVCC的版本控制来说,没有必要。解决方法:在实例化SQLAlchemy时,给一个参数修改session的autocommit=True:db=SQLAlchemy(session_options={"autocommit":True})db.__init__(app)关于表元数据锁来自官网介绍:为了确保事务的可序列化性,服务器不得允许一个会话在另一个会话中未完成的显式或隐式启动的事务中使用的表上执行数据定义语言(DDL)语句。服务器通过获取事务中使用的表的元数据锁并推迟释放这些锁直到事务结束来实现这一点。这意味着启用一个锁以确保事务的串行执行。这个锁只有在事务结束时才会被释放。因此,当事务被提交或返回Rollingmoney时,任何对该表的DDL操作都会被阻塞。如果服务器为语法上有效但在执行过程中失败的语句获取元数据锁,则它不会提前释放锁。锁释放仍然延迟到事务结束,因为失败的语句被写入二进制日志并且锁保护日志一致性。这个Metadatalock是MySQL在5.5.3版本之后引入的,为了防止5.5.3之前的一个bug:当一个session对主库执行DML操作还没有提交时,另一个session对同一个对象如droptable进行了DDL操作,由于MySQL的binlog是它是按照事务提交的顺序记录的,所以从库申请的时候,会出现Q先丢表再插入表的情况,导致从库申请出错。一致性,Mysql会对开启事务的DML表加一个表元数据锁。在事务提交之前,其他DDL操作将被阻塞。对于以查询数据为主的项目,默认是不开启事务的。如果确实有必要,需要在程序中手动开启事务时,应尽量减少事务的运行时间。不要在事务中包含太多语句。程序必须捕获任何错误异常并回滚事务,否则事务将离开程序。只能等事务自己超时,手动关闭事务或者重启服务来释放锁。关于我如果文章对您有所帮助,您可以收藏转发,这将是对我莫大的鼓励!另外可以关注我公众号【码农富哥】(coder2025),我会持续输出原创算法和计算机基础文章!