跨表操作必须显式开启事务,否则回滚无效;ROLLBACK仅撤销未提交的变更且仅对当前连接生效;局部回滚需用SAVEPOINT;SELECT FOR UPDATE锁失效多因WHERE未走索引;避免在事务中执行非数据库操作。

跨表操作必须显式开启事务,否则回滚无效
MySQL 不会自动把多张表的写入当成一个原子操作。哪怕你执行了 INSERT INTO orders 和 UPDATE inventory 两条语句,只要没用 START TRANSACTION 包裹,它们就是两个独立的自动提交事务——前一条成功、后一条失败,数据就错位了。
常见错误现象:INSERT INTO orders 成功,但 UPDATE inventory SET stock = stock - 1 因库存不足被拒绝,订单已建、库存未扣,业务逻辑崩了。
- 必须在应用层(PHP/Python/Java 等)显式调用
START TRANSACTION或等效 API(如 Python 的conn.begin()) - ORM 用户要确认是否真正启用事务:Django 必须加
@transaction.atomic,MyBatis 需配<transactionManager type="JDBC"/> - 不要依赖存储过程封装事务逻辑——出问题时无法和上游服务协同重试,监控链路也断了
ROLLBACK 只撤销未提交的变更,且仅对当前连接生效
ROLLBACK 不是“撤销最近一次 SQL”,而是撤销从 START TRANSACTION 开始、到它执行前的所有 DML 操作。前提是:事务还没 COMMIT,且你仍在同一个数据库连接里。
典型误用场景:在事务中调用 HTTP 接口耗时 3 秒,期间其他连接查不到新数据,而你自己又忘了 COMMIT 或 ROLLBACK,连接空挂,锁一直不释放。
-
ROLLBACK后,行锁、间隙锁立即释放,其他会话不再阻塞 - 如果用的是连接池,务必确保
ROLLBACK在连接归还前执行,否则下次复用该连接的人可能意外继承一个未清理的事务状态 - 别指望
ROLLBACK能撤回已经COMMIT的数据——那是物理不可逆的
需要局部回滚?用 SAVEPOINT,别碰嵌套事务
MySQL 不支持真正的嵌套事务。START TRANSACTION 在已有事务内执行,只会警告并忽略。想只撤回某一步?必须靠 SAVEPOINT。
使用场景:批量导入用户数据,第 5 条因邮箱重复失败,你只想丢掉这一条,不影响前面 4 条已成功插入的记录。
- 设保存点:
SAVEPOINT sp_insert_user - 执行可能失败的操作:
INSERT INTO users (email) VALUES ('bad@domain.com') - 出错时回退到该点:
ROLLBACK TO sp_insert_user - 继续后续逻辑,比如记录错误日志、跳过当前条目
-
RELEASE SAVEPOINT sp_insert_user可显式清理,但不释放也不影响事务本身;同名保存点会被覆盖
SELECT FOR UPDATE 锁失效?大概率是 WHERE 没走索引
跨表一致性常依赖 SELECT ... FOR UPDATE 先锁住关键行,再更新。但如果 WHERE 条件没命中索引,InnoDB 会升级为表级锁——整个表卡死,不是你想要的“精准控制”。
例如:SELECT * FROM inventory WHERE sku = 'ABC' FOR UPDATE,若 sku 字段没建索引,就会锁全表,连 SELECT COUNT(*) FROM orders 都可能被堵住。
- 用
EXPLAIN确认查询是否走了索引 - 避免在事务中做非数据库操作(发邮件、生成 PDF、调外部 API),它们会让锁持有时间不可控
- 长事务会拖慢主从同步,监控
information_schema.INNODB_TRX表里的TRX_STARTED和TRX_STATE,超 5 秒的事务值得人工介入
事务边界划在哪,比怎么写 ROLLBACK 更关键。很多人花时间调 SAVEPOINT 语法,却让事务包裹了日志推送或文件读取——锁住了不该锁的东西,最后查半天才发现是自己卡住了自己。


















