在使用加锁的方式解决问题时,由于既要允许读-读情况不受影响,又要使写-写、读-写或写-读情况中的操作相互阻塞,MySQL 中的锁有好几类:
共享锁,英文名:Shared Locks,简称 S 锁。在事务要读取一条记录时,需要先获取该记录的 S 锁。
SELECT ... LOCK IN SHARE MODE;
独占锁,也常称排他锁,英文名:Exclusive Locks,简称 X 锁。在事务要改动一条记录时,需要先获取该记录的 X 锁。
SELECT ... FOR UPDATE
假如事务 E1 首先获取了一条记录的 S 锁之后,事务 E2 接着也要访问这条记录:
如果事务 E2 想要再获取一个记录的 S 锁,那么事务 E2 也会获得该锁,也就意味着事务 E1 和 E2 在该记录上同时持有 S 锁。
如果事务 E2 想要再获取一个记录的 X 锁,那么此操作会被阻塞,直到事务E1 提交之后将 S 锁释放掉。
如果事务 E1 首先获取了一条记录的 X 锁之后,那么不管事务 E2 接着想获取该记录的 S 锁还是 X 锁都会被阻塞,直到事务 E1 提交。
所以我们说 S 锁和 S 锁是兼容的,S 锁和 X 锁是不兼容的,X 锁和 X 锁也是不兼容的,画个表表示一下就是这样:
X 不兼容 X 不兼容 S
S 不兼容 X 兼容 S
DELETE
对一条记录做DELETE 操作的过程其实是先在B+树中定位到这条记录的位置, 然后获取一下这条记录的 X 锁,然后再执行 delete mark 操作。我们也可以把这个定位待删除记录在 B+树中位置的过程看成是一个获取 X 锁的锁定读。
INSERT
一般情况下,新插入一条记录的操作并不加锁,InnoDB 通过一种称之为隐**式锁**来保护这条新插入的 记录在本事务提交前不被别的事务访问。当然,在一些特殊情况下 INSERT 操作也是会获取锁的。
UPDATE
在对一条记录做 UPDATE 操作时分为三种情况:
- 如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化,则先在 B+树中定位到这条记录的位置,然后再获取一下记录的 X 锁, 最后在原记录的位置进行修改操作。其实我们也可以把这个定位待修改记录在B+树中位置的过程看成是一个获取 X 锁的锁定读。
- 如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化,则先在 B+树中定位到这条记录的位置,然后获取一下记录的 X 锁,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新记录。这个定位待修改记录在 B+树中位置的过程看成是一个获取 X 锁的锁定读, 新插入的记录由 INSERT 操作提供的隐式锁进行保护。
- 如果修改了该记录的键值,则相当于在原记录上做 DELETE 操作之后再来一次 INSERT 操作,加锁操作就需要按照 DELETE 和 INSERT 的规则进行了。