MySQL 入门(4):锁

在这里,会涉及到一个概念:两阶段加锁协议。

之后,我会介绍行锁中的S锁和X锁,以及这两种锁的作用。

但是我们会发现仅仅有行锁是不能解决幻读问题的,于是我会用例子的方式跟你介绍各种间隙锁。

最后,我会聊一聊粒度更大的表级锁和库锁。

1 行锁

在上一篇的文章中,我们用了这个具体的例子来解释MVCC:

MySQL 入门(4):锁

假设我们调换一下T5和T6:

MySQL 入门(4):锁

此时,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锁,此时的“可重复读”,就变成了“序列化”。

2 间隙锁 2.1 幻读问题

还记得我们上面提到过的幻读吗?

现在你应该能够理解幻读产生的原因了:因为在插入数据的时候,InnoDB采用的是当前读,而读取数据的时候,由于MVCC的存在,采用的是快照读,这就造成了幻读。

但是我们在上面又提到了,select语句也一样可以采用“当前读”。那么,这样能解决幻读吗?

答案是能解决其中一种情况的幻读。

比如我们在上一篇文章中举的关于幻读的例子:

MySQL 入门(4):锁

现在你能理解了,因为这里的select是快照读,而事务B的插入操作对于事务A来说是不可见的。如果在T5时刻,事务A的sql语句是select * from t where v = 0 for update,即采用当前读的话,是可以看得到事务B所提交的数据的,这样的话,就避免了幻读的情况。

那如果在T2时刻,事务A的语句就是select * from t where v = 0 for update会怎么样的?

如果在T2时刻就使用了“当前读”,那么T3时刻事务B是无法进行插入操作的。你可以理解为,T2时刻,InnoDB把v=0的数据,都给加上了一把锁。

因为这行sql语句把v=0的数据行都锁住了,所以没有办法再插入一行v=0的数据。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfxyz.html