事务是唯一可靠方式,不加事务的INSERT、UPDATE、DELETE永远不是原子的——哪怕只有一条语句,出错就停,前面改过的数据不会自动回滚;只有显式开启事务并配对使用BEGIN(或START TRANSACTION)和COMMIT/ROLLBACK,才能保证一组操作要么全成功、要么全不生效。

事务是唯一可靠方式
不加事务的 INSERT、UPDATE、DELETE 永远不是原子的——哪怕只有一条语句,出错就停,前面改过的数据不会自动回滚。只有显式开启事务并配对使用 BEGIN(或 START TRANSACTION)和 COMMIT/ROLLBACK,才能保证一组操作要么全成功、要么全不生效。
常见错误:以为“单条 SQL 天然原子”,结果在批量更新时漏写事务,导致部分行被修改后崩溃,留下脏数据。
- MySQL 默认 autocommit=1,每条语句独立提交,必须先
SET autocommit = 0或用BEGIN显式开启 - PostgreSQL / SQL Server / Oracle 默认不自动提交,但应用层(如 JDBC、psycopg2)常默认开启 autocommit,需手动关闭
- SQLite 在执行多语句时若没开事务,每条都立即生效,无法回滚
如何正确写一个原子操作块
关键不是语法多复杂,而是控制边界清晰:从 BEGIN 到 COMMIT 之间所有 DML 必须逻辑连贯、无外部依赖、不跨连接。
示例(MySQL):
START TRANSACTION; INSERT INTO orders (user_id, total) VALUES (123, 99.5); UPDATE accounts SET balance = balance - 99.5 WHERE id = 123; UPDATE inventory SET stock = stock - 1 WHERE sku = 'A001'; COMMIT;
如果中间任意一步失败(如余额不足触发检查约束),必须由客户端或存储过程捕获异常并执行 ROLLBACK;仅靠 COMMIT 不足以兜底。
- 不要把 SELECT 放进事务里当“判断依据”再执行 DML——并发下可能失效,要用
SELECT ... FOR UPDATE锁住相关行 - 避免在事务中调用外部 API 或写文件,超时或失败会导致事务长时间悬挂
- 长事务会持有锁、阻塞其他操作,单个事务尽量控制在 1 秒内完成
应用层怎么配合才不翻车
数据库事务只是基础,真正决定原子性的往往是应用代码是否正确管理连接和错误分支。
典型坑点:
- 用连接池时,
BEGIN和COMMIT必须在同一个物理连接上执行,不能换连接 - ORM 如 SQLAlchemy 默认每个
session.commit()对应一个事务,但若手动调了connection.execute("BEGIN"),ORM 可能不知道,造成嵌套混乱 - Go 的
database/sql中,tx, err := db.Begin()后必须用tx.Exec,不能混用db.Exec,否则语句跑出事务外 - Node.js 的 pg 库里,
client.query("BEGIN")后要确保后续 query 都用同一client,且最终调client.query("COMMIT")或"ROLLBACK"
哪些情况事务也救不了
事务能保证数据库内部一致性,但无法覆盖所有“看起来该原子”的场景。
例如:
- 跨库操作(如 MySQL + Redis 更新):事务只管单库,Redis 修改无法回滚,得靠补偿逻辑或 Saga 模式
- DDL 语句(
ALTER TABLE)在多数数据库中会隐式提交当前事务,导致之前 DML 提前生效 - 某些存储引擎不支持事务(如 MySQL MyISAM),
BEGIN完全无效,执行即生效 - 触发器里抛异常,有些数据库(如旧版 MySQL)不自动回滚主事务,需显式
ROLLBACK
真要强一致性,得从架构层面拆解:把必须原子的操作收拢到同一数据库、同一事务内;其余环节接受最终一致,并设计好重试与对账机制。

















