MySQL 事务与锁机制
事务的四大特性(ACID)
| 特性 | 描述 | 实现方式 |
|---|---|---|
| 原子性(Atomicity) | 事务是不可分割的最小单位 | Undo Log |
| 一致性(Consistency) | 事务前后数据保持一致 | 由其他三个特性保证 |
| 隔离性(Isolation) | 事务之间互不干扰 | MVCC + 锁 |
| 持久性(Durability) | 事务提交后永久保存 | Redo Log |
事务隔离级别
并发事务问题
| 问题 | 描述 |
|---|---|
| 脏读 | 读取到其他事务未提交的数据 |
| 不可重复读 | 同一事务中,两次读取同一数据结果不同(被其他事务修改) |
| 幻读 | 同一事务中,两次查询的记录数不同(被其他事务插入/删除) |
四种隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ |
| READ COMMITTED | ❌ | ✅ | ✅ |
| REPEATABLE READ(MySQL 默认) | ❌ | ❌ | ⚠️ 部分解决 |
| SERIALIZABLE | ❌ | ❌ | ❌ |
sql
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;MVCC 多版本并发控制
MVCC(Multi-Version Concurrency Control)通过保存数据的历史版本,实现读写不冲突。
核心组件
隐藏字段:每行数据有两个隐藏列
DB_TRX_ID:最后修改该行的事务 IDDB_ROLL_PTR:指向 Undo Log 的指针
Undo Log:记录数据的历史版本,形成版本链
Read View:事务开始时生成的快照,决定能看到哪些版本
Read View 工作原理
Read View 包含:
- m_ids:当前活跃的事务 ID 列表
- min_trx_id:最小活跃事务 ID
- max_trx_id:下一个将要分配的事务 ID
- creator_trx_id:创建该 Read View 的事务 ID可见性判断规则:
trx_id == creator_trx_id→ 可见(自己修改的)trx_id < min_trx_id→ 可见(事务已提交)trx_id >= max_trx_id→ 不可见(事务在 Read View 之后开启)trx_id in m_ids→ 不可见(事务未提交)
锁机制
锁的分类
按粒度分:
├── 表级锁
│ ├── 表锁(Table Lock)
│ ├── 元数据锁(MDL)
│ └── 意向锁(Intention Lock)
└── 行级锁
├── 记录锁(Record Lock)
├── 间隙锁(Gap Lock)
└── 临键锁(Next-Key Lock)行级锁详解
1. 记录锁(Record Lock)
锁定单条记录。
sql
-- 对 id=1 的记录加锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;2. 间隙锁(Gap Lock)
锁定索引记录之间的间隙,防止幻读。
sql
-- 假设表中有 id: 1, 5, 10
-- 以下语句会锁住 (5, 10) 这个间隙
SELECT * FROM user WHERE id > 5 AND id < 10 FOR UPDATE;3. 临键锁(Next-Key Lock)
记录锁 + 间隙锁的组合,是 InnoDB 默认的行锁算法。
sql
-- 锁定 (5, 10] 区间
SELECT * FROM user WHERE id = 10 FOR UPDATE;死锁
当两个或多个事务互相等待对方释放锁时,就会发生死锁。
sql
-- 事务 A
BEGIN;
UPDATE user SET name = 'A' WHERE id = 1; -- 持有 id=1 的锁
UPDATE user SET name = 'A' WHERE id = 2; -- 等待 id=2 的锁
-- 事务 B
BEGIN;
UPDATE user SET name = 'B' WHERE id = 2; -- 持有 id=2 的锁
UPDATE user SET name = 'B' WHERE id = 1; -- 等待 id=1 的锁 → 死锁!解决方案:
- InnoDB 自动检测死锁,回滚较小的事务
- 按固定顺序访问资源
- 设置锁等待超时:
innodb_lock_wait_timeout
