1 背景 1html
1.1 MVCC:Snapshot Read vs Current Read 2mysql
1.2 Cluster Index:聚簇索引 3sql
1.3 2PL:Two-Phase Locking 3数据库
1.4 Isolation Level 4并发
MySQL/InnoDB的加锁分析,一直是一个比较困难的话题。我在工做过程当中,常常会有同事咨询这方面的问题。同时,微博上也常常会收到 MySQL锁相关的私信,让我帮助解决一些死锁的问题。本文,准备就MySQL/InnoDB的加锁问题,展开较为深刻的分析与讨论,主要是介绍一种思 路,运用此思路,拿到任何一条SQL语句,都能完整的分析出这条语句会加什么锁?会有什么样的使用风险?甚至是分析线上的一个死锁场景,了解死锁产生的原 因。性能
注:MySQL是一个支持插件式存储引擎的数据库系统。本文下面的全部介绍,都是基于InnoDB存储引擎,其余引擎的表现,会有较大的区别。spa
MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是很是重要的,极大的增长了系 统的并发性能,这也是为何现阶段,几乎全部的RDBMS,都支持了MVCC。.net
在MVCC并发控制中,读操做能够分红两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有多是历史版本),不用加锁。当前读,读取的是记录的最新版本,而且,当前读返回的记录,都会加上锁,保证其余事务不会再并发修改这条记录。插件
在一个支持MVCC并发控制的系统中,哪些读操做是快照读?哪些操做又是当前读呢?以MySQL InnoDB为例:htm
快照读:简单的select操做,属于快照读,不加锁。(固然,也有例外,下面会分析)
select * from table where ?;
当前读:特殊的读操做,插入/更新/删除操做,属于当前读,须要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
全部以上的语句,都属于当前读,读取记录的最新版本。而且,读取以后,还须要保证其余并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其余的操做,都加的是X锁 (排它锁)。
为何将 插入/更新/删除 操做,都归为当前读?能够看看下面这个 更新 操做,在数据库中的执行流程:
从图中,能够看到,一个Update操做的具体流程。当Update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条知足条件的记录,而后InnoDB引擎会将第一条记录返回,并加锁 (current read)。待MySQL Server收到这条加锁的记录以后,会再发起一个Update请求,更新这条记录。一条记录操做完成,再读取下一条记录,直至没有知足条件的记录为止。 所以,Update操做内部,就包含了一个当前读。同理,Delete操做也同样。Insert操做会稍微有些不一样,简单来讲,就是Insert操做可能 会触发Unique Key的冲突检查,也会进行一个当前读。
注:根据上图的交互,针对一条当前读的SQL语句,InnoDB与MySQL Server的交互,是一条一条进行的,所以,加锁也是一条一条进行的。先对一条知足条件的记录加锁,返回给MySQL Server,作一些DML操做;而后在读取下一条加锁,直至读取完毕。
InnoDB存储引擎的数据组织方式,是聚簇索引表:完整的记录,存储在主键索引中,经过主键索引,就能够获取记录全部的列。关于聚簇索引表的组织方式,能够参考MySQL的官方文档:Clustered and Secondary Indexes 。本文假设读者对这个,已经有了必定的认识,就再也不作具体的介绍。接下来的部分,主键索引/聚簇索引 两个名称,会有一些混用,望读者知晓。
传统RDBMS加锁的一个原则,就是2PL (二阶段锁):Two-Phase Locking。相对而言,2PL比较容易理解,说的是锁操做分为两个阶段:加锁阶段与解锁阶段,而且保证加锁阶段与解锁阶段不相交。下面,仍旧以MySQL为例,来简单看看2PL在MySQL中的实现。
从上图能够看出,2PL就是将加锁/解锁分为两个彻底不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁。
隔离级别:Isolation Level, 也是RDBMS的一个关键特性。相信对数据库有所了解的朋友,对于4种隔离级别:Read Uncommited,Read Committed,Repeatable Read,Serializable,都有了深刻的认识。本文不打算讨论数据库理论中,是如何定义这4种隔离级别的含义的,而是跟你们介绍一下 MySQL/InnoDB是如何定义这4种隔离级别的。
MySQL/InnoDB定义的4种隔离级别:
Read Uncommited
能够读取未提交记录。此隔离级别,不会使用,忽略。
Read Committed (RC)
快照读忽略,本文不考虑。
针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。
Repeatable Read (RR)
快照读忽略,本文不考虑。
针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的知足查询条件的记录不可以插入 (间隙锁),不存在幻读现象。
Serializable
从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,全部的读操做均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
Serializable隔离级别下,读写冲突,所以并发度急剧降低,在MySQL/InnoDB下不建议使用。
备注:下接 MySQL 加锁处理分析(二)