MySQL的事务是什么?原理、ACID、隔离级别与实战详解
事务是MySQL InnoDB引擎最核心的能力,也是后端开发面试高频考点。转账、下单、扣库存等业务都离不开事务。本文用通俗语言讲清楚事务是什么、解决什么问题、ACID四大特性、隔离级别、常见坑点。
一、什么是事务
事务(Transaction)是把多条SQL打包成一个不可分割的执行单元。
单元内所有SQL:
- 全部执行成功:执行
commit提交,修改永久生效; - 任意一步失败:执行
rollback回滚,撤销所有修改,回到执行之前的状态。
简单一句话:同生共死,要么全成,要么全败。
生活案例:转账
用户A给用户B转账200元,需要两条update语句:
- A的余额减少200
- B的余额增加200
如果没有事务:第一条执行成功,服务器宕机/程序报错,第二条没有执行。
结果:A钱扣掉了,B没有收到钱,产生脏数据。
开启事务之后:只要第二步失败,第一步的扣款操作就会全部撤销,不会出现中间异常状态。
⚠️注意:只有
InnoDB引擎支持事务;MyISAM引擎不支持事务,commit、rollback不会生效。
二、事务四大特性 ACID
ACID是事务必须满足的四个核心属性,面试必背。
- 原子性(Atomicity)
事务是最小执行单元,内部操作不可拆分。全部成功,或者全部失败回滚,不会停留在中间状态。
转账不能只扣钱不加钱。
- 一致性(Consistency)
事务执行前后,业务数据整体合法一致。
转账前后,A+B总金额不变,不会凭空多出或者消失资金。原子性、隔离性、持久性最终都是为了保证一致性。
- 隔离性(Isolation)
多个事务并发执行,互相之间要做到互相隔离。一个事务看不到另外一个事务未提交的数据。
多个同事同时操作同一笔账户,不能读到别人还没提交的中间临时数据。隔离性由事务隔离级别控制。
- 持久性(Durability)
事务一旦commit提交成功,修改永久保存到磁盘,即使数据库宕机、断电,数据也不会丢失。依靠InnoDB redo log重做日志实现。
三、MySQL事务核心命令
手动SQL层面操作事务
-- 方式1:开启事务STARTTRANSACTION;-- 执行多条DML语句 update / insert / deleteupdateaccountsetbalance=balance-200whereusername='A';updateaccountsetbalance=balance+200whereusername='B';-- 全部成功,提交事务COMMIT;-- 如果出错,回滚所有修改ROLLBACK;START TRANSACTION:开启事务;COMMIT:提交,持久化修改;ROLLBACK:回滚,撤销本次事务所有变更;SAVEPOINT 保存点名称:设置保存点,可以局部回滚,不用全部回滚;ROLLBACK TO SAVEPOINT xxx:回滚到指定保存点。
MySQL默认
autocommit=ON,每执行一条update/insert/delete就自动commit,相当于每条SQL自己就是一个独立事务。想要手动控制事务,需要关闭自动提交。
-- 关闭自动提交SETautocommit=0;四、事务的4种隔离级别
并发场景下,不同隔离级别用来解决3种读现象:脏读、不可重复读、幻读。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED(读未提交) | 允许 | 允许 | 允许 |
| READ COMMITTED(读已提交 RC) | 禁止 | 允许 | 允许 |
| REPEATABLE READ(可重复读 RR,MySQL默认) | 禁止 | 禁止 | 允许(InnoDB通过MVCC解决幻读) |
| SERIALIZABLE(串行化) | 禁止 | 禁止 | 禁止 |
三种并发问题通俗解释
- 脏读:一个事务读到另一个事务还没有提交的数据,如果对方回滚,读到的数据就是无效脏数据。
- 不可重复读:同一个事务内,同一条SQL前后查询,中间被其他事务提交修改,导致两次查询结果不一样。重点是数据内容被修改。
- 幻读:同一个事务,两次范围查询,别的事务插入/删除数据,导致前后返回行数不一样。重点是行数变化。
MySQL InnoDB 默认隔离级别是
REPEATABLE READ,依靠MVCC多版本并发控制,在RR级别解决幻读问题。
查看当前隔离级别:
SELECT@@transaction_isolation;设置隔离级别:
SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;五、代码层面简单示例(pymysql)
importpymysql conn=pymysql.connect(host="127.0.0.1",user="root",password="xxx",database="test_db",charset="utf8mb4")# 关闭自动提交,开启手动事务conn.autocommit=Falsecur=conn.cursor()try:cur.execute("update account set balance=balance-200 where username=%s",("A",))cur.execute("update account set balance=balance+200 where username=%s",("B",))conn.commit()print("事务提交成功")exceptExceptionase:conn.rollback()print(f"事务回滚,异常:{e}")finally:cur.close()conn.close()六、什么时候必须使用事务?
✅适合使用事务场景
- 转账、资金交易;
- 创建订单同时扣减库存;
- 多表关联写入、更新,多条操作必须同时成功;
- 业务逻辑要求多条SQL整体生效。
❌不需要事务场景
- 单条insert/update/delete;
- 纯查询select语句(查询不会修改数据)。
七、开发常见坑
- 忘记关闭autocommit:自动提交打开时,commit/rollback无效,每条SQL直接落库。
- 长事务:事务开启之后迟迟不commit/rollback。会占用锁、回滚段,造成大量锁等待,数据库性能暴跌。事务粒度尽量小。
- 事务使用不同connection连接:同一个事务所有SQL必须使用同一个连接对象,换连接就是全新事务。
- MyISAM引擎用事务:MyISAM不支持事务,commit/rollback调用没有效果。
- 异常捕获不执行rollback:发生异常不回滚,事务挂起,持续持有行锁。
- DDL语句(create/alter/drop)会隐式提交当前事务,DDL执行后前面的修改直接提交,无法回滚。
⚠️重点提醒:执行
ALTER TABLE等DDL会触发隐式commit,会把之前未提交的事务直接提交,这个坑线上经常踩。
八、总结
- 事务是InnoDB引擎提供的能力,把多条SQL打包,保证要么全成功要么全失败。
- ACID四大特性:原子性、一致性、隔离性、持久性。
- 4种隔离级别用来控制并发读写行为,MySQL默认RR可重复读。
- 业务写操作尽量缩小事务范围,拒绝长事务;异常必须rollback。
- DDL语句会隐式提交事务,写代码要格外注意。
事务不是为了加快查询速度,而是保证数据一致性,防止业务产生脏数据。很多线上资金故障,根源就是没有合理使用事务。
