06 · 开发 · 约束

请求号没有唯一约束,重试就会插进第二行

数据库工程师设计表、约束和迁移,让单据号和流水挡得住重复,而不是靠程序记得去查。生产上的慢查询和杀会话,交给数据库运维。

数据库结构笔记

数据库工程师用约束保住单号和余额

后端工程师决定这笔业务写哪些表,数据库工程师决定这些表怎样拒绝脏数据。岗位特点是把关键不变量放进数据库,而不是只放进某次代码评审。同一请求号不能有两行流水,同一单据不能有两个外部单号,已关闭的单不能再被更新成草稿。这些用唯一约束和外键或状态检查来挡。只靠应用层先查再插,两个并发请求会同时通过查询。

他参与迁移脚本。加列、回填、建索引要能在生产的数据量上跑完,并有回退。手工在生产改表不算交付。索引针对真实筛选:仓库加状态加日期,而不是把每个字段都索引一遍。每个字段都索引会让收货写入变慢,月台会感觉系统卡,然后连点。

事务和锁

他和后端一起看事务边界。长事务里调用外部接口,会把锁留到超时。数据库工程师指出哪些更新必须短,哪些日志可以异步。死锁如果出现在收货高峰,他要能从等待的语句定位到具体单据,而不是重启数据库。重启会把半成品请求变成未知状态,集成那边最怕这个。

权限上,应用账号不能拥有删表和随意导出全库的权利。报表走只读账号。人用个人账号,到期可回收。共用的管理员口令在这个岗位上是缺陷,不是方便。

余额被覆盖的那次

有一个实现用「读出余额、加上数量、写回余额」。并发两笔收货互相覆盖,少记了一笔。数据库工程师改为插入不可变流水,余额由流水汇总或由短事务原子更新,并给请求号加唯一约束。历史错账按流水重算,不按最后一次覆盖的余额。之后并发脚本两笔同时提交,结果是两行流水而不是一行。

上线脚本在预发库用生产量级的拷贝跑过,时间写进发布说明。数据库运维按这个时间安排窗口,而不是上线当天才发现回填要跑四个小时。数据库工程师还会规定归档。关闭超过约定期限的流水可以挪到历史表,但唯一约束的请求号在归档表里也要保留,避免旧请求号被新交易重新使用。请求号一旦复用,幂等就会把两笔相隔很久的收货当成同一笔。归档脚本和在线约束一起评审,不在磁盘告警的当天才临时删行。批量回填必须分批,并记录已经处理到哪一主键。中途失败可以从断点继续,不是把整张表锁住重跑。锁表重跑会把收货挡在月台上。统计类字段如果是冗余,他写清由哪个事件维护,禁止人工直接改。

人工改过的汇总一旦和流水不一致,后面所有报表都会被迫选择相信哪一个数。上线后他要盯的不是自动增长和碎片这类技术项,而是有人绕过事务直接修数。修数必须有脚本、备份点、影响行数和复核人,四样缺一样就退回。退回不是因为流程好看,而是因为收货流水一旦被手改,幂等和回写都会失去参照。第二天再靠记忆解释改了哪几行,已经晚了。

约束先于自觉,这是数据库工程师留给开发的那句话。

回到时间线

哪一个数绝对不能出现两行

数据库工程师用约束把它挡住。