MySQL锁(二) 共享锁与互斥锁
共享锁与排他锁
2.1 共享锁(S锁)
定义:一个事务已获取共享锁,当另一个事务尝试对具备共享锁的数据进行读操作时,可正常读;进行写操作时,会被共享锁排斥。
共享锁的意思很简单,也就是不同事务之间不会排斥,可以同时获取锁并执行。但这里所谓的不会排斥,仅仅只是指不会排斥其他事务来读数据,但其他事务尝试写数据时,就会出现排斥性,举个例子理解:
事务
T1
对ID=18
的数据加了一个共享锁,此时事务T2、T3
也来读取ID=18
的这条数据,这时T2、T3
是可以获取共享锁执行的;但此刻又来了一个事务T4
,它则是想对ID=18
的这条数据执行修改操作,此时共享锁会出现排斥行为,不允许T4
获取锁执行。
在 MySQL
中,我们可以在SQL
语句后加上相关的关键字来使用共享锁,语法如下:
SELECT ... LOCK IN SHARE MODE;
-- MySQL8.0之后也优化了写法,如下:
SELECT ... FOR SHARE;
这种通过在 SQL
后添加关键字的加锁形式,被称为显式锁,而实际上为数据库设置了不同的事务隔离级别后,MySQL
也会对 SQL
自动加锁,这种形式则被称之为隐式锁。
样例:做个关于共享锁的小测试,先打开两个cmd窗口并于 MySQL
建立连接
-- 窗口1:
-- 开启一个事务
begin;
-- 获取共享锁并查询 id=2 的数据
select * from bank_balance where id=2 lock in share mode;
-- 窗口2:
-- 开启一个事务
begin;
-- 获取共享锁并查询 id=2 的数据
select * from bank_balance where id=2 lock in share mode;-- 尝试修改id=2的数据
update bank_balance set balance=230 where id=2;
- 当窗口1获取了共享锁,窗口2执行查询/读操作时 可获取共享锁、正常读;但当窗口2执行修改/写操作时 窗口2没反应、未执行成功。
- 而当窗口1中事务A提交后,窗口2事务B的写操作才能继续往下执行。
由上可见,一个事务已获取共享锁,当另一个事务尝试对具备共享锁的数据进行读操作时,可正常读;进行写操作时,会被共享锁排斥。因此从这个实验中可以得知:共享锁也具备排他性,会排斥其他尝试写的线程,当有线程尝试修改同一数据时会陷入阻塞,直至持有共享锁的事务结束才能继续执行
2.2 排他锁(X锁)
上面简单的了解了共享锁之后,紧着来看看排他锁,排他锁也被称之为独占锁。
当一个线程获取到独占锁后,会排斥其他线程(进行读写操作),如若其他线程也想对共享资源/同一数据进行操作,必须等到当前线程释放锁并竞争到锁资源才行。
值得注意的一点是:排他锁并不是只能用于写操作,对于一个读操作,咱们也可以手动地指定为获取排他锁,当一个事务在读数据时,获取了排他锁,那当其他事务来读、写同一数据时,都会被排斥。比如事务
T1
对ID=18
的这条数据加了一个排他锁,此时T2
来加排他锁读取这条数据,T3
来修改这条数据,都会被T1
排斥。
在MySQL
中,可以通过如下方式显式获取独占锁:
SELECT ... FOR UPTATE;
测试:
当两个事务同时获取排他锁,尝试读取一条相同的数据时,其中一个事务就会陷入阻塞,直至另一个事务结束才能继续往下执行;
但是select * from bank_balance where id=2
这种普通读 不会被阻塞,也就是另一个事务不获取排他锁读数据,而是以普通的方式读数据,这种方式则可以立刻执行,Why
?是因为读操作默认加共享锁吗?并不是,因为你尝试加共享锁读这条数据时依旧会被排斥。
可以明显看到,第二个事务中尝试通过加共享锁的方式读取这条数据,依旧会陷入阻塞状态,那前面究竟是因为啥原因才导致的能读到数据呢?其实这跟另一种并发控制技术有关,即MVCC
机制,详情可见 MVCC 原理分析、MySQL是如何解决幻读的。
增、删、改都会对数据添加X锁,在查询语句中使用for update也会添加X锁
S锁 | X锁 | |
---|---|---|
S锁 | √ | × |
X所 | × | × |
2.3 MySQL锁的释放
在前面的测试中,每次都仅获取了锁,但好像从未释放过锁?其实MySQL
中释放锁的动作都是隐式的,毕竟如果交给咱们来释放,很容易由于操作不当造成死锁问题发生。因此对于锁的释放工作,MySQL
自己来干,就类似于JVM
中的GC
机制一样,把内存释放的工作留给了自己完成。
- 但对于锁的释放时机,在不同的隔离级别中也并不相同,比如在“读未提交”级别中,是
SQL
执行完成后就立马释放锁;而在“可重复读”级别中,是在事务结束后才会释放。
如果完全按照数据库规范来实现
RC
隔离级别,为了保证其他事务可以读到未提交的数据,那就必须得在SQL
执行完成后,立马释放掉锁,这时另一个事务才能读到SQL
对应写的数据,但在InnoDB
引擎中,它基于MVCC
机制实现了该效果,为此,InnoDB
的RC
级别中,SQL
执行结束后并不会释放锁。