MySQL 的锁
1. 什么是锁
锁是数据库系统区别于文件系统的一个关键特性,用于管理对共享资源的并发访问
分类
-
粒度
-
表锁
- 整个表
-
行锁
-
某一行或多行
-
Record Lock:记录锁
- 一行
-
Gap Lock:间隙锁
- 左开右闭
- 查到的:1,4;实际锁住的:2,3,4
-
Next-Key Lock:临键锁
- 左闭右闭,记录锁 + 间隙锁
- 查到的:1,4;实际锁住的:1,2,3,4
-
-
-
页级锁
- 介于行锁和表锁之间
-
......
-
-
不同角度的说法
-
属性
- 共享锁
- 排他锁
-
状态
- 意向共享锁
- 意向排他锁
- 意向锁:加锁时设置一个标志位,其他事务只需要判断这个标志位,而不需要扫描每个节点判断是否加锁
-
逻辑层面
-
乐观锁
- 版本控制
-
悲观锁
-
-
作用
- 利用锁解决幻读问题
2. Lock 和 Latch
3. InnoDB 中的锁
3.1 锁的类型
-
共享锁(S Lock)
- 允许事务读一行数据
-
排他锁(X Lock)
- 允许事务更新或删除一行数据
3.2 一致性非锁定读
- 读取的记录正在执行 UPDATE 或 DELETE 操作,则会读取事务的快照数据 undo 段
3.3 一致性锁定读
-
SELECT ... FOR UPDATE
- 事务对读取的记录加 X 锁,其他事务不能再加任何锁
- RR 隔离级别下,总是读取事务开始时的结果
- RC 隔离级别下,总是读取最新的快照数据,破坏了隔离性
-
SELECT ... LOCK IN SHARE MODE
- 事务对读取的记录加 S 锁,其他事务可以加 S 锁,但 X 锁会被阻塞
4. 锁的算法
5. 锁问题
5.0 说明
-
存在快照读和当前读,快照读是记录版本,不加锁,当前读是最新版本,加锁
-
快照读
- 普通 select
-
当前读
- select ... lock in share mode; (共享锁)
- select ... for update;
- insert;
- update;
- delete;
-
5.1 赃读
- 数据可能同时存在内存和磁盘中,看读取的是哪一份
- 一个事务内读取到了另一个事务未提交的修改,破坏了隔离性
5.2 不可重复读
-
一个事务内读取到了另一个事务提交的数据,导致先后两次读取结果不一样,破坏了一致性
-
5.3 丢失更新(第二类)
-
事务 1 和事务 2 先后修改同一条数据,分别先后提交,先提交的会被后提交的覆盖掉
-
数据库理论上不会出现这种问题,因为事务会等待前面的事务提交完成,更多的是逻辑上的丢失更新
-
自己就遇到过类似的问题:邮件有一个是否读取字段,用户 1 给用户 2 发送了一个信函,用户 1 对邮件进行修改并更新数据库,但是用户 2 并没有获取数据库最新的版本,本地还是更新前的,此时读取邮件,调用更新接口,将是否读取字段设置为 true,同时信函内容又变回修改前的了
-
可以加悲观锁或乐观锁
- 悲观锁:select ... for update
- 乐观锁:由程序自己实现,判断版本号,比如更新时间,更新前先查询,拿到旧的更新时间,更新的时候再查询,更新时间一样的话就更新,否则通知用户刷新数据。
-
幻读
- 没有锁间隙
- 破坏了一致性
第一类丢失更新,(不会发生)
- 两个事务修改同一条记录,某一事务完成,另一个事务回滚,造成第一个事务更新丢失
- 现代的关系型数据库已经不会发生,排他锁
6. 阻塞
7. 死锁
7.1 什么是死锁
-
并行下,多个事务因竞争资源而产生的相互等待现象,若没有外力干涉将无法继续执行
-
解决方法
-
超时回滚
-
等待图
-
记录事务等待链表和锁链表
-
事务等待链表
- t1
t2
t3
t4
- t1
-
锁的信息列表
- row1
t2:x
t1:s - row2
t1:s
t4:s
t2:x
t3:x
- row1
-
调用关系
- t1->row1.t1:s
t2->row2:t2:x
t3->row2:t3:x
- t1->row1.t1:s
-
等待图
-
t1<--->t2 ↖ ^ × | ↙ | t4<--- t3
- 存在回路 t1,t2
- 回滚 undo 量最小的事务
-
-
-
判断是否存在回路
-
深度优先
-
-
7.2 死锁概率很小
7.3 死锁示例
- AB-BA 死锁
- MySQL 自动为外键添加索引,删除索引会报错,need a foreign key constraint
8. 锁升级
降低锁的粒度
- 一个表的 1000 行的行锁升级为页锁,页锁升级为表锁
- 目的:如果将锁看为一种稀有资源,降低锁的粒度是为了保护系统资源,一定程度上提高效率
- InnoDB 不存在锁升级问题,因为不是根据每条记录产生行锁的,而是根据事务访问的每页对锁进行管理,采用的是位图方式。不管锁住的是一页中的一条记录还是多条记录,开销通常都是一致的。