回顾一次“神奇”的MySQL死锁问题

问题背景

大约在今年5月份左右,由于系统同时需要访问两个MySQL集群的数据,决定使用MyCat作为数据库中间件,因此在一个夜黑风高的晚上,屏蔽了用户访问,准备将系统切换成MyCat。整个切换过程其实很简单,配置好MyCat配置文件,修改应用的数据源访问连接即可大功告成。但是用过MyCat的人应该都清楚,MyCat有个很蛋疼的毛病,必须要求MySQL开启大小写忽略,否则启动后找不到表名大写的表。不巧的是公司之前DBA要求所有的表名、字段名都必须大写(OracleDBA,完全忽略了MySQL的感受)。没辙,只能修改表名了,将大写全部转换为小写。通过一个存储过程循环游标构建ALTER TABLE tableName RENAME newName语句来将表名改为大写。本以为执行应该会很快结束,但是整个命令持续了半个小时,卡在一张表上不动了,执行SHOW ENGINE INNODB STATUS,发现已经出现死锁。

疑问

这里先来介绍一下ALTER TABLE tableName RENAME newName命令,这一个名副其实的DDL操作,但是整个命令的执行仅仅是变更INFORMATION_SCHEMA库中相应字典表的数据(例如表名),且此时没有任何对表的操作,怎么会导致死锁问题呢?

毫无思路,但是仔细想想,这个操作应该会修改磁盘上.ibd、.frm文件的名称,否则仅仅是字典表变了也找不到对应的文件。如果说修改文件的话,就可能会出现死锁问题,若此时文件被其他进程读写占用呢,那就有可能会造成死锁。

究竟是不是这样呢,由于本人对MySQL底层了解的不深,只好求助论坛了。(以下内容摘自论坛)

探索

InnoDB buffer pool中的page管理牵涉到两个链表,一个是lru链表,一个是flush 脏块链表,由于数据库的特性:

1.脏块的刷新,是异步操作;

2.page存在两个版本,一个是ibd文件的持久化版本,和buffer pool内存中的当前版本。

所以在对table对象进行ddl变更的时候,要维护两个版本之间的一致性,有一些操作需要同步进行page缓存的管理。例如以下三种ddl操作:

1. flush table t for export

这是MySQL 5.6提供的InnoDB transportable tablespace功能,用于在不同实例之间进行表传输。由于需要透明的在物理层面迁移ibd文件,所以需要保证buffer pool中的page和ibd文件中的page的一致性。其操作步骤如下:

持有t表的MDL锁,保证在t表上没有活跃事务,即buffer pool中的脏page都是已提交事务;

扫描buffer pool中的flush list,同步刷下脏块;

记录数据字典信息到cfg文件,用于目标端的表结构匹配和验证,最后在目标端import的时候,变更page的space,max_lsn等。

2. drop table t

在对表进行删除的时候,需要清理掉buffer pool中的page,但如果表比较大,占用过多的buffer pool,清理的动作会影响到在线的业务,所以MySQL提供了lazy drop table的方式。

同步方式: 扫描lru链表,如果page属于t表,就从lru链表,hash表, flush list中删除,回收block到free list中。

lazy方式: 扫描lru链表,如果page属于t表,就给page设置一个space_was_being_deleted属性,等lru置换或者checkpoint flush dirty block的时候进行清理。

3. alter table t rename to t1

rename table name操作,虽然是DDL,但rename操作只是变更了数据字典中的table name和文件系统的ibd文件名称,所以,在rename的过程中,不存在对buffer pool中属于t表的page的同步操作,但由于要变更表名,即需要同步对文件的IO操作。

问题现象:

在MySQL 5.5版本上,error日志大量报出以下的错误信息:


查看操作日志,是一个普通的rename语句操作,但持续很久,因为rename只是数据字典的变更,除了MDL锁阻塞以外

不应该持续这么长时间,pstack查看线程栈信息:



这里我只列了有意义的三个线程:

用户线程Thread 5

用户线程确实在进行rename操作,但阻塞在fil_rename_tablespace函数中。

master线程Thread 120

InnoDB的master线程阻塞在fil_mutex_enter_and_prepare_for_io函数中。

IO线程Thread 100

InnoDB的IO线程一共有8个,4个读,4个写线程,发现都在os_event_wait_low中,也就是都空闲着等待condition中。

从上面的调用栈来看,线程之间长时间维持在这种状态下,明显发生了死锁,在我们解这个死锁之前,我们先来回顾一点背景知识,然后再说明死锁的真正原因。

InnoDB背景

checkpoint

由于对数据库的数据操作也遵循read-update-write的方式,所以数据的更新,会把buffer pool中的page变成脏块,由于write-ahead logs机制保证事务的完整性,脏块的write可以变成异步的,但又由于buffer pool的大小终究有限,而且对于recovery的时间的要求,又要求脏块的flush又要持续保证。

MySQL 5.5的版本由master thread来承担dirty flush的角色, dirty flush的过程就称为making checkpoint,lsn的推进保证了recovery的时间不被持续的变长。刷新的策略,受到当前IO pending的情况,double write-buffer是否打开,buffer pool中dirty page所占的比例,以及innodb_max_dirty_pages_pct参数的设置,进行灵活刷新,具体的代码细节,这里就不展开了。

异步IO

由于dirty flush是异步的,所以,master thread只负责提交IO请求,真正的IO操作是由IO helper thread来完成的。InnoDB使用的simulate AIO和native AIO会有一些差别,我们这里以simulate AIO为例进行说明。假设double write-buffer是打开的:

首先master thread搜集dirty pages,同步写入double write-buffer;

由于double write-buffer的方式是buffered write,所以等double write-buffer写满了之后;

同步把double write-buffer的page顺序写入到ibdata系统表空间中,如果完成之后系统crash,可以使用持久化的double write-buffer进行page恢复;

开始把 double write-buffer中的page,写入真正的ibd文件中。依次提交异步IO操作,提交IO操作的步骤分为:

持有fil_system mutex,判断当前tablespace是否可用,

判断当前fil_space的stop_io标示,如果设置就循环等待

如果stop_io没有标示,就打开fil_space对应的ibd文件句柄,然后递增 fil_space->n_pending

提交IO请求

等double write-buffer中的pages提交完所有的IO请求,使用os_aio_simulated_wake_handler_threads来唤醒IO helper thread来完成IO操作。

Rename 操作

接下来我们来看下rename操作的步骤:

首先在server层hold MDL锁;

进入InnoDB层,首先使用自治事务变更数据字典,包括SYS_TABLES,SYS_FOREIGN;

变更数据字典的内存对象,包括table, index, foreign list等;

变更fil_space对象以及对应的ibd数据文件名称,其中变更文件系统名称的时候:

设置当前的fil_space的stop_io,阻止再进行IO操作

判断当前是否有IO pending,如果有,就等IO pending结束

如果没有IO pending,就关闭opened的句柄,并rename文件名称

恢复stop_io标示

提交自治事务。

有了这些操作的具体步骤,我们就可以清晰的分析出死锁的原因。

死锁原因

两个线程,一个是master thread,需要提交flush dirty block的异步IO请求;一个是user thread,需要进行rename操作。

Rename操作,只变更数据字典和ibd文件名,并不需要同步buffer pool中的page,唯一需要同步的就是IO操作,通俗一点说,也就是在user thread进行rename table需要变更ibd文件名的时候,其它线程暂时不要对这个文件进行IO操作,等rename完成后,可以重新打开这个ibd文件,接着进行IO操作。

InnoDB使用两个标识来进行IO同步操作,即stop_io,n_pending。

stop_io:user thread要进行rename操作,提前设置这个标识,表示IO操作可以先hold暂停。

n_pending:master thread要进行flush操作,我已经提交了IO请求,user thread要进行rename可以先hold,等IO完成。

假设下面的时序:

master thread提交了1个IO请求,设置了n_pending;

rename操作设置stop_io,判断n_pending>0 就等待;

master thread需要提交剩下的几个IO,发现stop_io已设置,就等待;

由于master thread没有提交完这批IO,没有唤醒IO helper thread,导致第1个IO请求无法完成,n_pending一直等于1;

rename操作因为n_pending一直等于1,陷入了死等;

master thread发现stop_io等于true,陷入了死等。

具体的代码可以参考:



修复方法

修复的方法也比较简单,在fil_rename_tablespace的时候,如果发现node->n_pending > 0的时候,在sleep之前,发起一次唤醒动作,即os_aio_simulated_wake_handler_threads,IO helper thread去完成master thread已经提交的IO请求,这样n_pending就会降到0,死锁就解开了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容