数据库一致性不是“用了事务就不会出错”。事务保证一组数据库操作的边界,锁和条件更新解决并发冲突,复制负责可用性与读扩展;它们解决的是不同问题。
ACID 落到业务里是什么
| 特性 | 扣库存场景中的含义 |
|---|---|
| 原子性 | 创建订单和扣库存要么都成功,要么都回滚 |
| 一致性 | 库存不能被业务操作变成负数 |
| 隔离性 | 并发事务不应读到不允许看见的中间状态 |
| 持久性 | 提交成功的数据在故障恢复后仍可找回 |
事务只覆盖同一个数据库事务中的操作。发短信、调用支付接口或向另一个数据库写入,不能靠本地事务自动回滚。
最直接的原子扣减
1 | UPDATE products |
应用必须检查受影响行数。为 0 表示商品不存在或库存不足。这种条件更新通常比“先查库存再更新”更安全,因为判断和修改在一条 SQL 中完成。
悲观锁与乐观锁
悲观锁先锁住目标行:
1 | START TRANSACTION; |
乐观锁用版本号判断数据是否已变化:
1 | UPDATE products |
冲突少且允许重试时,乐观锁更轻;冲突频繁且必须串行修改时,悲观锁更直观。两者都需要明确失败后的业务处理。
死锁为什么发生
事务 A 先锁商品 1 再等商品 2,事务 B 先锁商品 2 再等商品 1,就形成循环等待。常用预防方式:
- 按固定顺序访问资源。
- 缩短事务,不在持锁期间请求远程接口。
- 给查询条件建立合适索引,减少锁定范围。
- 捕获死锁错误并做有上限的重试。
主从复制解决什么
1 | 应用写主库 -> 主库记录 Binlog -> 从库拉取并重放 -> 应用读取从库 |
异步复制存在延迟,因此“刚写完立刻读从库”可能读不到新数据。关键读可以短时间走主库,或者通过一致性标记、复制位点等机制确认从库已经追上。
主库故障后的切换
1 | 监控确认主库不可用 |
自动切换不能消除数据丢失风险。是否允许丢失最后几秒写入,取决于复制模式、故障时副本进度和业务恢复目标。