MySQL InnoDB引擎锁机制主要有「共享锁Shared Lock」、「排他锁Exclusive Lock」,另外还有「意向锁Intention Lock」,从锁的粒度上来看我们主要关注表锁(Table Lock)、行锁(Row Lock)、以及通过意向锁实现更细粒度的锁。
锁,是操作系统中用于「管理对共享资源的并发访问」的机制。存在「临界资源」的「并发读写访问」时,为保证数据一致性,锁是其中的一种重要手段。
一、不同类型的锁
1.1 共享锁 Shared Lock
S锁,又称为「读锁Read Lock」,共享访问权限,或者说相互不会阻塞。
多个客户端在同一时刻可以同时读取同一个资源,而互不干扰。
1.2 排他锁 Exclusive Lock
X锁,又称为「写锁Write Lock」,特点是排他,一个写锁会阻塞其他的写锁和读锁。
这是出于安全的考虑,只有如此才能保证再给定时间内,只有一个用户执行写入,防止其他用户读取正在写入的统一资源。
1.3 意向锁 Intention Lock
上述两种锁对InnoDB而言,都是行锁级别的锁;而更细粒度的,可以允许事务同时在行级、表级加锁。
I锁,意向锁,又分为两类:意向共享锁(IS Lock)、意向排他锁(IX Lock),下面逐一详述。
意向锁,是在表级设置的一种锁,意在表名当前事务将在行级上增加什么类型的锁(S锁或X锁)
- 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁。
- 意向排他锁(IX Lock):事务想要获得一张表中某几行的排他锁。
通过命令show engine innodb status
查看存储引擎的锁请求情况,类似如下内容:TABLE LOCK table xxx trx id 10080 lock mode IX
意向锁的加锁规则:
- 事务在获取行级S锁之前,必须获取其对应表的IS或IX锁
- 事务在获取行级X锁之前,必须获取其对应表的IX锁
当事务申请锁和表、行已存在的锁兼容时,该锁被授权;否则,则锁等待。意向锁的主要目的是表明:存在请求正在或即将锁定此表的某行。意向锁除了全表请求(例如LOCK TABLES ... WRITE
)外,不阻止任何其他内容。
InnoDB 锁相关的表
infomation_schema
库中存在三张与锁有关的数据表,分别是:
- INNODB_TRX:事务信息表,记录事务完整信息
- 表结构:
INNODB_TRX.trx_requested_lock_id
:事务等待的锁id,也就是后文中INNODB_LOCKS.lock_id
INNODB_TRX.trx_state
:事务状态,Lock Wait
表示锁等待状态INNODB_TRX.trx_query
:事务执行SQL
- 表结构:
- INNODB_LOCKS:锁信息表,记录锁完整信息
INNODB_LOCKS.lock_id
:锁idINNODB_LOCKS.lock_mode
:锁模式,S锁、X锁INNODB_LOCKS.lock_type
:锁类型,行锁、表锁INNODB_LOCKS.lock_table
:锁定的表INNODB_LOCKS.lock_index/space/page
:锁定的索引、spaceId、页INNODB_LOCKS.lock_rec
:锁定行的数量INNODB_LOCKS.lock_data
:锁定记录的主键值,注意此值为非可信值
- INNODB_LOCK_WAITS:锁等待信息记录表,记录锁、事务的等待关系,可以直白看出哪些事务发生了锁等待。
INNODB_LOCK_WAITS.requesting_trx_id
:申请锁资源的事务IdINNODB_LOCK_WAITS.requesting_lock_id
:申请的锁IdINNODB_LOCK_WAITS.blocking_trx_id
:阻塞的事务IdINNODB_LOCK_WAITS.blocking_lock_id
:阻塞的锁Id- 也就是说,从
lock_waits
表可以看出,不同事务的锁等待情况,关联trx
和locks
表后就可以获取详细的事务、锁信息。
一致性非锁定读 VS 一致性锁定读
- 一致性非锁定读(consistent nonblocking read):通过「并发多版本控制」MVCC获取当前时间数据库中的行数据。由于获取版本机制的时间点不同,也就产生了不同的隔离级别。如
READ COMMITTED
总是读取行数据的最新版本,而REPEATABLE READ
则是取事务开始时的数据版本。这也就造就了二者的差异,前者多次读取时可取到其他事务已提交的数据,而后者在多次读取时总是保持行为一致。 - 一致性锁定读(locking read):为保证读操作中数据的一致性,需显式进行加锁来保证并发情况下的数据一致性。
select ... for update
:在读取行上加X锁,其他事务不能加任何锁select ... in share mode
:在读取行上加S锁,其他事务只能加S锁,如果加X锁将导致锁等待- 上述两种锁定读操作时,如果其他事务为
非锁定读
操作时,是可以正常读取的(因为非锁定读操作不会加任何锁) - 在进行锁定读操作时,必须显式通过
begin
、start transaction
或set autocommit=0
将多条SQL放在同一个事务中提交
行锁中算法
- Record Lock:对单个行记录上锁
- 通过聚簇索引或二级索引查找时,会在索引上加
Record Lock
- 通过
SHOW ENGINE INNODB STATUS
可以看到类型如下内容:
- 通过聚簇索引或二级索引查找时,会在索引上加
1 | RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table xxxx |
- Gap Lock:范围锁定
- 按照范围锁定索引记录
- 例如,
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
,为防止其他事务将值15插入到t.c1列中,无论该列中是否已有这样的值,该范围中所有现有值之间的间隙都会被锁定。 Gap Lock
是在性能和并发能力上的一种权衡方案,仅在部分事务隔离级别上采用SELECT * FROM child WHERE id = 100;
对这类id
具有唯一索引
的情况,只会明确加Record锁
,而不会加Gap锁
- 而对于无索引或无唯一索引情况时,会增加
Gap锁
- Next-Key Lock:Gap+Record锁
- 在唯一索引情况下会降级为RecordLock提高并发能力
Next-Key Lock
在锁定时会将下一个key区间也进行Gap Lock
,目的是为了避免幻读的情况- 所谓的
Next-Key
是指,范围区间划分是包含下一个值 - 如索引有10,11,13,20四个值时,可被
Next-Key Lock
的范围区间是(-∞, 10]
,(10, 11]
,(11, 13]
,(13, 20]
,(20, +∞]
五个区间 - 如插入新值12后,则
(10, 11]
,(11, 13]
裂变为(10, 11]
,(11, 12]
,(12, 13]
实例1:唯一索引时,Next-Key Lock降级为Record锁,仅锁定单行记录
1 | create table t(a int primary key); |
此时插入按唯一键a
插入时,只会锁定单条记录,如下表:
实例2:辅助索引(非唯一索引或主键索引)
1 | create table z(a int, b int, primary key(a), key(b)); |
执行SQLselect * from z where b=3 for update
时,锁处理为:
- 存在两个索引,需分别进行锁定
- 对于
a
聚簇索引,对a=5
进行RecordLock - 对于
b
辅助索引,按Next-Key
对区间(1,3]
加锁 - 同时,InnoDB还会对下一个键值加
GapLock
,即对区间(3,6]
加锁
其他会话的下列SQL将被阻塞(锁等待):
1 | select * from z where a=5 for update; -- a=5,是`a=5`的RecordLock锁定记录,锁等待 |
二、锁注意问题
- 脏读(Dirty Read)
- 详见之前文章:脏读、不可重复读、幻读
- 不可重复读
- 丢失更新
- 幻读(Phantom Problem)
Phantom Problem
幻读是指,同一个事务中,连续执行两次相同SQL可能出现不同的结果,第二次SQL会返回之前不存在的行。
三、死锁
死锁是指两个或两个以上的事务在执行过程等中,因争夺资源而造成的一种相互等待的现象。
按照《Operating.System.Concepts 操作系统概念》一书中死锁的必要条件:
- 互斥(Mutual exclusion,简称Mutex)
- 至少有一个资源处于非共享模式,即一次只能有一个进程使用
- 如另一进程申请此资源,则须等待该资源释放
- 占有并等待(Hold and wait)
- 一个进程必须占有至少一个资源
- 并等待另一资源
- 而该资源被其他进程占用
- 非抢占(No preemption)
- 资源不能被抢占
- 即资源只能在进程完成任务后自动释放
- 循环等待(Circular wait)
- 一组等待进程{P0, P1…Pn-1, Pn}
- P0等待资源P1占有,P1等待资源P2占有
- Pn-1等待资源Pn占有,Pn等待资源P0占有
- 循环等待,则形成环形结构
死锁预防、检测相关内容可详阅本书,或参考其他文献,本文不再详细展开。
死锁发生的四个条件缺一都无法导致死锁,所以死锁恢复的方法也就是打破其中一项条件,导致死锁解除。