隔离级别、锁、脏读、不可重复读与幻读问题
一、三种并发问题对比
问题类型 | 定义 | 发生场景 | 示例 | 解决隔离级别 |
---|---|---|---|---|
脏读 | 读取到其他事务未提交的数据 | READ UNCOMMITTED | 事务B读取事务A未提交的修改,事务A回滚 | READ COMMITTED |
不可重复读 | 同一事务内多次读取同一数据结果不同 | READ COMMITTED | 事务A读取数据后,事务B修改并提交,事务A再次读取结果不同 | REPEATABLE READ |
幻读 | 同一事务内多次查询同一范围返回不同行数 | REPEATABLE READ | 事务A查询范围数据后,事务B插入新数据并提交,事务A再次查询发现”幻影行” | SERIALIZABLE |
二、详细说明与示例
1. 脏读 (Dirty Read)
定义:一个事务读取了另一个未提交事务修改过的数据。
发生条件:
- 隔离级别为READ UNCOMMITTED
- 一个事务修改数据但未提交
- 另一个事务读取了这个未提交的数据
示例:
1 | -- 事务A |
危害:
- 基于脏数据做出错误决策
- 数据一致性被破坏
2. 不可重复读 (Non-repeatable Read)
定义:同一事务内,多次读取同一数据返回不同结果(因为其他事务已提交修改)。
发生条件:
- 隔离级别为READ COMMITTED或更低
- 一个事务读取数据后
- 另一个事务修改该数据并提交
- 第一个事务再次读取同一数据
示例:
1 | -- 事务A |
与脏读的区别:
- 脏读读取的是未提交数据
- 不可重复读读取的是已提交的修改
危害:
- 同一事务内数据不一致
- 可能导致业务逻辑错误
3. 幻读 (Phantom Read)
定义:同一事务内,多次执行同一查询返回不同行数的结果(因为其他事务已提交新增/删除数据)。
发生条件:
- 隔离级别为REPEATABLE READ或更低
- 一个事务读取某个范围数据后
- 另一个事务在该范围插入/删除数据并提交
- 第一个事务再次读取同一范围
示例:
1 | -- 事务A |
与不可重复读的区别:
- 不可重复读针对的是已存在的行的值变化
- 幻读针对的是行数的变化(新增或删除的行)
三、MySQL各隔离级别对问题的解决
隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现机制 |
---|---|---|---|---|
READ UNCOMMITTED | ❌ 可能 | ❌ 可能 | ❌ 可能 | 无锁 |
READ COMMITTED | ✅ 解决 | ❌ 可能 | ❌ 可能 | 行锁(写时) |
REPEATABLE READ | ✅ 解决 | ✅ 解决 | ⚠️ 部分解决* | MVCC+间隙锁 |
SERIALIZABLE | ✅ 解决 | ✅ 解决 | ✅ 解决 | 完全串行化 |
*注:InnoDB在REPEATABLE READ级别通过间隙锁(Gap Lock)解决了大部分幻读问题,但某些特殊场景仍可能出现
四、实际应用建议
- 默认选择REPEATABLE READ:
- MySQL的默认级别
- 平衡了性能和数据一致性
- InnoDB的优化使其幻读问题很少出现
- 需要更高性能时考虑READ COMMITTED:
- 适用于对一致性要求不高的场景
- 比REPEATABLE READ并发性能更好
- 关键业务使用SERIALIZABLE:
- 金融、支付等绝对要求一致性的系统
- 注意性能代价和死锁风险
- 避免使用READ UNCOMMITTED:
- 除非是只读统计等特殊场景
- 数据不一致风险太大
MySQL 并发控制机制详解:行锁、MVCC 和间隙锁
MySQL 的 InnoDB 存储引擎通过多种机制实现并发控制,主要包括行级锁、多版本并发控制(MVCC)和间隙锁。这些机制共同工作,在保证数据一致性的同时提供良好的并发性能。
一、行锁 (Row-Level Locking)
基本概念
行锁是锁定索引记录(而非整张表)的锁机制,InnoDB 支持两种行锁:
- **共享锁(S锁)**:
- 又称读锁
SELECT ... LOCK IN SHARE MODE
显式获取- 多个事务可以同时持有同一行的共享锁
- **排他锁(X锁)**:
- 又称写锁
SELECT ... FOR UPDATE
、UPDATE
、DELETE
等操作自动获取- 一旦有事务持有某行的排他锁,其他事务不能获取该行的任何锁
行锁特点
- 锁定粒度小,并发度高
- 只锁定需要操作的记录,不影响其他记录
- 死锁概率比表锁高,但并发性能更好
行锁示例
1 | -- 事务1 |
二、多版本并发控制 (MVCC - Multi-Version Concurrency Control)
基本概念
MVCC 通过保存数据在某个时间点的快照来实现非锁定读,是 InnoDB 实现 READ COMMITTED 和 REPEATABLE READ 隔离级别的关键机制。
MVCC 核心组件
- 隐藏字段:
DB_TRX_ID
:记录最近修改该行的事务IDDB_ROLL_PTR
:回滚指针,指向undo日志DB_ROW_ID
:隐藏的自增行ID
- ReadView:
- 事务执行SQL时产生的一致性读视图
- 包含:活跃事务ID列表、最小活跃事务ID、下一个事务ID等
- undo日志:
- 记录数据被修改前的值
- 用于事务回滚和实现MVCC
MVCC 工作原理
- SELECT 操作:
- 只查找版本早于当前事务的数据
- 确保读取的是事务开始时的数据快照
- INSERT/DELETE/UPDATE 操作:
- 创建新版本数据
- 旧版本存入undo日志
MVCC 示例
1 | -- 事务1 (事务ID=100) |
三、间隙锁 (Gap Lock)
基本概念
间隙锁锁定索引记录之间的”间隙”,防止其他事务在范围内插入新记录,解决幻读问题。
间隙锁特点
- 只存在于REPEATABLE READ和SERIALIZABLE隔离级别
- 锁定的是索引记录之间的区间
- 可以防止幻读,但会降低并发性能
间隙锁类型
- **记录锁(Record Lock)**:锁定单个索引记录
- **间隙锁(Gap Lock)**:锁定索引记录之间的区间
- **临键锁(Next-Key Lock)**:记录锁+间隙锁的组合
间隙锁示例
1 | -- 表中有id为10,20,30的记录 |
四、三种机制的协同工作
- 写操作:
- 使用行锁(排他锁)锁定要修改的记录
- 必要时添加间隙锁防止幻读
- 创建新版本数据并记录undo日志
- 读操作:
- 普通SELECT使用MVCC实现非锁定读
- 锁定读(SELECT FOR UPDATE)使用行锁
- 范围查询在REPEATABLE READ下使用间隙锁
- 事务隔离级别影响:
- READ UNCOMMITTED:不使用MVCC,可能脏读
- READ COMMITTED:MVCC每次读创建新ReadView
- REPEATABLE READ:MVCC使用事务开始时的ReadView,加间隙锁
- SERIALIZABLE:所有SELECT转为SELECT FOR SHARE,使用更多锁
五、实际应用建议
- 合理设计索引:
- 行锁和间隙锁都依赖索引
- 无合适索引会导致锁表
- 控制事务大小:
- 大事务持有锁时间长,影响并发
- 将大事务拆分为小事务
- 避免不必要的锁定读:
- 只在必要时使用SELECT FOR UPDATE
- 优先使用普通SELECT+应用层校验
- 监控锁争用:
- 使用
SHOW ENGINE INNODB STATUS
查看锁情况 - 关注
innodb_row_lock_waits
等状态变量
理解这些并发控制机制有助于优化数据库性能,设计更高效的应用程序。
Copy-on-Write 是一种优化策略,只有在真正需要修改数据时才会创建副本,否则多个使用者共享同一份数据。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Min的博客!
评论