事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。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:对数据一致性要求极高时使用,并发性能差

隔离性的实现机制

数据库通过以下机制实现隔离性:

瑞幸场景示例

场景: 两个用户同时购买最后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 日志
  • 即使系统崩溃,提交的修改也不会丢失

参考链接