事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。ACID是事务必须满足的四个特性:
原子性(Atomicity)
- 事务是一个不可分割的工作单位
- 事务中的所有操作要么全部成功,要么全部失败回滚
- 不会出现部分操作成功、部分失败的情况
一致性(Consistency)
- 事务执行前后,数据库从一个一致性状态转换到另一个一致性状态
- 数据库的完整性约束(主键、外键、唯一约束等)在事务前后都保持满足
- 业务规则在事务前后都保持有效
示例:商品下单(瑞幸场景)
- 一致性前:库存100杯,用户余额200元
- 事务:用户购买1杯咖啡(20元)
- 库存:100 - 1 = 99
- 用户余额:200 - 20 = 180
- 生成订单记录
- 一致性后:库存99杯,用户余额180元,有一条对应的订单记录
- 违反一致性的情况:如果库存减了但订单没生成,或者钱扣了但库存没减,就破坏了一致性
一致性的关键点
- 一致性更多是业务层面的概念,由业务规则定义
- 原子性、隔离性、持久性是为了保证一致性的手段
- 数据库通过约束(主键、外键、CHECK等)保证数据层面的一致性
- 应用通过事务保证业务层面的一致性
隔离性(Isolation)
- 多个并发事务之间相互隔离,互不干扰
- 一个事务的内部操作对其他事务是不可见的
- 通过隔离级别来控制隔离程度
为什么需要隔离性?
多个事务同时执行时,如果不加隔离,可能会出现三种并发异常 脏读-Dirty-Read、不可重复读-Non-repeatable-Read、幻读-Phantom-Read
四种隔离级别
隔离级别从低到高,隔离程度越高,并发性能越差:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ✅ | ✅ | ✅ | 读未提交,几乎不用 |
| READ_COMMITTED | ❌ | ✅ | ✅ | 读已提交,互联网常用 |
| REPEATABLE_READ | ❌ | ❌ | ✅ | 可重复读,MySQL默认 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 串行化,性能最差 |
隔离级别的选择:
- READ_COMMITTED:大多数互联网场景使用,防止脏读,性能较好
- REPEATABLE_READ:需要保证同一事务内多次读取一致时使用
- SERIALIZABLE:对数据一致性要求极高时使用,并发性能差
隔离性的实现机制
数据库通过以下机制实现隔离性:
- 锁机制:读锁、写锁、行锁、表锁 锁机制实现详解
- MVCC(多版本并发控制):通过数据版本实现读写不阻塞 MVCC-多版本并发控制
- undo/redo日志:用于回滚和恢复 Undo-Redo日志详解
瑞幸场景示例
场景: 两个用户同时购买最后1杯咖啡
无隔离性的情况:
T1: 用户A查询库存=1
T2: 用户B查询库存=1
T3: 用户A扣库存=0,生成订单
T4: 用户B也扣库存=0,生成订单
结果:超卖!1杯咖啡卖给了2个人
有隔离性的情况(使用select … for update):
T1: 用户A查询库存=1(加锁)
T2: 用户B查询库存(被阻塞,等待锁释放)
T3: 用户A扣库存=0,生成订单,提交,释放锁
T4: 用户B获取锁,查询库存=0,提示"库存不足"
结果:正确!不会超卖
隔离性与其他特性的关系
- 原子性:保证事务内操作要么全成功要么全失败
- 隔离性:保证并发事务之间互不干扰
- 两者共同保证一致性:只有原子性没有隔离性,并发操作仍可能破坏一致性
持久性(Durability)
- 事务一旦提交,对数据库的修改就是永久性的,通过Redo log实现,具体见:3. Redo 日志
- 即使系统崩溃,提交的修改也不会丢失