停止在数据库事务上胡搞
A stray commit buried multiple levels deep cost me months
我花了数月制定规范,却在代码审查时惊觉:有人竟在事务装饰器之外手动调用了 commit()。这不是 ORM 或 SQL 参数化的问题,而是代码组织与抽象层的彻底崩塌。文章通过三个真实案例,揭示了隐藏在深层调用中的手动提交、静默写入以及因缺失事务导致的数据丢失。作者强调,程序员必须对代码质量负全责,数据库抽象层应独占事务与提交的控制权。通过 AST 分析、flake8 规则以及 LLM 辅助检查,可以强制杜绝此类低级错误。记住,原子性多写操作应封装在单一函数中,切勿将 DB 模型暴露给业务层。
数据库抽象层拥有提交和事务的控制权,其他一切做法都是在为错误找借口。
HN 评论区
10- Groxx
# transaction has been commented out
# with transaction():
db_models = DBAccess.fetch_records(ids)
db_models[0].yo_mama_fat = True
# request ends, data poofs into the ether
说真的,如果这既没有立即报错(因为没有开启事务,修改尝试会直接失败),也没有在 GC 时崩溃(如果存在某种延迟逻辑),那我会说这是一个极其糟糕的框架,它确实难辞其咎。“你可以修改连接了数据库的对象,有时它们会回写到数据库,有时则不会”——这种行为完全不合理。
(显然这样的框架大有人在,而且数量不少。但数量多绝不代表合理。)
> 2. 不要在数据库层内外传递 DB models。
没错,我用的“轻量级”ORM 越多,就越确信它们基本上在所有时候都是最佳选择。试图搞些魔法很可爱,但这玩意儿注定是由“辛辣的未获金属”(spicy unobtanium)构成的,迟早会因为某个简单的原因以极其复杂的方式在你脸上爆炸,而你会发现这原因无处不在,从此只能被迫永远处于 paranoid 状态。没必要活得这么累。
- Retr0id
> 不要把锅甩给框架
为什么不甩?我凭什么被允许在事务中途执行 db.commit()?
- magicalhippo
我觉得这篇文章很难读懂。直到我看到域名,一切都豁然开朗了……
看来我们的模式不太一样,并没有感受到作者提到的痛点。或者可能是语言/框架的问题,我不确定示例代码是用什么写的,但在我用过的框架里,给实体记录设置属性绝不会自动更新数据库。
当我们手动处理这些事情时,会确保像 `create_main_records` 这样的方法:如果当前不在事务中,就开启一个事务;只有当它自己开启了事务时,才会提交。这样我们就能放心地嵌套调用。虽然我们的数据库支持嵌套事务,但用这种模式我们根本感觉不到需要它。