事务隔离级别、外键约束、update/delete条件校验及缓存一致性四层机制共同保障数据一致性,任一缺失都可能导致线上故障。

事务隔离级别选错,SELECT 看不到刚 INSERT 的数据
不是代码写错了,是默认隔离级别在捣鬼。MySQL 默认 REPEATABLE READ,PostgreSQL 默认 READ COMMITTED,而 SQLite 默认不开启 WAL 模式时连真正的 MVCC 都没有。同一事务内 SELECT 看不到自己刚 INSERT 的行?大概率是开了自动提交(autocommit=True)但没意识到——那每次 INSERT 都是独立事务,还没等你 SELECT,它就已经提交又“消失”在快照里了。
实操建议:
- 确认驱动是否默认开启自动提交:Python 的
sqlite3默认关,pymysql默认开;Go 的database/sql默认关,但连接池可能重用已提交的连接状态 - 显式控制事务边界:用
BEGIN/COMMIT包住一组操作,别依赖隐式行为 - 读已提交场景下,避免在
READ UNCOMMITTED下做业务判断——脏读不是“快”,是不可靠
UPDATE ... WHERE 条件漏掉主键或唯一约束,导致意外更新多行
这是线上事故高频原因。比如想改某用户余额:UPDATE accounts SET balance = 100 WHERE user_id = 123,结果 user_id 字段没建索引,又或者传参时把 user_id 写成 id 导致条件恒真,整张表全被设成 100。
实操建议:
- 所有
UPDATE和DELETE必须带WHERE,且该条件字段必须有索引 + 唯一性保障(如主键、UNIQUE约束) - 开发期加一层防护:在 ORM 层或 DAO 方法里强制校验
WHERE子句是否包含主键字段,或用LIMIT 1(MySQL 支持,PostgreSQL 需用 CTE 或子查询模拟) - 上线前用
EXPLAIN看执行计划,确认type是const或eq_ref,而不是ALL
外键没开,级联更新/删除失效,应用层硬编码补逻辑
外键不是性能包袱,而是数据一致性的最低防线。关掉外键(比如 MySQL 的 FOREIGN_KEY_CHECKS=0),再用脚本批量导入数据,之后忘记恢复——后面所有 ON DELETE CASCADE 都形同虚设。更糟的是,应用层自己写“先删子表再删主表”的逻辑,结果并发时子表记录被其他请求插入,删主表就报错。
实操建议:
- 建表时明确声明外键并指定动作:
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,别等出问题再补 - 不要为“怕锁表”禁用外键——真正影响性能的是缺失索引,不是外键本身;确保外键列都有索引(MySQL 要求,PostgreSQL 推荐)
- 迁移工具如
gh-ost或pt-online-schema-change默认保留外键,但需手动检查生成的 DDL 是否含ADD CONSTRAINT
应用层缓存和数据库不一致,cache-aside 模式下删缓存失败就彻底失联
查缓存 → 缓存未命中 → 查 DB → 写缓存,这流程看着稳,但只要中间一步失败(比如 DB 查到了,缓存写入超时),下次读到的就是旧值。更危险的是“先删缓存 → 再更新 DB”:删缓存成功,DB 更新失败,缓存空了,DB 还是旧数据,下次读直接穿透到旧值。
实操建议:
- 删缓存操作必须和 DB 更新在同一事务中完成——做不到就用可靠消息队列兜底,比如更新 DB 后发一条
invalidate_user_123消息,消费者重试直到缓存删除成功 - 给缓存加短过期时间(如 60s),不追求强一致时,这是最简单有效的安全阀
- 避免用哈希 key 拼接业务字段(如
"user:" + str(user_id)),一旦字段类型变(int → string)、格式变(加前缀)、分库分表规则变,key 就对不上,缓存永远不命中也不报错
数据一致性不是靠单点机制保证的,是隔离级别、约束、事务边界、缓存策略四层叠加的结果。少一层,漏一个配置,就可能在某个并发路径上崩掉。细节藏在 WHERE 条件有没有索引、FOREIGN_KEY_CHECKS 是开是关、缓存 DEL 命令有没有重试逻辑里——不是不知道,是上线前没盯住那一行。










