事务本质是保障多步写入操作的原子性以应对并发冲突,核心用于“多步写入+状态依赖”场景,如银行转账、电商下单等,需显式开启并合理设置隔离级别与超时控制。

事务不是为了“保证正确”,而是为了应对并发写入冲突
MySQL 的事务机制本质是解决多个操作必须“一起成功或一起失败”时的原子性需求,尤其在多用户同时修改同一数据时。比如银行转账:A 账户扣款和 B 账户入账必须视为一个不可分割的动作,否则中间出错(如网络中断、程序崩溃)会导致资金不一致。
常见错误现象:UPDATE account SET balance = balance - 100 WHERE id = 1 和 UPDATE account SET balance = balance + 100 WHERE id = 2 如果不加事务包裹,第二条语句失败时,第一条已生效,钱就“消失”了。
- 事务不是所有场景都必需:单条
INSERT或只读SELECT通常无需显式事务 - InnoDB 是唯一支持完整 ACID 事务的 MySQL 引擎;MyISAM 不支持事务,强行用
BEGIN也不会生效 - 自动提交(
autocommit=1)是默认行为,意味着每条 SQL 都是一个独立事务;业务中需先执行SET autocommit = 0或用BEGIN显式开启
哪些业务逻辑必须用事务?看有没有“多步写入+状态依赖”
核心判断标准:是否有多条写操作(INSERT/UPDATE/DELETE),且后一步的执行逻辑依赖前一步的结果或状态。
典型场景举例:
- 电商下单:扣库存 + 插入订单 + 插入订单明细 —— 库存不足时,后面两步必须回滚
- 积分发放:更新用户积分表 + 写入积分流水日志 —— 日志写入失败,积分不能白加
- 权限变更:删旧角色关联 + 插入新角色关联 —— 中间失败会导致用户权限错乱
注意:SELECT ... FOR UPDATE 这类加锁查询也必须在事务中使用,否则锁会在语句结束时立即释放,起不到保护作用。
事务不是万能的:隔离级别选错反而引发新问题
MySQL 默认隔离级别是 REPEATABLE READ,但它不等于“完全隔离”。比如幻读(Phantom Read)在该级别下仍可能发生(虽然 InnoDB 用间隙锁做了部分缓解);而 READ COMMITTED 虽然避免幻读更彻底,但可能让业务看到“中间态”数据(如两次 SELECT COUNT(*) 结果不同)。
-
READ UNCOMMITTED:几乎不用,会读到未提交的脏数据,SELECT可能返回后续被ROLLBACK的结果 -
SERIALIZABLE:最严格,但性能差,所有SELECT都隐式加读锁,容易导致大量锁等待 - 调整方式:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED,建议按接口粒度设置,而非全局改
事务太长会拖垮数据库,尤其是没意识到的隐式事务
一个常见陷阱是:以为只有 BEGIN / COMMIT 才算事务,其实任何 DML 操作在 autocommit=0 下都会延续当前事务。如果忘记 COMMIT 或 ROLLBACK,连接会一直持有锁、占用 undo log 空间,甚至阻塞其他事务。
- 超时参数
innodb_lock_wait_timeout默认 50 秒,但长时间未提交事务可能先触发wait_timeout断连,留下孤立事务 - 监控方法:查
information_schema.INNODB_TRX表,重点关注TRX_STARTED和TRX_STATE - ORM 框架(如 Django、Spring)常自动管理事务,但嵌套事务、异步调用、异常未被捕获等情况,仍可能导致事务悬挂
真正难的不是写 START TRANSACTION,而是判断哪段逻辑该包、包多大、锁多久、出错怎么退——这些得结合业务状态流和数据依赖图来设计,不能靠模板套用。










