一、ACID 实现原理
-
原子性(Atomicity)
- 通过
undo log
实现:事务回滚时,逆向执行undo log
中的操作。 - 若事务失败,数据库会撤销所有未提交的修改。
- 通过
-
一致性(Consistency)
- 由应用层和数据库约束(如唯一索引、外键)共同保证。
- 事务执行前后数据库必须满足所有预定义的业务规则。
-
隔离性(Isolation)
- 通过锁机制或 MVCC(多版本并发控制)实现。
- 不同隔离级别对应不同锁策略(如
READ COMMITTED
使用行级锁)。
-
持久性(Durability)
- 依赖
redo log
:事务提交时,修改先写入redo log
,再异步刷盘。 - 崩溃恢复时通过
redo log
重放未持久化的操作。
- 依赖
二、隔离级别与问题
隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现方式 |
---|---|---|---|---|
READ UNCOMMITTED | ✔️ | ✔️ | ✔️ | 无锁 |
READ COMMITTED | ✖️ | ✔️ | ✔️ | 行级锁 + MVCC |
REPEATABLE READ | ✖️ | ✖️ | ✔️ | MVCC + 间隙锁(如 MySQL) |
SERIALIZABLE | ✖️ | ✖️ | ✖️ | 表级锁或严格串行化调度 |
三、MVCC 核心机制
-
版本链
- 每行数据包含隐藏字段:
DB_TRX_ID
(事务ID)、DB_ROLL_PTR
(回滚指针)。 - 通过
undo log
构建历史版本链。
- 每行数据包含隐藏字段:
-
ReadView
- 事务启动时生成活跃事务列表,用于判断数据可见性。
- 可见性规则 :
- 若
DB_TRX_ID
小于最小活跃事务ID → 可见 - 若
DB_TRX_ID
在活跃事务列表中 → 不可见 - 若
DB_TRX_ID
大于最大事务ID → 不可见
- 若
-
隔离级别差异
READ COMMITTED
:每次查询生成新ReadView
REPEATABLE READ
:事务首次查询时生成ReadView
四、悲观锁 vs 乐观锁
特性 | 悲观锁 | 乐观锁 |
---|---|---|
实现方式 | 数据库锁(如 SELECT ... FOR UPDATE ) |
版本号/时间戳 + CAS 机制 |
适用场景 | 高并发写冲突 | 低冲突场景 |
性能影响 | 锁竞争可能引发阻塞 | 无锁,但需处理重试逻辑 |
实现示例
-
悲观锁(MySQL)
sqlBEGIN; SELECT * FROM table WHERE id=1 FOR UPDATE; -- 加行锁 UPDATE table SET value=100 WHERE id=1; COMMIT;
-
乐观锁(Java)
java// 更新时检查版本号 UPDATE table SET value=100, version=version+1 WHERE id=1 AND version=old_version; // 若影响行数为0,则重试或抛异常
五、关键设计权衡
- 隔离级别选择 :
REPEATABLE READ
平衡了性能与一致性(如 MySQL 默认级别)。 - 锁粒度 :行级锁(
InnoDB
)比表级锁并发度高,但管理成本更高。 - MVCC 优势:读操作不阻塞写,提升并发性能。
实际选型需结合业务场景(如高频更新 vs 低频冲突)和数据库特性(如 MySQL 的间隙锁)。