事务的原子性靠什么保证?
原子性不是靠 SQL 语句写得“看起来一起执行”就成立的,而是由
InnoDB存储引擎通过
Undo Log和
Redo Log协同实现的。你写
BEGIN、
COMMIT或
ROLLBACK,只是告诉引擎“我要开始/结束一个事务”,真正回滚时,InnoDB 会按
Undo Log中记录的反向操作(比如
INSERT对应
DELETE,
UPDATE对应反向
UPDATE)逐条还原数据。 如果你在事务中执行了
DROP TABLE或
ALTER TABLE,会**立即触发隐式提交**,后续
ROLLBACK无效——DDL 是事务“终结者”
innodb_flush_log_at_trx_commit设为
0或
2虽能提升写性能,但机器断电时可能丢事务;生产环境建议保持默认
1大事务(如批量更新百万行)容易撑爆
undo log空间,导致
rollback极慢甚至卡死,应拆分执行
一致性不是数据库自动兜底的
很多人误以为“用了事务,数据就天然一致”。其实
Consistency是 ACID 中最依赖业务逻辑的一环:数据库只保证约束(如主键、外键、
CHECK)、不破坏已定义的规则;但业务层面的“钱总额不变”“库存不能为负”这类逻辑,必须靠你写对 SQL 顺序、加锁、校验条件来保障。 转账场景下,仅靠
UPDATE account SET balance = balance - 100 WHERE id = 1+
UPDATE account SET balance = balance + 100 WHERE id = 2不够——若第一条成功、第二条失败且没
ROLLBACK,钱就凭空消失了 没有外键或
ENUM约束时,
status字段被误设为
'pending_pay'(而合法值只有
'paid'/
'canceled'),事务照样提交成功,但业务已不一致
SELECT ... FOR UPDATE在读取余额后加行锁,能防止并发扣款超支,这是手动保一致的关键动作
隔离性失效的典型现场
默认的
REPEATABLE READ隔离级别看似安全,但在高并发下仍可能遇到幻读(新插入行被读到),而
READ COMMITTED虽避免幻读,却带来不可重复读问题。是否出问题,取决于你有没有在事务内做“范围条件读 + 后续写入”这类操作。 执行
SELECT * FROM order WHERE status = 'unpaid'得到 5 条,接着想把这 5 条全更新为
'processing',但如果别人在你查询后插入了第 6 条未支付订单,
REPEATABLE READ下你查不到它,但更新语句会命中它(InnoDB 的 next-key lock 机制决定),结果更新了 6 条——这就是“幻读”的实际影响 用
SELECT ... LOCK IN SHARE MODE或
FOR UPDATE显式加锁,比单纯依赖隔离级别更可控 应用层缓存(如 Redis)和数据库事务不同步时,即使数据库强一致,用户看到的仍是脏数据——隔离性只管 DB 层,不管应用层
持久性≠永不丢失,要看刷盘配置
所谓“提交即永久”,前提是
Redo Log已落盘。而是否落盘,由
innodb_flush_log_at_trx_commit决定。别只看文档说“ACID 保证持久”,线上出问题时,往往栽在这个参数上。 设为
0:崩溃前一秒提交的事务大概率丢失(log 只在 buffer,未刷盘也未交由 OS) 设为
2:MySQL 挂了不丢,但 OS 崩溃或断电仍可能丢(log 在 OS cache,未 fsync 到磁盘) 设为
1(默认):每次
COMMIT都强制
fsync,最安全,但写入吞吐受限——SSD 上影响小,机械盘上明显 如果用了
binlog+
Redo Log双写,配合
sync_binlog=1,才能支撑主从强一致和崩溃恢复
事务的 ACID 不是开关一开就自动生效的魔法,每个特性背后都有明确的机制、配置和使用边界。最容易被忽略的是:一致性靠代码逻辑兜底,隔离性靠锁+隔离级别协同,而持久性最终取决于你敢不敢让 Redo Log 多等那一次磁盘 IO。
