6.1 什么是锁?
6.2 lock 和 latch
展示 lock 信息:
1 show engine innodb status
2 information_schema 表中的
innodb_trx, // 事务信息
innodb_locks, // 锁信息
innodb_lock_waits // 锁等待
连接三个表, 查询锁等待信息: 待补
6.3 InnoDB 引擎中的锁
6.3.1 锁的类型
共享锁(S Lock): 允许事务读一行数据
排他锁(X Lock): 允许事务删除或更新一行数据
意向锁的两种类型 --
意向共享锁(IS Lock): 事务想要获得一张表中某几行的共享锁
意向排他锁(IX Lock): 事务想要获得一张表中某几行的排他锁
6.3.2 一致性非锁定读
//演示的例子有问题
指 InnoDB 存储引擎通过多版本控制(Multi Version Concurrency Control, MVCC)的方式读取当前执行时间数据库中行的数据。//读取数据的快照
之所以称其为非锁定读, 因为不需要等待访问的行上 X 锁的释放。
在 InnoDB 存储引擎的默认设置下(REPEATABLE READ), 这是默认的读取方式。
在 READ COMMITTED (总读快照最新)和 REPEATABLE READ (读事务开始时的)下, InnoDB 引擎使用非锁定的一致性读
6.3.3 一致性锁定读
在某些情况下, 用户需要显示地对数据库读取操作进行加锁以保证数据逻辑的一致性。这要求数据库支持加锁语句, 即使是对于 select 的只读操作。
select ... for update 加了 X 锁, 其他事务不能对其加任何锁。
select ... lock in share mode 加了 S 锁,其他事务只能对其加 S 锁, 若加 X 锁, 则会被阻塞。
以上两种加锁模式只能在一个事务中, 事务提交了, 锁也就被释放了。
6.3.4 自增长与锁
插入操作会依据自增长计数器(auto-increment counter)加 1 赋予自增长列, 这种实现方式称作Auto-Inc Locking
这种锁采用一种特殊的表锁机制, 为了提高插入的性能, 锁不是在一个事务完成后才释放, 而是在完成自增长值插入的 SQL 语句后立即释放。 // 可以车市下它的锁等待
innodb_autoinc_lock_mode ,控制自增长的模式, 该参数默认值为 1.
为 1 时: 对于 “simple inserts”, 该值会用互斥量(mutex)去对内存中的计数器进行累加的操作。对于“bulk inserts” 还是使用传统表锁的 auto-inc Locking 范式。需要注意的是, 如果已经使用 auto-inc locing 方式去产生自增长的值, 而这时需要再进行“simple inserts”的操作时, 还是需要等待 auto-inc locking 的释放。
6.3.5 外键和锁(待理解)
在 InnoDB 存储引擎中, 对于一个外键列, 如果梅雨显示地对这个列加索引, InnoDB 存储引擎自动对其加一个索引, 因为这样可以避免表锁。
对于外键值的插入或更新, 首先需要查询父表中的记录, 及 select 父表。 但对于父表的 select 操作, 不是使用一致性非锁定读的方式, 因为这样会发生数据不一致的问题, 因此使用的是 select ... lock in share mode 方式,即主动对父表加一个 S 锁,如果这时父表上已经加 X 锁, 自表上的操作会被阻塞。
两个 Session 中, 执行父表的 x 锁,未 commit 或 rollback 时,另一个 session 的修改操作会被阻塞;
6.4 锁的算法
6.4.1 行锁的 3 种算法
Record Lock : 单个行记录上的锁
Gap Lock : 间隙锁, 锁定一个范围, 但不含记录本身
Next-Key Lock: Gap Lock + Record Lock , 锁定一个范围, 并且锁定记录本身
6.4.2 解决 Phantom Problem
两个事务中,由于一个事务的修改操作, 导致另一个事务的两次查询操作(一般为范围查询,如 select * from t where a> 2), 结果不唯一。
// 例子没有操作成功, 会话 2 的 插入操作不成功, 尽管 tx_isolation 已经设置 了 read-commited
如果用户通过索引查询一个值, 并对该行加一个 S Lock, 那么即使查询的值不在, 其锁定的也是一个范围, 因此若没有返回任何行, 那么新插入的值一定是唯一的。
6.5 锁的问题
6.5.1 脏读
一个事务可以读到另一个事务中未提交的数据。
脏页指的是在缓存池中已经修改的页, 但是还没有刷新到磁盘中, 即数据库实例内存中的页和磁盘中的页的数据是不一致的, 当然在刷新到磁盘之前, 日志已经被写入到了重做日志文件中。 脏读数据是指事务对缓冲池中行记录的修改, 并且还没有被提交。
脏读发生条件是需要事务的隔离级别为 READ UNCOMMITTED, 而目前绝大部分的数据库至少设置成 READ COMMITTED.
6.5.2 不可重复读
不可重复读和脏读的区别是:脏读是读到未提交的数据, 而不可重复读读到的是已经提交的数据, 但是违反了数据库的事务一致性要求。
这种问题被定义为 Phantom Problem , 即 幻读问题。
6.5.3 丢失更新
丢失更新时另一个锁导致的问题, 简单来说就是一个事务的更新操作会被另一个事务的更新操作锁覆盖, 导致数据不一致。
6.6 阻塞
// 例子没有成功, 插入时有锁生成。 select * from t where a<4 for update
// 4 是表里的最大值, 4 到正无穷, 均被锁了, 所以该表中均不可插入。
innodb_lock_wait_timeout 用来控制等待的时间, 默认是 50 秒。
默认情况下, innodb 不会回滚超时引发的错误异常。
6.7 死锁
6.7.1 死锁的概念
死锁是指两个或两个以上的事务在执行过程中, 因为争夺资源而造成的一种互相等待的现象。 若无外力作用, 事务都将无法推进下去。
解决死锁问题最简单的一种方法是超时。
当前数据库普遍采用 wait-for graph 的方式来进行死锁检测。
6.7.2 死锁的概率
系统中任何一个事务发生死锁的概率 约等于 n^2 * r^4 / 4 * R^2
系统中事务的数量(n),数量越多发生死锁的概率越大。
每个事务操作的数量(r), 每个事务操作的数量越多, 发生死锁的概率越大。
操作事务的集合(R), 越小发生死锁概率越大。
注:每个操作从 R 行数据中取一条数据, 每行数据被取到的概率为 1 / R.
6.7.3 死锁的示例
InnoDB 存储引擎并不会回滚大部分的错误异常, 但是死锁除外。
InnoDB 引擎会自动对外键添加索引, 因而能够很好的避免死锁发生。 // 待理解
6.8 锁升级(待理解)
锁升级(Lock Escalation)是指将当前锁的粒度降低。如数据库把一个表的 1000 行锁升级为一个页锁, 或将页锁升级为表锁,来避免锁的开销。
InnoDB 引擎不存在锁的升级问题。 因为其不是根据每个记录来产生行锁的, 相反, 其根据每个事务访问的每个页对锁进行管理的,
采用位图的方式
。 因此不管一个事务锁住一个记录还是多个记录, 其开销通常都是一致的。