26、MySQL 教程 - MySQL 锁家族:从数据库操作的类型划分~读锁~写锁

对于数据库中并发事务的读-读情况并不会引起是什么问题。对于写-写、读-写或者写-读这些情况可能会引起一些问题,需要使用MVCC或者加锁的方式来解决它们。在使用加锁的方式解决问题时,由于既要允许读-读情况不受影响,又要使写-写、读-写或者写-读情况中的操作相互阻塞,所以MySQL实现一个由两种类型的锁组成的锁系统来解决。这两种类型的锁通常称为共享锁(S锁)和排他锁(X锁)。也叫读锁和写锁。

读锁:也称为共享锁、英文用S表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。

写锁:也称为排他锁、英文用X表示。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。

注意:对于Innodb存储疫情来说,读锁和行锁可以加载表上,页可以加在行上。

假如事务T1首先获取了一条记录的S锁,之后事务T2接着也也要访问这条记录:

如果事务T2想要再获取一个记录的S锁,那么事务T2也会获取该锁,这也意味着事务T1和T2在该记录上同时持有S锁。

如果事务T2想要再获取一个记录的X锁,那么此操作会被阻塞,直到事务T1提交之后将S锁释放掉为止。

假如事务T1首先获取了一条记录的X锁,那么之后无论事务T2是想要获取该记录的S锁还是X锁,都会被阻塞,直到事务T1提交之后将X锁释放为止。

所以,S锁和S锁是兼容的,S锁和X锁是不兼容的,X锁与X锁也是不兼容的

1. 锁定读

在采用加锁方式解决脏读、不可重复付款、幻读这些问题时,读取一条记录时需要获取该记录的S锁,其实是不严谨的,有时候需要在读取记录时就获取记录的X锁,来进制别的事务读写该记录,为此MySQL提出了两种比较特殊的select语句格式:

对于读取的记录加S锁:

select ... lock in share mode;
// 或者
select ... for share;

在普通的 select 后边加 lock in share mode,如果当前事务执行了该语句,那么它就会为读取到的记录加S锁,这样允许别的事务继续读取这些记录的S锁(比方说别的事务页使用 select ... lock in share mode 语句来读取这些记录),但是不能获取这些记录的X锁(比方说使用 select ... for update 语句来读取这些这些记录,或者修改这些记录)。如果别的事务想要获取这些记录的X锁,那么他们会阻塞,直到当前事务提交之后将这些记录的S锁释放掉。

对于读取的记录加X锁:

select ... for update;

在普通的 select 语句后边加 for update,如果当前事务执行了该语句,那么它会为读取的记录加X锁,这样即不允许别的事务获取这条记录的S锁(比方说别的事务页使用 select ... lock in share mode语句来读取这些记录),也不允许获取这些记录的X锁(比方说使用 select ... for update语句来读取这些这些记录,或者修改这些记录)。如果别的事务想要湖区这些记录的S锁或者X锁,那么他们会阻塞,知道当前事务提交之后将这些记录的X锁释放掉。

2. 写操作

平时使用的写操作无非是delete、update、insert这3种:

1. delete :

对于一条记录做 delete 操作的过程其实是先在B+树中定位到这条记录的位置,然后获取这条记录的X锁,再执行delete mark 操作。我们可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。

2. update:

在对一条语句做update操作时分为3种情况:

情况1:未修改该记录的键值,并且被更新的列占用的存储空间在修改前后未发生变化。则先在B+树中定位到这条记录的位置,然后再获取记录的X锁,最后在原记录的位置进行修改操作。我们也把这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读。

情况2:未修改该记录的键值,并且至少有一个被更新的列占用的存储空间在修改前后未发生变化。则先在B+树中定位到这条记录的位置,然后再获取记录的X锁,将该记录彻底删除掉,最后再插入一条新记录。这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读,新插入的记录由insert操作提供的隐式锁保护。

情况3:修改了该记录的键值,则相当于在原记录做 delete 操作之后再来一次 insert 操作,加锁操作就需要按照delete 和insert 的规则进行了。

3. insert :

一般情况下,新插入一条记录的操作并不加锁,新插入的示例受隐式锁保护,不需要在规则中生成对应的锁结构。