MySQL技术内幕:Innodb存储引擎
(间隙锁目前理解的还不是很透彻,后面索引看完了再过来回顾一下间隙锁)
第六章 锁
一、Innodb存储引擎中的锁
1.锁是数据库区别于文件系统的一个关键特性,
2.两种标准的行级锁:
- 共享锁(S Lock):允许事务读一行数据
- 排它锁(X Lock):允许事务删除或更新一行数据
S锁与S锁兼容,与X锁不兼容:也就是如果事务t1获取了行r的共享锁,那么事务t2也想获取行r的共享锁是可以获取到的(这种情况成为锁兼容),但是如果事务t3想获取行r的排它锁是不能获取到的,必须释放t1、t2释放行r上的共享锁(这种情况成为锁不兼容)。
X锁与任何锁都不兼容。
S锁与X锁都是行锁,兼容指的是对于同一行(row)数据来说。
表:排它锁与共享锁的兼容性
X | S | |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
Innodb支持多粒度锁定,这种锁定允许事务在行级锁和表级锁同时存在,为了支持在不同粒度上进行加锁操作,Innodb支持一种额外的锁方式,叫做意向锁。
3.意向锁:
意向锁的含义,在innodb中如果对表加了意向锁,说明表中的行在加锁的状态,对任意行加锁前必须先对他的表加上意向锁,
意向锁分为:
- 意向共享锁:事务想获得一张表中某几行的共享锁
- 意向排它锁:事务想获得一张表中某几行的排它锁
由于innodb存储引擎支持的行级锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。故表级意向锁与表级锁的兼容性如表
IS | IX | S | X | |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
PS:上述表格中的IS、IX、S、X都表示的是表级锁,S、X并不是书上 所说的表级锁。
4.一致性非锁定读(和隔离级别有关)
如果读取的行正在执行delete或者update操作,这时读取操作不会因此去等待行上锁的释放,相反,Innodb会去读取行的一个快照数据。之所以成为非锁定读,是因为不需要等待行上的X锁释放。
快照数据:是指改行之前的版本的数据,该实现是通过undo段来完成的,而undo用来在事务回滚数据。因此快照本身并没有额外的开销。
因此一致性非锁定读提高了并发性,Innodb存储引擎是默认的读取方式,即读取不会占用和等待表上的锁,但是在不同的隔离级别下,读取的方式是不同的,并不是每个事物的隔离级别都采用一致性非锁定读的,并且即使采用一致性非锁定度读取的快照副本也是不一样的。
在事务的隔离级别Read Committed(读已提交)和Repeatable Read(可重复度)下,Innodb采用一致性非锁定读;Read Committed 下是读取被锁定行的最新一份的快照数据,而Repeatable Read下是读取事务开始时的行数据版本。
例子:Read Committed下:事务1开启查询id=1,事务二开启将id=1的值改变为了id=2;事务1再一次读id=1,获取的结果为Empty,因为读已提交是读取最新一份的快照数据。
Repeatable Read下:事务1开启查询id=1,事务二开启将id=1的值改变为了id=2;事务1再一次读id=1,获取的结果是id=1,因为可重复度是会读取事务开启时的快照副本的数据。
ps:查看当前的隔离级别:select @@tx_isolation;
更改事务的隔离级别:set [ global | session ] transaction isolation level Read uncommitted | Read committed | Repeatable | Serializable; (global | session :全局(已存在的不受影响)|当前窗口)
5.一致性锁定读
在某些情况下,用户需要显式地对数据库读取操作进行加锁来保证数据逻辑的一致性,这就要求数据库支持对读操作的加锁语句,即便是只读。Innodb存储引擎对于select语句支持两种一致性锁定读操作:
- Select … For Update
- Select … Lock In Share Mode
Select … for update 对读取的行记录加一个X锁,其他事物不能对已锁定的行加任何锁。
Select … Lock In Share Mode 对读取的行记录加一个S锁,其他事物可以向被锁定的行加S锁,但如果加X锁,则会被阻塞;
对于一致性非锁定读即使读取的行已经加了select … for update也是可以读取的,因为他读取的快照数据。
另外一致性锁定读Select … For Update、Select … Lock In Share Mode必须在事务中使用才有用,当事务提交了,锁也就被释放了。
6.外键和锁
外键主要用于完整的约束性检查,在Innodb中对于一个外键列,如果没有显式地创建这个列的索引,Innodb存储引擎就会对其加一个索引,因为这样可以避免表锁。
对于外键值的插入或更新,首先需要查询父表中的记录,即select父表。但是对于父表Select操作,不是使用的一致性非锁定读的方式,因为这样会导致数据的不一致的问题,因此这时使用的是Select … Lock In Share Mode 方式,即主动对父表加S锁,如果这时父表已经被上了X锁,子表的操作会被阻塞。
例子:假设父表id与子表pid关联,事务1删除父表中id=1的行不提交会对id=1的行加X锁,事务2要向子表中插入数据pid=1的数据,首先要Select父表id=1就会对id=1的行加S锁,X锁是排它锁所以Select操作被阻塞,所以子表插入被阻塞。
锁的算法
行锁的三种算法
- Record Lock:单个行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
- Next-Key Lock:Gap Lock+Record Lock,锁定一个范围并包含记录本身
Record Lock:总是会去锁住索引记录,如果Innodb存储引擎表在建立的时候没有设置任何一个索引,那么这时Innodb存储引擎会使用隐式地主键来进行锁定。
Next-Key Lock:是结合了Gap Lock 和 Record Lock的一种锁定算法,在Next-Key Lock算法下,Innodb对于行的查询都采用这种锁定算法,例如一个索引有10,11,13,和20四个值,那么该索引可能被Next-Key Locking的区间为
(负无穷 - 10 ]
(10 - 11]
(11 - 13]
(13 - 20]
(20 - 正无穷)
除了Next-key Locking 还有previous-key locking,使用previous-key locking锁住的区间为
(负无穷 - 10 )
[10 - 11)
[11 - 13)
[13 - 20)
[20 - 正无穷)
当查询的索引含有唯一属性时,Innodb存储引擎会对Next-Key locking进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。
锁引发的问题:
脏读:事务读取到了其他事物未提交的数据,违反了事务的隔离性。
不可重复读:事务多次读取到的数据不一样,事务1第二次读取的数据是其他事物DML操作后提交的数据,造成了同一个语句两次读取的数据不一致。(与脏读的区别是不可重复读读取的数据是事务提交后的,脏读读取的是事务未提交的数据)
幻读:在可重复度的隔离级别下:事务使用当前读(for update,直接读读取的是开启时的undo快照数据)后一次查询查到了前一次没有查到的行,造成了幻读现象,使用间隙锁解决。