事务是保证多条sql“全成或全挂”的逻辑单元,通过begin/commit/rollback控制,依托undolog、redolog和锁实现acid;mysql默认rr隔离级别存在幻读陷阱,需谨慎使用间隙锁与快照读。

事务就是一组必须“全成或全挂”的SQL操作
你在做转账、下单、库存扣减这类业务时,如果只执行一半就出错(比如张三扣了钱,李四没收到),数据库就会陷入不一致状态。事务就是为解决这个问题而生的:它把多条相关SQL打包成一个逻辑单元,要么全部成功写入,要么全部撤销,不留中间态。BEGIN 开启,COMMIT 提交,ROLLBACK 回滚——这是最底层的控制节奏。
ACID不是口号,是MySQL用undolog、redolog和锁硬扛出来的
原子性靠 undolog 实现:每步修改都记下“怎么 undo”,回滚时直接逆向重放;一致性不是事务自己保证的,而是靠你写的业务逻辑 + 外键/约束 + 隔离级别共同兜底;隔离性由锁和MVCC配合完成——比如 REPEATABLE READ 下,InnoDB 用快照读避免幻读,但当前读仍可能被 select ... for UPDATE 阻塞;持久性依赖 redolog 刷盘机制,哪怕断电也能恢复已提交事务。
MySQL默认的REPEATABLE READ其实有“陷阱”
很多人以为它能完全避免幻读,但真实情况是:普通 SELECT 看不到其他事务插入的新行(快照读),可一旦执行 SELECT ... FOR UPDATE 或 INSERT,就可能触发间隙锁(gap lock),甚至升级为临键锁(next-key lock)。这意味着:
- 并发插入相同范围数据时,可能意外阻塞,而不是报错
- 用
SELECT count(*)再INSERT做“存在性校验”,在RR下仍可能重复插入(因为两次SELECT看到的是同一快照,但INSERT会实际加锁) - 想真正串行化,得显式用
SELECT ... FOR UPDATE或干脆设成SERIALIZABLE,但性能代价极大
改隔离级别不能只靠SET session TRANSACTION ISOLATION LEVEL
这个命令只影响当前会话,且必须在事务开始前设置;如果用连接池(如Druid、HikariCP),连接复用后隔离级别可能继承上一次的值,导致行为不一致。更稳妥的做法是:
- 在应用层连接初始化时统一设置(比如spring的
TransactionDefinition.ISOLATION_REPEATABLE_READ) - 避免在同一个事务里动态切换级别(MySQL不支持)
-
READ UNCOMMITTED在InnoDB中基本无效——它忽略行锁,但InnoDB仍会加锁,实际效果接近READ COMMITTED
事务真正的复杂点不在概念,而在“哪条SQL触发什么锁”“快照何时生成”“undo日志什么时候清理”——这些细节不看INFORMATION_SCHEMA.INNODB_TRX和SHOW ENGINE INNODB STATUS,光靠理论很难定位死锁或长事务问题。