mysql锁总结
脏读,幻读,重复读与隔离级别
脏读,幻读,重复读都是两次读取的数据不一致,概念相似但又不同,这里描述一下他们与隔离级别间的关系
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。
Read uncommitted 读未提交
描述为事务进行过程中可以读取其他事务未提交的数据,即事务A进行过程中修改了数据但未提交,事务B执行过程中也需要修改同一数据,这时事务B读取了事务A未提交的数据并修改。若事务A最终回滚,则事务B出现了脏读。
Read committed 读已提交
读提交即是允许事务读取已提交的数据,设想一个场景:事务A读取数据判断这条记录满足接下来的修改条件,此时事务B修改该记录并提交,事务A在接下来的修改过程中再读取该记录时已经和之前读取到的不同,就产生了不可重复读
Repeatable read 可重复读(这是MySQL的默认事务隔离级别)
重复读即是在在上面场景中的事物A开始时就给改记录加锁,在释放锁之前事务不可以对其操作。这样事务A执行过程中前后读取到的数据就一致了,实现了可重复读。但是当事务A锁住了满足某条件的10条记录,例如将年龄大于20的记录锁住,再更新或删除这些记录。但此时事务B虽不能操作这10条数据但可以新插入一条年龄大于20的记录。事务A执行完毕再次读取年龄大于20的记录时居然会有没被更新的记录,这就出现了幻读。
mysql通过gap(间隙)锁防止出现幻读。即事务A锁住年龄大于20的所有记录后在这些记录与其他记录的间隙加入锁,其他事务不能再从间隙中插入数据
Serializable 序列化
Serializable 是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。
总结
Record lock
单条索引记录上加锁,record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的。
Gap lock
在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。就像上面提到的场景
快照读:
简单的select操作,快照读不会加任何的锁,也不会被阻塞。但是如果事务的隔离级别是SERIALIZABLE的话,那么快照读也会被加上共享的next-key锁
当前读:
locking read,也就是insert,update,delete, select in share mode和select for update, 当前读会在所有扫描到的索引记录上加锁,不管它后面的where条件到底有没有命中对应的行记录。当前读可能会引起死锁。
意向锁:
innodb的意向锁主要解决用户多粒度的锁并存的情况。
举个例子,如果表中有大量记录,事务A把其中有几条记录上了行锁了,这时事务B需要给这个表加表级锁,如果没有意向锁的话,那就要去表中查找所有的记录是否存在记录上锁。如果事务A在更新一条记录之前,先加意向锁,再加X锁,事务B先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务A释放,而无须逐条记录去检测。意向锁作为事务A已经加锁的标志。
加锁过程分析
对于mysql加锁过程自己并没有非常理解,简单分享一下我的感悟
我们以下面这个表执行delete from t1 where id = 10 为例进行分析
我们的示例基于mysql的innodb引擎RR隔离级别
情景一:id字段为主键
id是主键时,此SQL只需要在id=10这条记录上加X锁即可。
情景二:id字段有唯一索引,name字段为主键
id是unique索引,而主键是name列。此时,加锁的情况由于组合一有所不同。由于id是unique索引,因此delete语句会选择走id列的索引进行where条件的过滤,在找到id=10的记录后,首先会将unique索引上的id=10索引记录加上X锁,同时,会根据读取到的name列,回主键索引(聚簇索引),然后将聚簇索引上的name = ‘d’ 对应的主键索引项加X锁。由上面对Record lock锁的介绍,这里的主键和唯一索引建立了两个不同的索引树注意这里两把锁加的位置是索引上,而非记录本身。为什么聚簇索引上的记录也要加锁?试想一下,如果并发的一个SQL,是通过主键索引来更新:update t1 set id = 100 where name = ‘d’; 此时,如果delete语句没有将主键索引上的记录加锁,那么并发的update就会感知不到delete语句的存在
情景三:id字段有非唯一索引,name字段为主键
为方便描述我们在这里换一个表,还是执行delete from t1 where id = 10 语句
这里的加锁过程如下
与情景二的区别在于,将所有满足查询条件的记录都加锁。另外,这里出现了我们前面说的gap锁,保证在事务执行期间已经锁定了id=10的记录,其他事务不能再插入id=10的记录不会,防止了幻读。
情景四:id字段无索引,name字段为主键
若id列上没有索引,SQL会走聚簇索引(主键索引)的全扫描进行过滤,会锁上表中的所有记录,同时会锁上聚簇索引内的所有GAP,杜绝所有的并发 更新/删除/插入 操作。当然,也可以有些手段来缓解加锁开销与并发影响,但是那样本身也会带来其他问题,而且不满足条件的记录上的加锁/放锁动作不会省略
后记
本文是对mysql锁的知识点的总结,参考的主要来源是一位叫crazyYang的网友 http://www.cnblogs.com/crazylqy/p/7611069.html