3. 数据库基础:关系型数据库 ACID 特性、常见数据库(MySQL、PostgreSQL)与缓存的关系
在构建缓存一致性方案(如 TCC 模式)之前,必须深刻理解关系型数据库的底层保障机制。数据库的 ACID 特性决定了事务的边界与可靠性,而不同数据库的实现差异直接影响缓存策略的设计。本章将拆解 ACID 的核心含义,对比 MySQL 与 PostgreSQL 在事务隔离与持久化上的关键区别,并建立数据库与缓存之间的协作模型。
3.1 ACID 特性:事务的四大基石
ACID 是关系型数据库事务正确执行的四个基本属性。任何缓存一致性方案,最终都要依赖数据库事务的原子性和持久性来兜底。
| 特性 | 全称 | 核心含义 | 缓存场景关联 |
|---|---|---|---|
| A | Atomicity(原子性) | 事务内的所有操作要么全部成功,要么全部回滚 | TCC 的 Confirm/Cancel 必须依赖原子性保证业务状态不部分更新 |
| C | Consistency(一致性) | 事务执行前后,数据库完整性约束不被破坏 | 缓存数据必须与数据库满足最终一致性,不能出现违反业务规则的数据 |
| I | Isolation(隔离性) | 并发事务之间互不干扰,隔离级别控制可见性 | 隔离级别影响缓存读取到的数据版本,脏读/不可重复读会破坏缓存一致性 |
| D | Durability(持久性) | 已提交的事务结果永久保存,不因故障丢失 | 缓存失效后,持久化数据是重建缓存的唯一可靠来源 |
关键认知: 缓存一致性方案不能替代数据库的 ACID 保障,而是建立在 ACID 之上的“最终一致性”协调层。数据库的持久性是缓存恢复的底线,隔离性决定了缓存何时能看到最新数据。
3.2 隔离级别与缓存可见性
隔离级别直接决定了事务之间数据的可见范围。在缓存场景中,如果数据库隔离级别过低,缓存可能读到“脏数据”或“不可重复读”的数据版本,导致缓存与数据库长期不一致。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 缓存风险 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 缓存可能读到未提交的脏数据,极高风险 |
| READ COMMITTED | 避免 | 可能 | 可能 | 缓存可能读到已提交但随后被回滚的数据(间隙问题) |
| REPEATABLE READ | 避免 | 避免 | 可能(MySQL InnoDB 通过间隙锁避免) | 缓存一致性较好,但快照读可能导致缓存延迟更新 |
| SERIALIZABLE | 避免 | 避免 | 避免 | 缓存一致性最强,但并发性能极低,不适合高吞吐 |
实践建议: 大多数缓存一致性方案选择 READ COMMITTED 或 REPEATABLE READ 作为数据库隔离级别。READ COMMITTED 配合乐观锁(版本号)可以兼顾性能与一致性;REPEATABLE READ 适合对一致性要求更高的金融类场景。
3.3 MySQL 与 PostgreSQL:事务实现差异
MySQL(InnoDB)和 PostgreSQL 是当前最主流的两款关系型数据库,它们在事务实现、MVCC 机制和持久化策略上存在显著差异,这些差异直接影响缓存一致性方案的设计选择。
| 对比维度 | MySQL (InnoDB) | PostgreSQL |
|---|---|---|
| MVCC 实现 | 基于回滚段(undo log)+ 聚簇索引中的隐藏列(DB_TRX_ID, DB_ROLL_PTR) | 基于堆表 + 可见性映射(Visibility Map)+ 事务快照(Snapshot) |
| 默认隔离级别 | REPEATABLE READ(但通过间隙锁避免幻读) | READ COMMITTED |
| 事务快照 | 语句级快照(REPEATABLE READ 下为事务级快照) | 事务级快照(每个事务开始时获取全局快照) |
| 持久化机制 | 双写缓冲(Doublewrite Buffer)+ Redo Log + Binlog | WAL(Write-Ahead Log)+ Full Page Writes |
| 主从复制 | 基于 Binlog(Row/Statement/Mixed) | 基于 WAL 的流复制(Streaming Replication) |
| 锁机制 | 行锁 + 间隙锁(Gap Lock)+ 临键锁(Next-Key Lock) | 行锁 + 谓词锁(Predicate Lock)+ 轻量级锁 |
缓存设计注意: MySQL 的 REPEATABLE READ 下,快照读不会看到其他事务已提交的最新数据,这可能导致缓存更新滞后。PostgreSQL 的 READ COMMITTED 下,每次查询都能看到最新已提交数据,更适合缓存实时性要求高的场景,但需要额外处理不可重复读问题。
3.4 数据库与缓存的协作模型
数据库与缓存不是替代关系,而是分层协作关系。数据库负责持久化与事务保障,缓存负责加速读取与减轻数据库压力。理解两者的协作模型,是设计缓存一致性方案的前提。
3.5 缓存一致性方案对数据库的依赖
不同的缓存一致性方案对数据库能力的要求不同。下表总结了常见方案与数据库特性的依赖关系:
| 缓存一致性方案 | 依赖的数据库特性 | MySQL 支持度 | PostgreSQL 支持度 |
|---|---|---|---|
| Cache-Aside(旁路缓存) | 事务原子性(避免部分更新) | ✅ 良好 | ✅ 良好 |
| Read-Through / Write-Through | 事务隔离性(避免脏读) | ✅ 良好 | ✅ 良好 |
| Write-Behind(异步回写) | 持久性 + 主从复制(数据不丢) | ✅ 需配置双1参数 | ✅ 需配置同步复制 |
| Binlog 订阅(Canal / Debezium) | 行级变更日志(Row-Based Binlog / WAL) | ✅ 原生支持 Row 格式 | ✅ 原生支持逻辑解码 |
| TCC 模式(本课程核心) | 原子性 + 隔离性 + 持久性 | ✅ 需配合事务表 + 幂等性设计 | ✅ 需配合 SAVEPOINT 或子事务 |
TCC 模式对数据库的特殊要求: TCC 的 Try、Confirm、Cancel 三个阶段都需要数据库事务的原子性来保证状态不丢失。同时,隔离性决定了 Try 阶段预留的资源对其他事务是否可见。MySQL 的 REPEATABLE READ 配合间隙锁可以防止幻读,但会降低并发;PostgreSQL 的 READ COMMITTED 配合行锁更适合高并发 TCC 场景。
3.6 数据库选型对缓存策略的影响
选择 MySQL 还是 PostgreSQL,不仅影响数据库层的设计,还会对缓存一致性策略产生连锁反应:
- MySQL 场景: 适合读多写少、对缓存实时性要求不极端、依赖 Binlog 订阅做缓存更新的业务。MySQL 的 REPEATABLE READ 默认隔离级别下,缓存更新需要额外处理快照读延迟问题。
- PostgreSQL 场景: 适合写密集、对缓存实时性要求高、需要复杂事务嵌套(如 TCC 子事务)的业务。PostgreSQL 的 READ COMMITTED 默认隔离级别让缓存更容易读到最新数据,但需要处理不可重复读带来的缓存版本跳跃。
- 混合场景: 部分大型系统采用 MySQL 做核心交易库,PostgreSQL 做分析库或配置库,缓存层统一对接 Redis,通过不同的同步策略适配两种数据库的事务特性。
选型建议: 如果团队对 MySQL 运维经验丰富,优先选择 MySQL + Binlog 订阅方案;如果团队追求更强的事务一致性和更灵活的并发控制,PostgreSQL 是更好的选择。TCC 模式在两种数据库上都能实现,但 PostgreSQL 的 SAVEPOINT 机制让 Cancel 阶段的回滚更优雅。