摘要
在这篇文章中,我将从上一篇的一个小例子开始,跟你介绍一下InnoDB中的行锁。
在这里,会涉及到一个概念:两阶段加锁协议。
之后,我会介绍行锁中的S锁和X锁,以及这两种锁的作用。
但是我们会发现仅仅有行锁是不能解决幻读问题的,于是我会用例子的方式跟你介绍各种间隙锁。
最后,我会聊一聊粒度更大的表级锁和库锁。
1 行锁
在上一篇的文章中,我们用了这个具体的例子来解释MVCC:
假设我们调换一下T5和T6:
此时,T5是没有办法执行的。
原因是这样的:InnoDB在更新一行的时候,需要先获取这一行的行锁。
但是,当一条语句获取了行锁之后,不是这行语句执行完毕就能释放锁,而是要等到这个事务执行完毕,才会释放锁。
这里涉及到了两阶段加锁协议:它规定事务的加锁和解锁分为两个独立的阶段,加锁阶段只能加锁不能解锁,一旦开始解锁,则进入解锁阶段,不能再加锁。
然后我们再来说说共享锁(S锁,读锁)和排他锁(X锁,写锁)。
对于共享锁来说,如果一个事务获取了某一行的共享锁,则这个事务只能读这一行数据,而不能修改,并且其他事务也可以获取这一行数据的共享锁,读取这一行的数据,同样不能修改数据。
对于排它锁,只能被某一个事务获取。并且在获取排它锁之前,这一行数据上不能存在共享锁。一旦某一个事务获取了这一行的排它锁,那么只有这一个事务可以对这一行数据进行读写操作,其他事务对这一行数据的读写操作都会被阻塞。
此外,不仅仅只有更新操作,插入、删除操作也会获取这一行数据的X锁。
在这里我还要再介绍这两个概念:“快照读”和“当前读”。
你可能还会有印象,在上一篇内容中,我提到了所有的更新操作都必须是“当前读”,现在可以解释原理了,在更新一行数据的时候,InnoDB会对需要更新的那行数据加上X锁,直接获取最新的那一行数据。
与之相对的是“快照读”,也就是MVCC中的数据读取方式,利用“快照”来读取数据的方式,可以极大的提高事务的并发度。
但是并不是说select语句就只能读取快照,它也照样可以给需要读取的数据加锁,来读取最新的数据。也就是说,select语句也一样可以“当前读”。
下面这两个select语句,就是分别加了读锁(S锁,共享锁)和写锁(X锁,排他锁)。
mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;
注意,由于两阶段加锁协议的存在,如果你采用了一致性读,那么这个锁必须要等事务提交后才能解除。这是牺牲了并发度的一种做法。所以,如果所有的select语句,都加上了S锁,此时的“可重复读”,就变