事务就是一组必须“全成或全挂”的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,光靠理论很难定位死锁或长事务问题。
