锁的作用:
InnoDB 中锁的使用,一方面是为了提高 DB 的并发访问能力, 另外一方面是为了确保每个DB链接都能安全的读取和修改数据。
MyISAM 中使用的是表锁;SQL Server 也引入了行锁,但其行锁越多,占用的资源就越多;InnoDB 下的行锁不占用额外的资源,有更好的并发处理能力。
InnoDB 下的悲观锁:
锁的类型
-
行级锁:排他锁(x)、共享锁(s)。
-
页面锁:意向排他锁(ix)、意向共享锁(is)。
-
表级锁:意向排他锁(ix)、意向共享锁(is)。
为了支持在不同粒度上进行加锁操作,更好地控制表级锁和行级锁之间的冲突,提高并发性能,InnoDB 支持一种行锁外的意向锁(lntention Lock) 。意向锁是将锁定的对象分为多个层次,如下图所示:
在 InnoDB 中,当需要对记录 r 添加 X 锁时,首先需要对表、页添加 IX 锁,再对 r 添加 X 锁,若过程中出现冲突,那么加锁过程将被阻塞。
锁类型 | 意向排他锁 | 意向共享锁 | 排他锁 | 共享锁 |
---|---|---|---|---|
意向排他锁 | 冲突 | 冲突 | ||
意向共享锁 | 冲突 | |||
排他锁 | 冲突 | 冲突 | 冲突 | 冲突 |
共享锁 | 冲突 | 冲突 |
锁兼容性小总结:
- 排他锁与所有的锁都不兼容;
- 意向锁之间不会冲突;
- 意向排他锁(IX)与X 、S 锁不能共存。
一致性非锁定读(consistent nonlocking read)
CNR 是指 InnoDB 通过 MVCC 方式,读取不同版本的行数据,避免加锁的情景,提高 DB 的并发能力。
在事务隔离级别 READ COMMITTED 和 REPEATABLE READ 下, InnoDB 存储引擎使用CNR 。但两种隔离级别, 对快照数据的定义不相同。在 READ COMMITTED 级别下,总是 读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照。而在 REPEATABLE READ 级别下,总是读取事务开始时的行数据版本 (MVCC的 undo 隐藏字段来实现)。
更多细节内容参考《MySQL技术内幕InnoDB存储引擎》6.3.2
一致性锁定读(consistent locking read)
部分情况下,需要显式地对数据库读取操作进行加锁,以保证数据逻辑的一致性。InnoDB 支持两种一致性的锁定读操作:
- SELECT *** FOR UPDATE:对读取的行记录加一个x 锁
- SELECT•••LOCK IN SHARE MODE:对读取的行记录加一个S 锁
注意点:
-
MySQL InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果要加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务中不能获取到两种锁(即不能修改数据),但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
-
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且索引不能失效,否则都会从行锁升级为表锁。
锁的实现算法
InnoDB 存储引擎有 3 种行锁的算法, 其分别是
- Record Lock : 单个行记录上的锁
- Gap Lock : 间隙锁, 锁定一个范围, 但不包含记录本身
- Next-Key Lock : Gap Lock+Record Lock , 锁定一个范围, 并且锁定行记录本身。InnoDB 对于行的加锁查询(X锁或S锁)都是采用这种锁定算法。
UniqueKey的加锁
当 查询的列是完整的唯一索引列 的情况下, Next-Key Lock 可以降级为 Record Lock 。若查询的列是唯一索引列的一部分(某个字段或字段的前半部分),那么Next-Key Lock 不会被降级。
需要注意的是,Next-Key Lock 降级为 RecordLock 后,其他事务就可以对间隙中的某个位置进行加锁操作,可能会导致并发场景下的业务问题。
辅助索引的加锁
对于辅助索引,InnoDB 会对辅助索引的 当前 key 使用 Next-Key Lock,对辅助索引的下一区间 key 使用 Gap Lock(案例:《技术内幕InnoDB存储引擎 v2》P267),并将辅助索引 对应的聚簇索引 Key 上添加 Record Lock。
案例:
select * from table_test_gap_lock where b=5 for update
table_test_gap_lock:表中UK为a,Key为b
b=5对应的UK-a=3
对辅助索引 b=3 使用 Next-Key Lock,锁定列b的前后区间;且对聚簇索引 a=3 添加 Record Lock。
Next-Key Lock 加上 MVCC,就可以解决 Repeatable Read 下幻读的问题。更多 幻读问题参考
MVCC
作用:
Multi-Version Concurrency Control(多版本并发控制机制),应用在 Repeatable-Read 和 Read Committed 隔离级别下,用于控制并发读写冲突问题,也可以保证事务的隔离性和数据一致性。
实现:
MVCC 通过3个隐式字段(DB_ROW_ID、DB_ROLL_PTR、DB_TRX_ID)、Read View、undo log来实现。
MVCC 是一种乐观锁,基于版本号设计出了数据快照的概念,在每行记录后面保存了两个隐藏的列来实现。这两个列分别保存了行的创建版本号、行的过期或删除版本号。
每个事务开始前,都有确定了自己的版本号,用来和查询到的每行记录的版本号进行比较。
(这个描述取自《高性能MySQL-第三版》,但MySQL中不是这么简单的。)
隐藏字段:
每行数据后新增的3个隐藏字段,主要用于记录当前数据行操作的事务id(DB_TRX_ID)
Read View:
主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
undo log:
- 当事务回滚时用于将数据恢复到修改前的样子
- 另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 恢复之前版本的数据,以此实现非锁定读
参考资料:
《高性能MySQL-第三版》
《MySQL技术内幕InnoDB存储引擎》