必须用显式事务(begin...commit)包裹多表更新,否则并发或异常会导致数据不一致;严禁依赖触发器实现强一致性,所有update需严格校验where条件和索引。

用事务包住多表更新,否则一定丢数据
跨表更新不加事务,哪怕只差毫秒,中间出错或并发写入就会让订单表和库存表对不上。MySQL 默认自动提交,START TRANSACTION 必须显式写,不能靠“应该没问题”蒙混过去。
- 所有
UPDATE、INSERT、DELETE操作必须在同一个BEGIN ... COMMIT块里 - 遇到任何异常(比如库存不足、外键冲突),立刻
ROLLBACK,不能只靠应用层捕获 - 避免在事务里调用耗时操作(如 HTTP 请求、大文件读写),会锁表太久
存储过程里别用 SELECT ... FOR UPDATE 盲等
想“先查再改”时,SELECT ... FOR UPDATE 看似稳妥,但若没加 WHERE 条件或条件没走索引,会锁整张表。高并发下直接卡死。
- 必须确保
FOR UPDATE的查询字段有索引,比如按order_id查就建INDEX(order_id) - 不要在存储过程中嵌套循环查再逐条锁,改用
JOIN或批量IN一次性锁住所需行 - 超时控制要设:MySQL 5.7+ 可用
SET innodb_lock_wait_timeout = 5,避免一个慢事务拖垮全部
触发器不是同步替代方案
有人想用 AFTER INSERT 触发器自动更新关联表,但触发器无法回滚调用它的主事务——主 SQL 失败了,触发器却已执行完,数据彻底不一致。
- 触发器适合日志记录、缓存失效这类“尽力而为”的事,绝不用于核心业务数据联动
- 如果已有触发器在跑,检查它是否包含
INSERT INTO log_table这类无事务保护的操作,它们可能掩盖真实错误 - 真正需要强一致的多表变更,只接受存储过程 + 显式事务这一种路径
WHERE 条件写错会导致静默覆盖
更新用户余额和积分两个表时,如果第二个 UPDATE 的 WHERE 漏了 user_id,可能把全表积分清零——而事务照样 COMMIT 成功,因为语法完全合法。
- 每个
UPDATE语句执行前,用SELECT COUNT(*)验证WHERE能匹配几行,开发环境可加断言 - 避免用
UPDATE t1 JOIN t2 ON ...写法处理多表,容易误更新;拆成独立语句,每句WHERE单独核对 - 上线前用
EXPLAIN看每条语句的扫描行数,type: ALL就是危险信号
最麻烦的不是写不出逻辑,而是某次 WHERE 少打一个等号、索引失效没察觉、或者事务里混进了非事务引擎的表——这些错误不会报错,只会慢慢把数据啃歪。










