在MySQL的事务并发控制中,有三种方案:分别是悲观并发控制、乐观并发控制和多版本并发控制。MVCC在Post not found: jmysql/mvcc 上一篇文章中已经讲过了,不再赘述。
悲观锁
悲观并发控制也就是悲观锁,同其他领域的锁一样,为了最大化的并发能力,分为互斥锁和共享锁。在MySQL中互斥锁就是写锁(x),共享锁就是读锁(s)。如果一个事务持有读锁,那么其他事务可以继续持有读锁,但是不可以持有写锁。如果一个事务持有写锁,那么其他事务什么锁都不能持有。
对读取的记录加S锁:
1 | SELECT ... LOCK IN SHARE MODE; |
对读取的记录加X锁:
1 | SELECT ... FOR UPDATE; |
锁粒度
正常来说,我们锁定的资源是记录,也就是行锁。但是也可以加表锁。表锁也分读锁和写锁,执行逻辑和行锁也是一样的。不过由于加表锁时,表内所有的记录也可能已经被上锁,那么为了保证读写锁的逻辑完整。MySQL引入了意向锁(Intention Locks),意向锁也分读锁(is)和写锁(ix)。这样在一个事务给某个记录上读锁或者写锁时,需要先给表上is或者ix。这样另一个事务在尝试上表锁时,根据表的意向锁情况,执行上锁逻辑。例如:如果发现已经有ix了,那么就不能给表上写锁。
行锁
行锁的基本结构可以如下:
行锁也有很多不同的类型:
Record Locks
最普通的行锁,有写锁和读锁的区别。
Gap Locks(间隙锁)
MySQL在REPEATABLE READ隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用MVCC方案解决,也可以采用加锁方案解决。gap锁就可以解决幻读的问题,并且仅仅是为了防止插入幻影记录而提出的。比方说我们把number值为8的那条记录加一个gap锁的示意图如下:
如图中为number值为8的记录加了gap锁,意味着number列的值(3, 8)这个区间的新记录是不允许立即插入的。如果你对一条记录加了gap锁(不论是共享gap锁还是独占gap锁,作用都是相同的),并不会限制其他事务对这条记录加Record Locks或者继续加gap锁,再强调一遍,gap锁的作用仅仅是为了防止插入幻影记录的而已。为了实现阻止其他事务插入number值在(20, +∞)这个区间的新记录,我们可以给索引中的最后一条记录,也就是number值为20的那条记录所在页面的Supremum记录加上一个gap锁,画个图就是这样:
Next-Key Locks
Record Locks和Gap Locks的合体,它既能保护该条记录,又能阻止别的事务将新记录插入被保护记录前边的间隙。
隐式锁
一个事务对新插入的记录可以不显式的加锁(生成一个锁结构),但是由于事务id的存在,相当于加了一个隐式锁。别的事务在对这条记录加S锁或者X锁时,由于隐式锁的存在,会先帮助当前事务生成一个锁结构,然后自己再生成一个锁结构后进入等待状态。