原子性不可妥协,但事务边界需收紧;长事务导致锁持有、undo log积压、连接堆积和死锁增多;应将http调用等耗时操作移出事务,按需选用read committed隔离级别,并通过乐观锁、最终一致性或saga模式解决跨服务原子性问题。

原子性不能妥协,但事务边界可以收紧——高并发下响应慢,90% 是因为事务拖得太长,而不是 ACID 本身有问题。
为什么长事务会直接拖垮并发能力
事务开启后,InnoDB 会持续持有锁、维护 undo log 版本、占用连接资源。一旦事务执行超过 100ms,就容易触发连锁反应:
- 行锁升级为间隙锁或表锁,阻塞其他事务的
SELECT ... FOR UPDATE或UPDATE - undo log 积压导致 purge 线程跟不上,MVCC 历史版本无法清理,
SELECT查询变慢 - 连接池中活跃连接堆积,新请求排队等待,
wait_timeout或max_connections被打满 - 死锁检测频率升高,
Innodb_deadlocks指标明显上涨
如何用代码控制事务生命周期(PHP/MySQLi 示例)
关键不是“要不要事务”,而是“在哪开、在哪关”。下面这段看似简单,但踩坑最多:
$mysqli->autocommit(FALSE);
try {
$mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
// ❌ 错误:这里调用外部 HTTP 接口,事务被卡住 500ms+
$result = file_get_contents('https://api.example.com/callback');
$mysqli->query("INSERT INTO logs (msg) VALUES ('transfer done')");
$mysqli->commit();
} catch (Exception $e) {
$mysqli->rollback();
throw $e;
}
正确做法是把耗时操作移出事务:
- 先在事务内完成所有数据库变更,并
commit() - 再单独发起回调、发消息、写日志等非事务操作
- 若回调失败,靠幂等接口 + 补偿任务修复,而不是把它们塞进同一事务
隔离级别选错会让原子性变成性能枷锁
REPEATABLE READ 是 MySQL 默认隔离级别,但它在高并发下容易引发幻读和间隙锁争用;而 READ COMMITTED 能显著减少锁范围,且对大多数业务足够安全:
- 电商下单:用
READ COMMITTED+SELECT ... FOR UPDATE显式加锁,比默认 RR 更轻量 - 避免在 RR 下执行无索引
WHERE条件的UPDATE,极易锁全表 - 确认业务能接受“不可重复读”时,直接设会话级:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - 不要全局改
transaction_isolation,只在关键事务里动态切
真正难处理的是“逻辑原子性”与“物理事务”的错位
比如库存扣减 + 订单生成 + 积分更新,三者必须整体成功,但硬塞进一个事务会放大锁竞争。这时候得拆:
- 用
UPDATE inventory SET stock = stock - 1 WHERE sku = 'A001' AND stock >= 1做乐观锁校验,返回affected_rows === 1才继续 - 订单和积分用最终一致性:主事务只管库存和订单头,积分异步发 MQ,由消费者重试保障
- 所有涉及跨服务的操作,放弃“强原子性”,转向 Saga 模式或 TCC,靠状态机 + 补偿动作兜底
最常被忽略的一点:事务的原子性保障只作用于单库单实例。一旦涉及分库、读写分离、缓存,就得靠应用层协调——这时候再纠结“MySQL 事务是否够原子”,已经偏离问题本质了。











