mysql创建索引导致死锁,数据库崩溃
问题提出
一次生产上优化查询,需要给表添加索引,于是乎,造成了数据库的崩溃,迫不得已重启了数据库才解决问题。
为什么添加一个索引,会导致这么严重的问题呢?
平时添加一个索
文章目录 问题提出 一次生产上优化查询,需要给表添加索引,于是乎,造成了数据库的崩溃,迫不得已重启了数据库才解决问题。 为什么添加一个索引,会导致这么严重的问题呢? 平时添加一个索引也没见数据库崩溃啊? 难道是造成了死锁? 别急,接下来我们慢慢分析。 什么是元数据锁(meta data lock,MDL) 在MySQL5.5.3之前,有一个著名的bug#989(bug链接: MySQL Bugs: #989: If DROP TABLE while there’s an active transaction, wrong binlog order),大致如下:
很显然mysql执行时会先删除表t,然后执行insert 会报1032 error。 再举一个简单的例子,如果你在查询一个表的过程中,另外一个session对该表删除了一个列,那前面的查询到底该显示什么呢?如果在RR隔离级别下,事物中再次执行相同的语句还会和之前结果一致吗?为了防止这种情况,表查询开始MySQL会在表上加一个锁,来防止被别的session修改了表定义,这个锁就叫‘metadata lock’,简称MDL,翻译成中文也叫‘元数据锁’,它是一个表级锁。 什么情况下会添加元数据锁 首先给出答案:mysql所有的增删改查以及ddl都会加mdl锁。 什么是DDL(Data Definition Languages)语句:数据定义语言,这些语句定义了不同的数据段,数据库,表,列,索引等数据库对象。常用的语句关键字主要包括create,drop,alter等。 也就是说,增删改查、数据库的创建、销毁、索引的修改、字段的调整等等操作,都会加一个表级锁——元数据锁(meta data lock,MDL)。 这是不是听起来很恐怖的事情?所有的操作都会加一个表级锁。 但是!不要慌张,请继续往下看。 元数据锁工作原理 在 MySQL 5.5 版本中引入了 MDL(元数据锁),当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。 读写锁大家应该都很清楚吧。 MDL写锁只有在执行DDL语句的时候才会加,平时我们的增删改查只是加了MDL读锁,MDL读锁之间是可以共享的,并不会出现锁等待的情况。 创建索引导致数据库崩溃 线上某数据库意外发现缺少索引,并且该表的数据量很少,只有几万条记录而已,因此很随意地尝试给该表添加索引。原本预期该表的记录很少,添加索引的耗时应该很短,结果却直接导致该表被锁,所有该表的增删改查操作全部阻塞,继而影响到了线上业务。 发现锁表后,执行show processlist发现大量线程阻塞,状态显示Waiting for table metadata lock。通过命令终止了DDL线程,该表恢复正常。mysql从5.6版本起支持Online DDL,理论上执行DDL语句不会阻塞诸如INSERT、UPDATE、DELETE这类DML操作。 事后排查发现,该表有个持续了很久未提交的事务,正是该事务导致DDL语句执行时锁表。 复现 1、新建表
2、新建一个会话1,开启事务执行以下命令后不要提交事务
3、新建另一个会话2mysql表索引,执行DDL命令,发现DDL语句执行被阻塞
4、此时表被锁定,再新建一个会话3,执行查询语句发现该操作同样被阻塞住
5、我们使用SHOW PROCESSLIST; 命令,发现Waiting for table metadata lock,就是元数据锁。 6、我们使用以下语句,也会发现metadata_locks
metadata_locks是5.7中被引入,记录了metadata lock的相关信息,包括持有对象、类型、状态等信息。但5.7默认设置是关闭的(8.0默认打开),需要通过下面命令打开设置:
如果要永久生效,需要在配置文件中加入如下内容:
单纯查询这个表无法得出具体的阻塞关系,也无法得知什么语句造成的阻塞,这里要关联另外两个表performance_schema.thread和performance_schema.events_statements_history,thread表可以将线程id和show processlist中id关联,events_statements_history表可以得到事务的历史sql,关联得到一条监控元数据锁的sql语句。 关联后的完整sql如下:
根据显示结果,processlist_id为12的线程阻塞了13的线程,我们需要kill 12即可解锁。 实际上,MySQL也提供了一个类似的视图来解决metadata lock问题,视图名称为sys.schema_table_lock_waits,但此视图查询结果有bug,不是很准确,建议大家还是参考上面sql。 7、我们使用语句查看一下当前事务,发现会话1长事务一直没有关闭,导致了元数据锁,导致后续所有的增删改查请求等待,数据库崩溃。
DDL导致锁表的原因 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. A metadata lock on a table prevents changes to the table’s structure. This locking approach has the implication that a table that is being used by a transaction within one session cannot be used in DDL statements by other sessions until the transaction ends. mysql官方文档metadata-locking一节中指出,为了确保事务可序列化,mysql不允许一个会话对在另一会话中未完成的显式或隐式启动的事务中使用的表执行DDL语句。服务器通过获取事务中使用的表上的元数据锁并将这些锁的释放推迟到事务结束之前来实现。表上的元数据锁可防止更改表的结构。这种锁定方法的含义是,一个会话中事务正在使用的表在事务结束之前不能被其他会话在DDL语句中使用。 mysql对申请MDL锁的操作会形成一个队列,队列中写锁获取优先级高于读锁。一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作。由上可知当事务一旦申请到MDL锁后,直到事务执行完才会将锁释放,当长事物或未提交的事务未提交完成时,执行DDL语句会等待MDL排他锁而阻塞,继而阻塞该表的后续其他操作。 MySQL Online DDL的改进与应用 所以,DDL语句只有才开始和结束的时候,才会禁止读和写,在语句执行的时候是可以进行读的。 如何安全地给表添加字段、添加索引 1、生产环境的任何大表或频繁操作的小表,ddl都要非常慎重,最好在业务低峰期执行。 2、设计上要尽可能避免大事务,大事务不仅仅会带来各种锁问题,还会引起复制延迟/回滚空间爆满等各类问题。 3、设置参数 lock_wait_timeout 为较小值,使被阻塞端主动停止。 4、增强监控告警,及时发现 MDL 锁。 5、或许这样操作也是一种好办法:按新结构创建新表 -> 将旧表数据迁移至新表 -> 重命名两个表(三步都通过编写sql语句完成,比手动操作快,第二步的数据迁移操作视情况而定)。过程中最好在没人用的时候操作 6、操作ddl之前,先用以下语句查一下有没有长事务:
7、多副本(主从、集群)下可以做热更新。 8、MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n 这个语法:
参考资料 一步步搞懂MySQL元数据锁(MDL) 《MYSQL实战》 (编辑:威海站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |