两种标准的行级锁
- 共享锁(Share):允许读行数据
- 排他锁(eXclude):允许删除/更新行数据
有以下锁获取顺序:
- 事务T1获得该行的S锁,事务T2可以继续获得该行的S锁。
- 事务T1获得该行的S锁,事务T2不能获得该行的的X锁。
- 事务T1获得该行的X锁,事务T2不能获得该行的的S锁。
- 事务T1获得该行的X锁,事务T2不能获得该行的的X锁。
MVCC
已知XX锁是互斥,但是XS互斥会极大影响sql的性能,所以通过MVCC的方式来解决这个问题。
当一行数据正在被事务T1删除或更新,也就是获取了行记录X锁被获取之后,读取操作会从快照中获取数据(不加S锁)。快照是该行数据在被T1操作之前内容,也就是undolog充当了快照数据的存储角色。由于为了保证事务一致性,undolog本身就是要记录的,所以快照机制并没有额外引入复杂度。
快照是有多个版本的,因为多个事务T1并发操作一条行数据,那么该
在RC级别下,事务总是读取最新的快照版本。
在RR级别下,事务总是读取事务开始时的快照版本。
在第5步中,RC和RR获取到的快照都是1。
在第6步行数据的更新提交后,生成了新的快照版本id=3。
所以在第7步中,RC获取的快照是3,RR获取到的快照还是1。
加锁读
快照读可以极大提升db性能,所以是mysql默认开启的,但是有些场景下需要对读操作进行加锁。
有两种语法:
- select * from table where id = 1 for update;(加X锁,等同于于写操作)
- select * from table where id = 1 lock in share mode;(加S锁)
由于快照读是不加锁的,所以for update之后,依然可以用select * from table where id = 1获取数据。
锁的3种算法
- 行锁:单行数据加锁,为了解决并发读写问题(加在索引上)。
- 间隙锁:锁定一个范围,不包含数据本身。
- 临键锁:上面两者结合,为了解决幻读问题。
临键锁的使用场景如下(只有在RR级别下才开启):
比如表中有数据1、2、3、4、5
- select * from table where id > 2 for update;(事务T1)(获取结果3、4、5)
- insert table values (6);commit;(事务T2)
- select * from table where id > 2 for update;(事务T1)(获取结果3、4、5、6)
显然在事务T1中,1、3两次获取的结果不一样,这就是幻读。那么在RR下,该场景中的第二步会被临键锁阻塞,锁住了(2, +无穷)的范围。
死锁
解决方案:
- 设定超时时间,抛出超时异常回滚事务,让另外一个事务继续进行下去。
- 等待图:通过对锁节点的链接和顺序进行检测,提前发现死锁。
锁升级
如果需要添加的行锁非常多,mysql会升级为表锁。