一个业务动作更新多张表,如果中间异常,只恢复页面提示还不够,数据库可能已经留下部分状态。事务设计应从完整业务动作开始,而不是每条 SQL 各自提交。
事务提供一组操作的提交边界,隔离级别决定并发可观察行为。应用异常不必然触发数据库回滚,连接复用也可能保留事务状态。跨数据库或外部接口不在同一本地事务的原子范围内。
一、问题的范围与前提
数据库连接、事务、SQL 执行和数据结果应分别验证。一次连接成功不证明事务可写,也不证明原有查询在不同数据库上结果相同。应从实际业务入口取得证据,记录角色、数据库、事务范围与查询条件,再分析执行计划或连接池。
二、技术分析与方案取舍
一个事务如果先写主记录,再写明细,中间失败,应确保两部分按同一业务边界回滚。若中间还调用外部发送接口,数据库回滚不能撤销已经发送的内容。可把发送意图先和业务数据提交到本地,再由独立任务执行;任务保存回执与幂等身份。事务持续期间尽量避免长时间等待远端,因为持锁会影响其它请求。
测试要故意在不同位置失败,查看持久状态而不是只看异常有没有被捕获。
测试数据先覆盖正常输入、边界值和并发条件。精度、空值、时间和关联约束需要固定规则,更新语句应处于明确事务中。对于会产生写入的试验,使用独立测试环境并准备恢复方法;只读执行计划也应核对查询是否会调用带副作用的函数。
三、实现与排查的顺序
- 列出一次动作涉及的写入。
- 确定事务开始和提交位置。
- 在每个失败点检查回滚。
- 外部副作用使用可重试的独立记录。
按这个顺序保留中间结果,可以区分输入条件、处理过程和最终输出。前一阶段的条件不成立时,先解释对应错误,再继续后面的试验;单次返回成功不足以覆盖整条链路。
最小示例
1
2
3
4
BEGIN;
-- 在同一事务中执行经过核验的相关操作
-- 检查约束与预期结果
ROLLBACK;
这是事务边界示意,ROLLBACK 用于明确结束示例,不含生产写入。实际迁移和 DDL 是否可回滚取决于数据库和操作。外部消息或设备动作不属于该本地事务,需要单独设计确认与补偿。
四、验证和结果解释
查询结果可以按主键集合、字段值和关联关系核验,不宜仅比较总行数。性能记录应同时保留参数、数据规模、缓存状态与并发条件。错误时检查服务端实际连接和活动事务,避免只观察另一台实例或另一份会话。
| 需要验证的结果 | 保留的对照 | 不能由此推出的结论 |
|---|---|---|
| 中间失败不留下半完成数据 | 固定输入与配置、对应输出 | 一次成功代表所有条件成功 |
| 并发请求不覆盖合法结果 | 中断前后状态与错误阶段 | 没有报错代表结果正确 |
| 提交成功但回执丢失不会重复创建业务 | 恢复后的同条件复测 | 临时恢复代表根因已经消失 |
重试、刷新连接和改变数据库角色属于不同操作。异常后先确定处理结果,再决定重复执行;跨外部接口的业务还需要幂等和补偿。备份恢复与数据迁移必须演练,回到旧程序版本不自动恢复被修改的持久数据。
五、适用边界与结论
不能在持有数据库锁时无限等待网络接口。涉及消息或第三方动作时,需要幂等与补偿设计,单个事务装饰器不能提供全链路原子性。
这条路径把“列出一次动作涉及的写入”与“外部副作用使用可重试的独立记录”连在一起,中间保留可复查的条件。实施时优先确认“中间失败不留下半完成数据”,再检查“提交成功但回执丢失不会重复创建业务”;两者都满足,才能在当前样本与配置范围内评价方案。设备、数据或软件版本改变以后,相关假设需要重新验证。