两种标准的行级锁

  • 共享锁(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级别下,事务总是读取事务开始时的快照版本。
image.png
在第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

  1. select * from table where id > 2 for update;(事务T1)(获取结果3、4、5)
  2. insert table values (6);commit;(事务T2)
  3. select * from table where id > 2 for update;(事务T1)(获取结果3、4、5、6)

显然在事务T1中,1、3两次获取的结果不一样,这就是幻读。那么在RR下,该场景中的第二步会被临键锁阻塞,锁住了(2, +无穷)的范围。

死锁

解决方案:

  1. 设定超时时间,抛出超时异常回滚事务,让另外一个事务继续进行下去。
  2. 等待图:通过对锁节点的链接和顺序进行检测,提前发现死锁。

锁升级

如果需要添加的行锁非常多,mysql会升级为表锁。