本文概览:1、介绍了三种锁类型,以及RecordLock、GapLock等具体锁。2、以及每一sql执行时如何上锁。
附: 本小节使用到数据表结构和数据为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
mysql> show create table student \G; *************************** 1. row *************************** Table: student Create Table: CREATE TABLE `student` ( `id` int(11) NOT NULL, `name` varchar(45) NOT NULL, `score` int(11) DEFAULT NULL, `des` varchar(45) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `unq_nam` (`name`), KEY `idx_score` (`score`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.01 sec) ERROR: No query specified mysql> select * from student ; +----+-----------+-------+----------+ | id | name | score | des | +----+-----------+-------+----------+ | 0 | vv | 66 | hello | | 1 | success55 | 6 | hi | | 5 | ff10 | 10 | koko | | 7 | ffg | 22 | tomorrow | +----+-----------+-------+----------+ 4 rows in set (0.01 sec) |
1 Mysql锁
1、Latches
在java并发包解除过锁,其实可以理解成LATCHES,控制线程之间争夺资源
2、LOCKS
是控制事务之间争夺资源的。如通过SelectForUpdate
1.1 三个基本锁
常见的有三种锁:共享锁,排他锁,意向锁。其中意向锁分为意向共享锁和意向排他锁。
1.1.1 互斥锁(X Lock)和共享锁(S Lock)
1、互斥锁和共享锁关系
共享锁 | 排他锁 | |
共享锁 | 兼容 | 冲突 |
排他锁 | 冲突 | 冲突 |
这里需要注意一点,就是锁具有可重入性,即,对于同一事务,在获取锁(S或X lock)之后,仍然可以继续获取锁。上面表格中锁的关系是指不同事务所属的锁之间关系
2、使用锁
1 2 3 4 5 6 |
//显示共享锁(S) : SELECT * FROM table_name WHERE .... LOCK IN SHARE MODE //显示排他锁(X): SELECT * FROM table_name WHERE .... FOR UPDATE. |
1.1.2 意向锁
为了支持多粒度的上锁(表锁和行锁),引入意向锁。比如事务A要在一个表上加S锁,如果表中的一行已被事务B加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级上引入新的锁类型来表示其所属行的加锁情况,这就引出了“意向锁”。所以,意向锁只有在使用表锁时,才会用到。
每次添加行锁时,都需要首先添加意向锁。加行锁步骤为:
- step1:先添加意向锁,因为意向锁都兼容,都是都会添加。
- step2:对行记录添加排他或者共享锁。
1.1.3 兼容关系
三种锁之间的兼容关如下表,这种兼容关系会导致事务死锁。
- 共享锁和 共享锁、意向共享锁兼容。与其他锁冲突
- 排他锁 和任何锁都是 冲突。
- 意向锁和意向锁兼容。 意向共享锁和共享锁兼容;意向排他锁和非意向锁冲突。
共享锁 | 排他锁 | 意向共享锁 | 意向排他锁 | |
共享锁 | 兼容 | 冲突 | 兼容 | 冲突 |
排他锁 | 冲突 | 冲突 | 冲突 | 冲突 |
意向共享锁 | 兼容 | 冲突 | 兼容 | 兼容 |
意向排他锁 | 冲突 | 冲突 | 兼容 | 兼容 |
1.2 Innodb 行锁
与MyISAM不同,InnoDB有两大不同点:
- 支持事务
- 采用行级锁
mysql innodb提供了三种行锁
- Record Lock:单个行记录上的锁(X锁或者S锁)
- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。Gap Lock引入是为了避免幻读。
- Next-key Lock算法:Record + Gap Lock,锁定一个范围,并锁定记录本身。
- Insert Intention Locks,只有insert时,在获取RecordLock之前进行获取该锁,只会跟GapLock进行互斥,与Record、InsertIntentionLocks都是兼容的。
X/S/IS/IX 和 RecordLock/GapLock 之间的关系?
(1)X、S、IS、IX是锁的类型,不是具体的锁。
(2)具体锁有Recored Lock和Gap Lock,每一种具体锁,都包含X、S、IS、IX类型,如共享类型的GapLock,互斥类型的GapLock。
根据不同事务级别,mysql innodb采用的加锁算法不同:
- 对于Read REPEAT级别,采用Netxt Lock算法。遇到唯一索引和主键降级到Record Lock。
- 对于Read COMMIT 采用Record Lock。
注意:mysqll默认使用的是Read Repeat事务隔离级别。
1.2.1 Gap Lock
1、GapLock介绍
间隙锁GapLock只在insert时会被用到,防止不同事务在insert在同一个gap插入数据,导致幻读出现。
1 2 3 4 5 6 7 |
官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-gap-locks 间隙上的不同冲突类型的锁可以同时被多个不同的事务获取。比如,事务A获取了某个间隙上的一个共享间隙锁(间隙 S-锁),同时事务B可以获取这个间隙上的互斥锁(间隙 X-锁)。多冲突类型间隙锁之所以能存在是因为:如果某个记录从索引中删除时,这条记录上的间隙锁(多个事务持有的)一定会被合并。 It is also worth noting here that conflicting locks can be held on a gap by different transactions. For example, transaction A can hold a shared gap lock (gap S-lock) on a gap while transaction B holds an exclusive gap lock (gap X-lock) on the same gap. The reason conflicting gap locks are allowed is that if a record is purged from an index, the gap locks held on the record by different transactions must be merged. 间隙锁在InnoDB中是专一功能(purely inhibitive),这意味着它们只能防止其他事务在这个间隙中插入数据,而无法阻止不同的事务在同样的间隙上获取间隙锁。所以就间隙锁来说,S锁和X锁效果一样 Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. They do not prevent different transactions from taking gap locks on the same gap. Thus, a gap X-lock has the same effect as a gap S-lock. |
对于同一个GAP区间,GapLock和Record Lock、GapLock和GapLock都是兼容的,GapLock只会跟Insert Intention Locks互斥。如下图
GapLock | InsertIntentionLock | RecordLock | |
GapLock | 兼容 | 兼容 | 兼容 |
InsertIntentionLock | 冲突 | 冲突 | 兼容 |
RecordLock | 兼容 | 兼容 | 冲突 |
2、GapLock算法
以普通索引key为例,
对于存在(1,3)(4,6)(6,8),对于key=6,不仅需要添加(4,6)gap锁,还需要对普通索引的下一个键值添加gap lock,即(6,8),则此时会有两个gap 锁。
3、如何理解GapLock可以避免幻读。
在没有GapLock,只有Record lock时可以走通,出现幻读,如下
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> select * from student where score<22 for update ; 出现2条数据 |
|
4 |
mysql> insert into student(id,name,score,desc) values (10,’ff’,16,’luce’); |
|
5 | commit | |
6 |
select * from student where score<22 for update ; 出现幻读:发现有3条数据 |
当存在GapLock时,GapLock可以阻止多个事务将记录插入到同一范围内,而导致上述流程会被阻塞。
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> select * from student where adr<‘cc’ for update ; 上锁:Gap锁(10,22) |
|
4 |
mysql> insert into student(id,name,score,desc) values (10,’ff’,16,’luce’); 需要等待事务1Gap锁(10,22),然后上锁Insert Intention Locks Lock wait timeout exceeded; try restarting transaction |
2 基于RR事务隔离级别的sql语句加锁
1、INSERT
只添加 Reocord Lock,没有使用gap 锁。在获取Reocred Lock时,还需要获取一个Insert Intention Locks。
Insert Intention Locks只发生在insert时,在获取RecordLock之前进行获取该锁。Insert Intention Lock只会跟GapLock进行互斥,与Record、InsertIntentionLocks都是兼容的。
2、对于DELETE FROM … WHERE … 、SELECT … FOR UPDATE 和 SELECT LOCK IN SHARE MODEL、Update … WHERE 三种操作,需要根据where条件来加锁,可以通过执行计划查看使用那些索引。
- where中使用普通索引,使用next-key lock,即不仅加RecordLock而且加GapLock。
- where使用唯一索引/主键。都是使用Recored lock。
- where 中未使用普通索引。锁表,对所有元素进行上锁RecordLock,而且对所有Gap上锁GapLock
2.1 Insert
1、Insert只会设置一个Reocred lock,不是 next-key lock。如下
(1)两个事务插入相同的元素时
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’); 上锁:Record Lock |
|
4 |
mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’); 等待事务1的Record Lock Lock wait timeout exceeded; try restarting transaction |
(2)当两个事务插入元素的主键不同时,此时不会进行阻塞
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’); Query OK, 1 row affected (0.01 sec) |
|
4 |
mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’); Query OK, 1 row affected (0.01 sec) |
2、当存在一个事务A已经执行了insert插入相同的元素,则当前事务B的insert操作会尝试获取一个RecordLock共享锁,此时会进行等等待,直到A进行commit或者rollback。
1 2 3 |
参考:https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. |
在 如下文中“三个insert”和“delete+2个insert”都是因为这个共享锁导致的死锁。
2.2 SelectForUpdate
2.2.1 普通索引
SelectForUpdate的where条件为普通索引时,会“score=22”数据上RecordLock),还有(10,22)和(22,无穷大)两个GapLock。
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> select * from student where score = 22 for update; 上锁GapLock和RecordLock |
|
4 |
mysql> insert into student(id,name,score,des) values (3,’ggg’,12,’luc’); 事务2需要等待事务1的Gap锁,然后上锁Insert Intention Locks,然后在上锁RecordLock Lock wait timeout exceeded; try restarting transaction |
在事务2执行insert进行阻塞时,查看锁信息,发现SelectForUpdate的where条件是普通索引时,此时lock_mode为X和Gap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
mysql> select * from information_schema.innodb_locks \G; *************************** 1. row *************************** lock_id: 50567:1351:5:3 lock_trx_id: 50567 lock_mode: X,GAP lock_type: RECORD lock_table: `test`.`student` lock_index: idx_score lock_space: 1351 lock_page: 5 lock_rec: 3 lock_data: 22, 7 *************************** 2. row *************************** lock_id: 50566:1351:5:3 lock_trx_id: 50566 lock_mode: X lock_type: RECORD lock_table: `test`.`student` lock_index: idx_score lock_space: 1351 lock_page: 5 lock_rec: 3 lock_data: 22, 7 2 rows in set (0.02 sec) |
2.2.2 唯一键/主键
对于上面操作步骤中,是互不影响的。查看锁的信息为
1、唯一键的信息,发现lock_mode只有X没有GAP
执行sql:
1 |
select * from student where name = 'vv' for update; |
查询锁信息为
1 2 3 4 5 6 7 8 9 10 |
lock_id: 48822:1348:4:5 lock_trx_id: 48822 lock_mode: X lock_type: RECORD lock_table: `test`.`student` lock_index: unq_nam lock_space: 1348 lock_page: 4 lock_rec: 5 lock_data: 'vv' |
2、主键的SelectForUpdate的锁信息,发现lock_mode只有X没有GAP
(1)sql
1 |
select * from student where id = 0 for update; |
(2)查询锁信息为
1 2 3 4 5 6 7 8 9 10 |
lock_id: 48848:1348:3:5 lock_trx_id: 48848 lock_mode: X lock_type: RECORD lock_table: `test`.`student` lock_index: PRIMARY lock_space: 1348 lock_page: 3 lock_rec: 5 lock_data: 0 |
3、主键/唯一键 和 普通索引区别。
- 普通索引在执行SelectForUpdate时,不仅对查询到元素进行加RecordLock,还会获取GapLock。
- 主键/唯一键在执行SelectForUpdate时,如果含有数据,则此时不会有GapLock,但是没有数据会产生GapLock。
4、当使用主键/唯一键时,如过查询为空,则此时会添加一个GapLock。
如下文中死锁case: 两个selectForUpdate和两个insert
2.2.3 无索引
执行SelectForUpdate时,如果where没有使用索引时,如何进行上锁?上的表锁。此时,事务1 在没有索引情况下会锁这个表,事务2无法对任何数据进行上锁。
1、执行步骤为
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> select * from student where des = ‘hello’ for update; 查询得到一行数据,但是对整个表的每一个数据进行上锁RecordLock。 |
|
4 |
mysql> select * from student where des = ‘hi’ for update; 等待这个数据RecoredLock。 Lock wait timeout exceeded; try restarting transaction |
2.3.4 没有获取到元素
1、唯一键和主键时
当使用唯一索引/主键 没有获取到元素时,此时没有获取到Reocrdlock,但是会获取到一个GapLock。
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
select * from student where id =5 for update; Empty set (0.00 sec) 上锁gap锁(负无穷,6) |
|
4 |
select * from student where id = 5for update; Empty set (0.00 sec) 上锁gap锁(负无穷,6) |
2、where条件使用普通索引或者不使用索引
都会获取GapLock和RecordLock
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
select * from student where score < 6 for update; Empty set (0.00 sec) 获取ReadLock |
|
4 |
select * from student where score < 6 for update; 获取ReadLock Lock wait timeout exceeded; try restarting transaction |
查看锁信息,两个事务都是获取的RecordLock。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
mysql> select * from information_schema.innodb_locks \G; *************************** 1. row *************************** lock_id: 66987:1351:5:6 lock_trx_id: 66987 lock_mode: X lock_type: RECORD lock_table: `test`.`student` lock_index: idx_score lock_space: 1351 lock_page: 5 lock_rec: 6 lock_data: 6, 1 *************************** 2. row *************************** lock_id: 66986:1351:5:6 lock_trx_id: 66986 lock_mode: X lock_type: RECORD lock_table: `test`.`student` lock_index: idx_score lock_space: 1351 lock_page: 5 lock_rec: 6 lock_data: 6, 1 2 rows in set (0.02 sec) |
2.3 Update
和Select For Update类似,分为普通索引、主键/唯一键、没有索引。
2.3.1 普通索引
此时,会产生gap锁
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> update student set name= ‘ff10’ where score=10; 上锁GapLcok(6,10)、GAPLock(10,22)、RecoredLock:10 |
|
4 |
mysql> insert into student(id,name,score) values (9,’fdd’,9); 需要事务2等待Gap锁(6,10),然后上锁Insert Intention Locks,然后再上锁RecoredLock Lock wait timeout exceeded; try restarting transaction |
|
5 |
mysql> insert into student(id,name,score) values (10,’fdd’,12); 需要事务2等待Gap锁(6,10),然后上锁Insert Intention Locks,然后再上锁RecoredLock Lock wait timeout exceeded; try restarting transaction |
|
6 |
mysql> insert into student(id,name,score) values (11,’fdd’,23); 可以正常执行 Query OK, 1 row affected (0.00 sec) |
2.3.2 主键/唯一键
主键和唯一键只会添加 Record Lock,不会添加Gap Lock。
1、主键
更新不同行不会进行阻塞
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> update student set des = ‘test’ where id = 5; 上锁Record lock:id=5 Query OK, 1 row affected (0.00 sec) |
|
4 |
mysql> update student set des = ‘test’ where id = 7; 上锁RecordLock:id=7,与事务1的RecordLock不冲突,所以不会被阻塞 Query OK, 1 row affected (0.00 sec) |
2、唯一键
更新不同行不会进行阻塞
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> update student set des = ‘test’ where name=’ff10′; 上锁Record lock:name=’ff10′ Query OK, 1 row affected (0.00 sec) |
|
4 |
mysql> update student set des = ‘test’ where name=’ffg’; 上锁RecordLock:name=’ffg’,与事务1的RecordLock不冲突,所以不会被阻塞 Query OK, 1 row affected (0.00 sec) |
2.3.3 没有使用索引
此时,事务1 在没有索引情况下会锁这个表,事务2无法对任何数据进行上锁。
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> update student set score = 100 where des = ‘koko’ ; Query OK, 1 row affected (0.00 sec) 查询得到一行数据,但是对整个表的每一个数据进行上锁RecordLock。 |
|
4 |
mysql> update student set score = 100 where des = ‘hi’ ; 等待事务1这个数据RecoredLock。 Lock wait timeout exceeded; try restarting transaction |
|
5 |
mysql> update student set score = 100 where des = ‘hello’ ; 等待事务1这个数据RecoredLock。 Lock wait timeout exceeded; try restarting transaction |
|
6 |
mysql> update student set score = 100 where des = ‘tommorrow’ ; 等待事务1这个数据RecoredLock。 Lock wait timeout exceeded; try restarting transaction |
2.3.4没有记录时
参考selectForUpdate没有记录的情况
2.4 Delete
2.4.1 普通索引
此时,会产生gap锁
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> delete from student where score = 10; Query OK, 1 row affected (0.00 sec) 上锁GapLcok(6,10)、GAPLock(10,22)、RecoredLock:10 |
|
4 |
mysql> insert into student(id,name,score,des) values (12,’c’,9,’luc’); 事务2等待Gap锁(6,10),然后上锁Insert Intention Locks,然后再上锁RecoredLock Lock wait timeout exceeded; try restarting transaction |
|
5 |
mysql> insert into student(id,name,score,des) values (13,’cc’,12,’luc’); 事务2等待Gap锁(10,22),然后上锁Insert Intention Locks,然后再上锁RecoredLock Lock wait timeout exceeded; try restarting transaction |
|
6 |
mysql> insert into student(id,name,score) values (14,’fdd’,23); Query OK, 1 row affected (0.00 sec) |
2.4.2 主键/唯一键
此时,只会上锁一个RecordLock,以主键为例,如下
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> delete from student where name= ‘ff10’; 只锁了一个Record Lock,没有Gap Lock Query OK, 1 row affected (0.00 sec) |
|
4 |
mysql> insert into student(id,name,score,des) values (12,’c’,9,’luc’); 因为事务1没有GapLock,所以不会阻塞事务2的insert的Insert Intention Locks,此时事务2可以直接获取RecoredLock,然后执行insert语句 Query OK, 1 row affected (0.00 sec) |
2.4.3 没有使用索引
此时,事务1 在没有索引情况下会锁这个表,事务2无法对任何数据进行上锁。
步骤 | 事务1 | 事务2 |
1 | begin | |
2 | begin | |
3 |
mysql> delete from student where des = ‘koko’. 对表中所有记录上了RecordLock |
|
4 |
mysql> update student set score = 100 where id = 0 ; 等待事务1的RecoreLock Lock wait timeout exceeded; try restarting transaction |
|
5 |
mysql> iupdate student set score = 100 where id = 2 ; 等待事务1的RecoreLock Lock wait timeout exceeded; try restarting transaction |
|
6 |
mysql>update student set score = 100 where id = 7 ; 等待事务1的RecoreLock Lock wait timeout exceeded; try restarting transaction |
2.4.4没有记录时
参考selectForUpdate没有记录的情况
附1 分析锁
1、查看事务信息
1 |
select * from information_schema.innodb_trx\G; |
2、查看事务之间等待
1 |
select * from information_schema.INNODB_LOCK_WAITS \G; |
3、查看事务之间锁
1 |
select * from information_schema.INNODB_LOCKS \G; |
对于information_schema#innodb_locks的字段解释。参考https://dev.mysql.com/doc/refman/5.5/en/innodb-locks-table.html
1 2 3 4 5 6 7 8 9 10 |
lock_id:锁 ID。 lock_trx_id:拥有锁的事务 ID。可以和 INNODB_TRX 表 JOIN 得到事务的详细信息。 lock_mode:锁的模式。有如下锁类型:行级锁包括:S、X、IS、IX,分别代表:共享锁、排它锁、意向共享锁、意向排它锁。表级锁包括:S_GAP、X_GAP、IS_GAP、IX_GAP 和 AUTO_INC,分别代表共享间隙锁、排它间隙锁、意向共享间隙锁、意向排它间隙锁和自动递增锁。 lock_type:锁的类型。RECORD 代表行级锁,TABLE 代表表级锁。 lock_table:被锁定的或者包含锁定记录的表的名称。 lock_index:当 LOCK_TYPE=’RECORD’ 时,表示索引的名称;否则为 NULL。 lock_space:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的表空间 ID;否则为 NULL。 lock_page:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的页号;否则为 NULL。 lock_rec:当 LOCK_TYPE=’RECORD’ 时,表示一堆页面中锁定行的数量,亦即被锁定的记录号;否则为 NULL。 lock_data:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的主键;否则为NULL。 |
4、查看死锁信息
1 |
show engine innodb status \G; |
查看如下信息
1 2 3 4 5 6 7 8 |
-------------------- LATEST DETECTED DEADLOCK ------------------------ ........ ........ |
- lock_mode X locks rec but not gap waiting 代表锁住的是一个索引,不是一个范围
- locks gap before rec:表示的是gap锁。
附2 mysql开启日志
默认情况下是没有输出sql日志的。
1 2 3 4 5 6 7 |
mysql> SHOW VARIABLES LIKE 'general%'; +------------------+-----------------------------------------+ | Variable_name | Value | +------------------+-----------------------------------------+ | general_log | OFF | | general_log_file | /usr/local/mysql/data/B000000064800.log | +------------------+-----------------------------------------+ |
开启日志
1 2 |
mysql> set GLOBAL general_log='ON'; Query OK, 0 rows affected (0.20 sec) |
此时general_log的属性为ON了,如下
1 2 3 4 5 6 7 |
mysql> SHOW VARIABLES LIKE 'general%'; +------------------+-----------------------------------------+ | Variable_name | Value | +------------------+-----------------------------------------+ | general_log | ON | | general_log_file | /usr/local/mysql/data/B000000064800.log | +------------------+-----------------------------------------+ |
然后查看日志/usr/local/mysql/data/B000000064800.log 如下:
1 2 3 4 |
wuzhonghu@B000000064800:/usr/local/mysql$ tail -f data/B000000064800.log 180531 17:25:09 183 Query select * from student where id=6 180531 17:25:16 183 Query begin 180531 17:25:35 183 Query commit |
参考资料
1、sql语句对应的锁,官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
2、msyql锁,官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
2、mysql锁,翻译:https://blog.csdn.net/tbwood/article/details/79004523
3、Myslq Locks.官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
(全文完)