InnoDB 存储引擎的锁学习

本文主要是针对《Mysql技术内幕:InnoDB 存储引擎》一书中第六章关于 InnoDB 存储引擎中锁的学习总结。mysql

锁是数据库系统区别于文件系统的一个关键特性,为了支持对共享资源的并发访问,提供数据的完整性和一致性,咱们必须为数据加锁,InnoDB 提供了一致性非锁定读、行级锁的支持。算法

共享锁(S Lock)和排他锁(X Lock)

  • 共享锁就是多个事务对于同一数据能够共享一把锁,都能访问到数据,可是只能读不能修改
  • 排他锁是容许当前事务修改和删除数据,当一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁
  • InnoDB 支持多粒度锁,既支持在表粒度上 S 锁和 X 锁,也支持在行粒度上加 S 锁 和 X 锁
  • 锁的兼容性是指是否能够同时给某个数据加两个锁,S 锁和 X 锁的兼容性以下:
锁类型 S 锁 X 锁
S 锁 兼容 不兼容
X 锁 兼容 不兼容

意向锁

  • 意向锁是一种表级锁
  • 用户要获取某个表的行锁,必须先获取该表的意向锁
  • 意向锁是 InnoDB 本身维护的,用户没法手动操做意向锁
  • 意向锁分为意向共享锁(IS)和意向排他锁(IX)
  • 意向锁和意向锁之间相互兼容,意向锁和行级锁也相互兼容
  • 意向锁和表锁的兼容性以下:
锁类型 IS 锁 IX 锁
表 S 锁 兼容 不兼容
表 X 锁 兼容 不兼容
  • 若是事务想对某个表加锁,那么先会检查是否与该表的意向锁相兼容,若是不兼容则不能加表级锁,而不须要检查是否与该表中行级锁的兼容性
  • 意向锁在保证并发性的前提下,实现了行锁和表锁共存且知足事务隔离性的要求

一致性非锁定读

  • 一致性非锁定读是基于多版本并发控制(MVCC)技术实现的
  • 多版本并发控制(MVCC) 技术是指一个行记录可能有不止一份快照数据,而 MVCC 是经过事务的 undo 日志实现的
  • 一致性非锁定读是 InnoDB 默认的读取方式,即读取数据时不占用和等待表上的锁
  • 一致性非锁定读只有在隔离级别为 REPEATABLE READ 和 READ COMMITTED 下才会支持
  • 当事务的隔离级别为 REPEATABLE READ 时,同一个事务中的一致性读都是读取的是该事务下第一次查询所创建的快照
  • 当事务隔离级别为 READ COMMITTED 时,同一事务下每次查询都是读的最新一份数据快照,因此可能会违背数据库的隔离性,由于同一份事务里可能读取到两份不一样的数据
  • 一致性非锁定读能够看到该时间点以前提交的事务所作的更改而且不会被以后的修改或者未提交事务所影响

一致性锁定读

在数据库的默认隔离级别为 REPEATABLE READ 下,Select 查询默认为一致性非锁定读,若是想要使用一致性锁定读,须要显示给 Select 查询加锁sql

Select ... FOR UPDATE          - 给读取的行加上 X 锁
Select ... LOCK IN SHARE MODE  - 给读取的行加上 S 锁
复制代码

自增加和锁

  • 由于主键索引是有序的,咱们在主键上设置自增属性,能够有效的减小索引页的分裂和数据的移动。
  • 使用关键字 AUTO_INCREMENT 给主键加自增加特性:
CREATE TABLE `test` (
  `id` int(1) NOT NULL AUTO_INCREMENT,
  `name` varchar(8) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12;
复制代码
  • 对于自增加列的索引比较特殊,是用 AUTO-INC Locking 方式先的,是一种表锁机制
  • 自增加的列必须是索引,同时必须是索引的第一个列
  • 为了提升插入性能,锁并非在一个事务完成时才释放,而是在完成对自增加值的插入 SQL 后当即释放
  • 对于并发插入存在必定性能问题,必须等待前一个自增加插入完成才能继续插入,虽然不是等待整个事务结束
  • 从 MySQL5.1.22 版本开始,InnoDB 存储引擎引擎中提供了一种轻量级互斥量的自增加实现机制,这种机制大大提升了自增加值插入的性能,能够经过参数 innodb_autoinc_lock_mode 对自增加索引模式进行设置:
- 0:这是 MySQL5.1.22 版本以前自增加的实现方式,即经过表锁的 AUTO-INC Locking 方式 
- 1:这是该参数的默认值。对于 simple inserts 该值会用互斥量去对内存中的计数器进行累加的操做,
     对于 bulk inserts 仍是使用传统表锁的 AUTO-INC Locking方式
- 2:在这个模式下,对于全部的 insert-like 自增加的产生都是经过互斥量,
     而不是经过 AUTO-INC Locking 的方式,显然这时性能最高的方式。
     然而会带来必定的问题。由于并发插入的存在,在每次插入时,自增加的值可能不是连续的。
复制代码

外键和锁

  • 在InnoDB存储引擎中,对于一个外键列,若是没有显示地对这个列加索引,InnoDB存储引擎会自动对其加一个索引,由于这样能够避免表锁
  • 对于外键值的插入或更新,首先须要检查父表中的记录,即 SELECT 父表
  • 在外键检查对于父表的 SELECT 操做时,不是使用一致性非锁定读的方式,由于这会发生数据不一致的问题,InnoDB 会主动对父表加一个 S 锁

行锁算法

  • InnoDB 三种行锁算法:
- Record Lock(行锁):单个行记录上的锁,锁定单条索引记录。
- Gap Lock(间隙锁):锁定一个范围,但不包括记录自己
- Next-Key Lock(临键锁):是行锁和间隙锁的结合,锁定一个范围,而且锁定记录自己。
复制代码
  • 行锁在 InnoDB 中是基于索引实现的,因此一旦某个加锁操做没有使用索引,那么该锁就会退化为表锁
  • 对于行的等值查询,通常状况下都是用 Next-Key Lock,若是发现查询条件没有设置索引,则退化为表锁,若是发现查询条件是惟一索引,则升级为行锁
  • 在根据非惟一索引进行区间查询时,会使用间隙锁,使用间隙锁锁住的是一个区间,而不只仅是这个区间中的每一条数据:
- 此时全部在(1,10)区间内的记录行都会被锁住
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
复制代码
  • 每一个数据行上的非惟一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据
  • 假设数据库表中某个非惟一索引存在如下值 10,24,32,45,该表中 age 列潜在的临键锁有:(-∞, 10], (10, 24], (24, 32], (32, 45],(45, +∞]
  • 在根据非惟一索引对记录行进行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE 操做时,InnoDB 会获取该记录行的临键锁 ,并同时获取该记录行下一个区间的间隙锁
- 执行如下 SQL 时,InnoDB 会获取 24 对应的临键锁 (10, 24],并获取下一个区间的间隙锁 (24, 32),因此最终在 (10, 32) 区间加上间隙锁
SELECT * FROM table WHERE age = 24 FOR UPDATE;

- 以后若是在其它事务中执行如下命令,则该命令会被阻塞:
INSERT INTO table(age, name) VALUES(26, 'Ezreal');

复制代码
  • Gap Lock 是为了阻止事务将数据插入同一范围内,从而解决幻读现象,也就是两次读不一致状况
  • 用户能够经过如下两种方式关闭间隙锁,这种状况下除了外键约束和惟一性检查之外,其他状况都是用记录锁:
方式一:将事务的隔离级别设置成 READ COMMITTED
方式二:将参数 innodb_locks_unsafe_for_binlog 设置为 1
复制代码

锁问题

  • 脏读:
- 脏读是指在并发状况下,当前事务能够读到其它事务未提交到数据
- 脏读违反了数据库事务的隔离性要求
- InnoDB 中脏读的发生条件是事务的隔离级别为 READ UNCOMMITED
复制代码
  • 不可重复读:
- 不可重复读是指同一个事务内,屡次读取同一个数据集合结果不一致现象
- 不可重复读违反了数据库事务的一致性要求
- InnoDB 经过 next-key 算法来避免不可重复读现象
- 为了不不可重复读现象发生,须要将事务的隔离级别设置为 REPEATABLE READ 以上
复制代码
  • 丢失更新:
- 丢失更新是指一个事务的更新操做会被令一个事务的更新操做覆盖
- 在当前数据库任何隔离级别下,都不会致使数据库丢失更新现象发生
复制代码

阻塞和死锁

  • 阻塞是指一个事务中的锁须要等待另一个事务的锁释放资源才能继续执行
- 阻塞主要是为了确保事务的并发可以正常有序的执行
- 经过参数 innodb_lock_wait_timeout 来设置事务的超时等待时间,默认是 50 s
- 经过参数 innodb_rollback_on_timeout 来设置事务超时是否进行回滚,默认不进行回滚
- 超时状况下默认不进行回滚,须要业务来捕获超时异常主动进行回滚,不然会致使数据的不一致性发生

复制代码
  • 死锁是指两个及两个以上的事务为了争夺资源而形成的一种相互等待的现象
innoDB 解决死锁的两种方案:
- 事务超时回滚事务,这种方案的缺点是若是超时的事务占用的比重比较大,回滚很是耗时
- 采用 wait-for graphic(等待图) 来进行死锁检查,经过深度优先遍历算法主动检查死锁发生,释放回滚 undo 量最小的事务
复制代码
相关文章
相关标签/搜索