当前读与快照读
遵循数据的隔离性,在RR级别下实现可重复读,避免幻读,数据读取的2种形式
当前读:
读取最新版本数据,通过锁实现 next-key 临建锁
以下语句会使用当前读 select...lock in share mode (共享读锁 , 加s锁) select...for update (排它锁 加x锁) update , delete , insert (排它锁 加x锁)
锁
锁是加在索引上的,而不是加在数据上的,record-lock锁的是叶子节点,gap锁的是叶子节点的next指针 对select for update而言,只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!没有索引时,所有扫描到的行和间隙都会上锁
表锁就是锁住了所有间隙和所有行
可重复读RR,当前读需要锁保证数据一致性,innodb是通过next-key lock 来解决幻读,RC时失效
next-key-lock是由record-lock和gap-lock组成的.
record-lock:行锁,也叫记录锁 gap-lock:间隙锁,RR才会存在,实际数据之间的间隙也会被加上锁
innodb中行锁、间隙锁与表的增删该查的关系
行锁包括两种锁,行共享锁和行排他锁,排他锁与两种锁均互斥,共享锁之间不互斥 行锁的加锁时间:执行语句需要的时候加上,用完也不释放,事务commit完才会释放 间隙锁与插入操作互斥 select可以获得两种锁:普通select不加锁,select lock in share mode获取行共享锁,select for update获得排他锁 update delete和insert均在执行的时候获取行排他锁。
next-key加锁规则
CREATE TABLE `t` (
`id` INT ( 11 ) NOT NULL,
`c` INT ( 11 ) DEFAULT NULL,
`d` INT ( 11 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `c` ( `c` )
) ENGINE = INNODB;
INSERT INTO t VALUES
( 0, 0, 0 ),
( 5, 5, 5 ),
( 10, 10, 10 ),
( 15, 15, 15 ),
( 20, 20, 20 ),
( 25, 25, 25 );
原则 1:加锁的基本单位是 next-key lock。next-key lock 是前开后闭区间,如(5,10]。 原则 2:查找过程中访问到的对象才会加锁。 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。 优化 3: (是bug?):唯一索引上的范围查询会访问到不满足条件的第一个值为止。(会向后锁住一段间隙)
案例1: select * from t where c=5 lock in share mode ; 推理:初始加锁(0,5]和(5,10], 根据优化2,优化成(0,5]和(5,10)
升级案例:行锁可以加到整行上,也可以加到索引上 session1: select * from t where c=5 lock in share mode ; session2: update t set d=d+1 where id=5; //阻塞 推理:根据原则2,边查找边加锁 , session1会在c的索引上加c=5行锁,因为需要回表,id的索引上也加了id=5行锁,因此阻塞 但是:将session1修改为select id from t where c=5 lock in share mode;达到了所有覆盖的效果,就不会锁住id=5的索引行锁,session2就不会阻塞
案例2:唯一索引范围查找 select * from t where id>=10 and id < 11 for update; 推理:对于条件id>=10,(5,10]的next-key-lock,因为是id是唯一索引,根据优化1,退化为id=10的行锁 , id<11,加(10,15]的next-key lock,并且是范围查询,不会执行优化2 最终的锁: id=10行锁+(10,15]的next-key lock
案例2-2:普通索引范围查找 select * from t where c>=10 and c<11 for update; 推理:对于条件c>=10,c索引加上(5,10]的next-key lock,因为是c不是唯一索引,锁不会退化 c<11,c索引加(10,15]的next-key lock,并且是范围查询,不会执行优化2 最终的锁: (5,10]的next-key lock+(10,15]的next-key lock
案例2-3:唯一索引bug select * from t where id>10 and id<=15 for update; 推理:条件id>10 会加(10,15] next-key lock id<=15 会根据上面规则中的bug加一个(15,20] next-key lock 所以总共的锁:(10,15] +(15,20]
案例3:limit明确限制
执行:delete from t where c=10 ; 根据原则1,这里加的是 (c=5,id=5) 到 (c=10,id=10) 这个 next-key lock。 然后, 向右查找,直到碰到 (c=15,id=15) 这一行,循环才结束。根据优化 2,这是一个等值查询,向右查找到了不满足条件的行,所以会退化成 (c=10,id=10) 到 (c=15,id=15) 的间隙锁,如上图
添加limit关键字 : selete from t where c=10 limit 2
索引 c 上的加锁范围就变成了从(c=5,id=5) 到(c=10,id=30) 这个前开后闭区间
案例4:不存在等值查找 update t set d=d+1 where id=7 ; 刚开始加的锁是(5,10],根据优化2,最终锁范围是(5,10)的间隙锁,不包括id=10
结论:主键或者唯一索引 1.索引命中唯一行,不会产生gap-lock,只会锁住当前行的record-lock(优化1) 2.范围搜索或者未命中,会产生gap-lock
结论:普通索引的间隙锁 1.普通索引只要加锁就会产生间隙锁,因为索引不唯一,mysql会向后查找到第一个不满足的值才停止,不满足的值之前的空隙也要加锁
- 在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通索引排序,再根据唯一索引排序。
分析是否被阻塞,要结合主键id的排序来看,排序如图
事务3添加 id = 6,number = 8 的数据,给阻塞了; 事务4添加 id = 8,number = 8 的数据,正常执行了。 id = 11,number = 12 的数据修改为 id = 11, number = 5的操作,给阻塞了;
间隙锁导致的死锁问题
表中有id为 5, 10的2行数据 session A: 第1步: select * from t where id=9 //不存在行,产生间隙锁(5,10) 第3步:insert into (9,9,9) //阻塞
session B: 第2步: select * from t where id=9 //不存在行,产生间隙锁(5,10) 第4步:insert into (9,9,9) //被检测出死锁报错
第2,4是可以执行的,因为间隙锁是不互斥的,但是后面的语句会被阻塞,第4步会被死锁检测出来,报错
快照读: (详见快照读笔记)
读取当前事物可见的数据版本,通过mvcc实现,因此不用加锁