事务未生效主因是自动提交模式干扰:mysql默认autocommit=1,需先set autocommit=0或用start transaction;postgresql默认关闭但函数内begin非事务;sqlite依赖驱动且conn.close()自动回滚;orm中flush≠commit;客户端工具可能强制autocommit;跨连接、非事务引擎、ddl操作及分布式场景均破坏原子性。

事务没生效?检查 BEGIN 后是否真在事务中
很多情况下你以为 BEGIN 启动了事务,其实数据库早已自动提交——尤其在自动提交模式(autocommit)开启时,BEGIN 只是临时禁用一次自动提交,后续语句执行完立刻隐式 COMMIT。MySQL 默认开启 autocommit,PostgreSQL 默认关闭,SQLite 则取决于驱动。
- MySQL 中务必先执行
SET autocommit = 0,再BEGIN;或者直接用START TRANSACTION(语义更明确) - PostgreSQL 不需要关 autocommit,但要注意:如果在函数或匿名块里用
BEGIN,它会被解释为异常块起始,不是事务控制——得用BEGIN TRANSACTION - Python 的
sqlite3模块默认关闭 autocommit,但调用conn.execute("BEGIN")后若没显式commit()或rollback(),连接关闭时会自动回滚,容易误以为“没执行”
COMMIT 前出错,为什么数据还在?
事务只对当前连接有效,COMMIT 是把变更持久化并通知其他连接可见。如果程序在 COMMIT 前崩溃、连接中断,或代码里漏写 COMMIT,那变更就永远卡在未提交状态——对其他连接不可见,对自己连接也查不到(除非用 SELECT 在同一事务内查),但不会报错。
- 常见陷阱:用 ORM(如 SQLAlchemy)时,误以为
session.add()+session.flush()就等于已提交,其实flush()只是同步到 DB 缓冲区,仍需session.commit() - 某些客户端工具(如 DBeaver)默认开启“自动提交”,即使你手动写了
BEGIN,它也可能在每条语句后偷偷COMMIT—— 查看连接设置里的 autocommit 开关 - 长时间未
COMMIT的事务会持有锁,可能阻塞其他查询,PostgreSQL 中可通过pg_stat_activity查state = 'idle in transaction'
嵌套事务不成立:MySQL 的 SAVEPOINT 才是真解法
SQL 标准不支持真正嵌套事务,所谓“子事务”只是保存点(SAVEPOINT)。MySQL 的 BEGIN / COMMIT 不允许嵌套;PostgreSQL 允许语法上嵌套,但外层 ROLLBACK 会连带回滚所有内层——实际仍是扁平事务模型。
- 想局部回滚,必须用
SAVEPOINT sp1,出错时ROLLBACK TO SAVEPOINT sp1,而不是再写一个BEGIN - MySQL 8.0+ 支持
RELEASE SAVEPOINT sp1清理,但老版本不支持,重复定义同名SAVEPOINT会覆盖前一个 - 注意:
SAVEPOINT不解决并发冲突,它只是单连接内的回滚锚点;并发更新同一行时,仍可能触发死锁或ERROR 1213 (HY000): Deadlock found
跨表更新失败,COMMIT 不能救活原子性
事务保证的是“全部成功或全部失败”,但前提是所有操作都在同一个事务内、且数据库能识别它们属于同一逻辑单元。如果更新分散在多个连接(比如不同微服务各自连 DB)、或用了不支持事务的存储引擎(如 MySQL 的 MyISAM),COMMIT 再规范也没用。
- 确认引擎:MySQL 中用
SHOW CREATE TABLE t1看是否为ENGINE=InnoDB;MyISAM 完全忽略BEGIN/COMMIT - 跨服务场景下,别指望单个 SQL 事务,得用 Saga 模式或分布式事务协调器(如 Seata),但代价是复杂度陡增
- DDL 语句(如
ALTER TABLE)在多数数据库中会隐式提交当前事务,执行后不能再ROLLBACK—— 这点常被忽略,尤其在迁移脚本里混写 DML 和 DDL
BEGIN 和 COMMIT 就自动成立的,它依赖连接状态、引擎能力、语句范围和外部协作边界。最容易被绕过的,其实是 autocommit 设置和跨连接误判。










