更新语句执行成功但数据未变,大概率是autocommit=true导致事务失效:sql server、mysql、postgresql的go/python/java驱动默认开启自动提交,使begin和rollback形同虚设;ddl、触发器错误或应用层未捕获err也会造成静默失败。

更新语句执行成功但最终数据没变,大概率不是“更新成功”,而是事务根本没生效——autocommit=true 在背后悄悄提交又回滚,或者压根没进事务上下文。
autocommit=true 让 BEGIN 和 ROLLBACK 形同虚设
SQL Server、MySQL、PostgreSQL 的 Go/Python/Java 驱动默认开启自动提交。这意味着:
-
db.Exec("UPDATE users SET name='a' WHERE id=1")会立刻提交,不等你调tx.Rollback() -
tx, _ := db.Begin()创建的*sql.Tx对象,若底层连接仍是autocommit=true,则所有tx.Exec()实际仍走自动提交路径 - SQL Server 驱动
github.com/denisenkom/go-mssqldb必须在连接字符串里显式加autocommit=false;MySQL 驱动需设parseTime=true&loc=Local&autocommit=false
DDL 或隐式提交语句中途“断掉”事务
哪怕你正确开启了事务,一条 DDL 就能让前面所有操作白干:
-
ALTER TABLE、CREATE INDEX、TRUNCATE在多数数据库中会触发隐式COMMIT - MySQL 中
SELECT ... FOR UPDATE在某些隔离级别下也可能提前提交(尤其配合innodb_lock_wait_timeout) - SQL Server 中
SET LOCK_TIMEOUT不影响事务边界,但DBCC类命令可能中断当前事务 - 验证方法:执行前查
SELECT @@TRANCOUNT(SQL Server)或SELECT TRX_ID FROM information_schema.INNODB_TRX(MySQL),执行后对比是否归零
触发器内部错误导致静默回滚
你以为 INSERT 成功了,其实触发器一报错,整条语句就回滚,但客户端可能只收到“0 rows affected”或模糊提示:
- MySQL 触发器里写错字段名(如
NEW.nonexist_col),不会抛明确错误,必须立刻执行SHOW WARNINGS才能看到 - SQL Server 触发器用
RAISERROR但没配SET XACT_ABORT ON,错误被吞,外层事务继续运行,看似成功实则已失效 - 触发器里调用存储过程或外部函数失败,若未显式
ROLLBACK,行为取决于@@ERROR检查逻辑和批处理终止策略 - 调试建议:在触发器开头加
INSERT INTO debug_log SELECT GETDATE(), 'trg_start',确认它是否真被执行
应用层没捕获错误,误判“更新成功”
很多 ORM 或封装层把 Exec() 返回的 err != nil 忽略了,或把 sql.ErrNoRows 当成正常流程,结果事务早因某步失败而回滚:
-
gorm.DB.Transaction()回调里没 return error → GORM 默认 commit,即使内部Create()失败 -
sqlx.NamedExec()报错但被if err != nil { log.Println(err); return }吞掉,后续tx.Commit()还在执行 - Go 的
defer tx.Rollback()放错位置(比如放在if err != nil分支外),导致无论成败都 rollback - 关键检查点:所有
tx.Exec()后必须判断err;tx.Commit()前确保没有未处理错误
最常被忽略的是连接级 autocommit 状态——它不像事务控制语句那样出现在 SQL 日志里,却决定着 BEGIN 到底有没有意义。别只盯着业务 SQL,先确认驱动连接参数和数据库实际会话状态。










