MySQL 2:锁 | 青训营笔记

115 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天。

lock 的对象是事务,用来锁定数据库中的对象。如:表、页、行。一般 lock 的对象仅在事务 commit 或 rollback 后释放。有死锁检测机制。
latch 称为闩锁(轻量级的锁),它要求锁定时间必须非常短,否则性能会很差。latch 分为 mutex (互斥锁) 和 rwlock (读写锁)。没有死锁检测机制。

1. 锁的分类

读写锁

  • 共享锁 (S Lock):允许事务读一行数据。
  • 排它锁 (X Lock):允许事务删除或更新一行数据。 X 锁与任何锁都不兼容,S 锁之间兼容。S 和 X 都是行锁。

意向锁

  • 意向共享锁 (IS Lock):事务想要获得一张表中某几行的共享锁。
  • 意向排它锁 (IX Lock):事务想要获得一张表中某几行的排它锁 。 意向锁之间相互兼容,IS 与 S 兼容

一致性非锁定读
如果读取的行正在被修改或删除, InnoDB 存储引擎会去读取行的一个之前版本的快照数据(undo段)。

  • Read Committed 事务隔离级别:总是读取行的最新版本(每次读取数据前都生成一个 ReadView)。
  • Repeatable Read 事务隔离级别:总是读取事务开始时的版本(只在第一次读取数据时生成一个 ReadView)。

一致性锁定读

  • select ... for update:加 X 锁,其他事务不能对该行加任何锁。
  • select ... lock in share mode:加 S 锁,其他事务只能加 S 锁,加 X 锁会被阻塞。

自增长与锁

  • INC Locking 锁是一种特殊的表锁,锁是在完成了自增长值的插入后被释放。

外键和锁 对于一个外键列,如果没有显示的给这个列加索引,InnoDB 引擎会自动加一个索引。这样做可以避免表锁。

2. 封锁协议

2.1. 三级封锁协议

一级封锁协议 事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。可以解决丢失修改问题。

二级封锁协议 在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。可以解决读脏数据问题。

三级封锁协议 在一级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。可以解决不可重复读的问题。

丢失修改脏读不可重复读
一级封锁协议YY
一级封锁协议Y
三级封锁协议

2.2. 两段锁协议

加锁和解锁分为两个阶段进行。当执行完最后一个加锁操作后,才能开始执行解锁操作。

事务遵循两段锁协议是保证可串行化调度的充分不必要条件。满足两段锁协议一定可以可串行化调度;可串行化调度不一定满足两段锁协议。

3. 行锁的三种算法

《MySQL 技术内幕 InnoDB 存储引擎》-> 6.4 锁的算法
MySQL的锁机制 - 记录锁、间隙锁、临键锁·知乎 zhuanlan.zhihu.com/p/48269420

Phantom Problem 幻想问题 Phantom Problem 是指在同一事务中,连续执行两次同样的 SQL 语句可能导致不同的结果,第二次的 SQL 语句可能会返回之前不存在的行。

3.1. Record Lock

单个行记录上的锁。
Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定。

A record lock is a lock on an index record. Record locks always lock index records, even if a table is defined with no indexes. For such cases, InnoDB creates a hidden clustered index and uses this index for record locking.

3.2. Gap Lock

间隙锁,锁定一个范围,但不包括记录本身。

显示地关闭 Gap Lock:

  1. 将事务的隔离级别设置为 Read Committed
  2. 将参数 innodb_locks_unsafe_for_binlog 设置为 1

3.3. Next-Key Lock

Record Lock + Gap Lock,锁定一个范围,并且锁定记录本身。

Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。 MVCC 不能解决幻影读问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。

查询列是唯一索引时,Next-Key Lock 降级为 Record Lock。

4. 阻塞

阻塞:有时一个事务的锁需要等待另一个事务的锁释放它所占用的资源。

InnoDB 存储引擎中参数 innodb_lock_wait_timeout 用来控制等待时间(默认 50 秒);参数 innodb_rollbck_on_timeout 用来设定是否在 等待超时时对进行中的事务进行回滚操作(默认 OFF 不回滚)。

innodb_lock_wait_timeout 是动态的,可以在数据库运行时调整; innodb_rollbck_on_timeout 是静态的,不可以在启动时修改。

必须在事务抛出异常时,进行 commit 或者 rollback,否则会十分危险。

5. 死锁

死锁:两个或者两个以上的事务在执行过程中,因为争夺锁而造成的一种相互等待的现象。若无外力作用,事务将无法推进下去。
线程死锁:两个或者两个以上的线程相互等待对方无法释放的资源,这种等待在无外力的作用下不可接触。

解决死锁:

  1. 超时法:当等待时间超过某个值时,一个事务回滚,另一个事务继续执行。
  2. 等待图法 wait-for graph: 数据库需要保存以下两种信息:
    • 锁的信息链表
    • 事务等待链表
      通过上述链表可以构成一张图,若图中存在回路,就代表存在死锁。

6. 锁升级

将多个粒度较小的锁升级为粒度更大的锁。
InnoDB 存储引擎不存在锁升级的问题。