数据库事务

基本特性

Atomicity(原子性)

原子性是指事务中的所有操作作为一个不可分割的整体执行,要么全部成功完成,要么全部不执行。如果事务执行过程中任何操作失败,系统会将数据库回滚到事务开始之前的状态。这就像银行转账操作中的”全有或全无”原则。

实现机制

  • 使用事务日志(Transaction Log)记录所有操作
  • 通过Undo日志实现回滚操作
  • 在分布式系统中采用两阶段提交协议

应用示例: 当用户从账户A向账户B转账100元时:

  1. 从A账户扣除100元
  2. 向B账户增加100元 如果第二步失败,系统会自动撤销第一步的操作,确保两个账户金额保持不变。

Consistency(一致性)

一致性确保事务执行前后,数据库从一个合法状态转变为另一个合法状态,所有数据约束、规则和完整性都得到保持。

关键要素

  • 实体完整性(主键约束)
  • 参照完整性(外键约束)
  • 用户定义的业务规则(如账户余额不能为负)

Isolation(隔离性)

隔离性定义了多个并发事务之间的可见性和影响程度,确保事务执行不受其他并发事务干扰。

隔离级别(从低到高):

  1. 读未提交(Read Uncommitted)
  2. 读已提交(Read Committed)
  3. 可重复读(Repeatable Read)
  4. 序列化(Serializable)

并发问题

  1. 脏读:事务A读取了事务B未提交的修改
  2. 不可重复读:事务A多次读取同一数据,期间事务B修改并提交了该数据
  3. 幻读:事务A读取满足条件的多行数据,期间事务B插入/删除了满足条件的新数据

Durability(持久性)

持久性确保一旦事务提交,其所做的改变就会永久保存在数据库中,即使系统发生故障也不会丢失。

实现方式

  • 预写日志(WAL)机制
  • 定期检查点(Checkpoint)
  • 数据库备份和恢复策略

基本介绍

分布式事务从实质上看与数据库事务的概念是一致的,既然是事务也就需要满足ACID(原子性、一致性、隔离性、持久性)四大特性。与传统本地事务相比,分布式事务涉及多个独立的资源或服务,这些资源可能分布在不同的物理节点上,通过网络进行通信协作,因此其实现方式和表现形式有很大的不同。

在本地事务中,所有操作都在同一个数据库实例中完成,事务管理器可以直接控制事务的提交或回滚。而在分布式环境中,事务可能跨越多个数据库、微服务或消息队列等异构系统,每个参与方都有自己的事务管理器。这种分布式特性带来了新的挑战,主要包括:

  1. 网络通信的不确定性(如延迟、丢包)
  2. 各参与方可能使用不同的事务实现方式
  3. 系统故障的独立性(某个节点故障不影响其他节点)

常见的分布式事务解决方案包括:

  • 两阶段提交(2PC):协调者先询问所有参与者能否提交,收到确认后再通知提交
  • 三阶段提交(3PC):在2PC基础上增加预提交阶段,降低阻塞时间
  • TCC(Try-Confirm-Cancel):通过预留资源、确认/取消的业务逻辑实现
  • 基于消息的最终一致性:利用消息队列实现异步的事务处理

两阶段提交协议(2PC)

2PC(Two-Phase Commit)即两阶段提交协议,是一种分布式事务处理机制,用于确保跨多个节点的数据操作要么全部成功,要么全部失败回滚。该协议将整个事务流程划分为两个明确的阶段:

  1. 准备阶段(Prepare phase)
    • 协调者(Coordinator)向所有参与者(Participants)发送准备请求
    • 各参与者执行事务操作但不提交,将Undo/Redo信息写入事务日志
    • 参与者锁定相关资源,确保数据一致性
    • 参与者向协调者反馈准备结果(同意或拒绝)
  2. 提交阶段(Commit phase)
    • 若所有参与者都同意,协调者发送提交指令
    • 参与者完成事务提交并释放资源
    • 若任一参与者拒绝,协调者发送回滚指令
    • 参与者使用事务日志回滚操作

准备阶段(Prepare phase)

在这个关键的准备阶段中,事务协调器(Transaction Coordinator)会向所有参与事务的数据库节点发送Prepare请求。具体流程如下:

  1. Prepare消息发送:事务管理器向每个参与事务的数据库节点发送Prepare消息,询问它们是否准备好提交事务。

  2. 本地事务执行

    • 各参与者收到Prepare消息后,在本地执行事务的所有操作
    • 执行期间会获取必要的数据库锁,防止其他事务干扰
    • 但此时不会真正提交事务
  3. 日志记录

    • Undo日志:记录事务修改前的数据状态
    • Redo日志:记录事务修改后的数据状态
    • 日志写入磁盘以保证持久性,即使系统崩溃也能恢复
  4. 响应准备结果

    • 参与者完成上述操作后,向协调器反馈响应
    • 成功则返回”准备就绪”(Ready)响应
    • 失败则返回”中止”(Abort)响应

提交阶段(Commit phase)

提交阶段是决定事务最终命运的环节,包含以下详细步骤:

  1. 决策阶段

    • 协调器收集所有参与者的响应
    • 若收到任一参与者的失败响应或超时未响应:
      • 向所有参与者发送Rollback指令
      • 参与者使用Undo日志回滚事务
    • 若所有参与者都返回”准备就绪”:
      • 协调器写入”提交”决定到持久存储
      • 向所有参与者发送Commit指令
  2. 执行阶段

    • 参与者收到Commit指令后:
      • 使用Redo日志完成事务的持久化
      • 正式提交事务
    • 参与者收到Rollback指令后:
      • 使用Undo日志恢复数据到事务前状态
      • 标记事务为已回滚
  3. 资源释放

    • 最后阶段必须释放事务中获取的所有锁资源
    • 包括行锁、表锁等各种级别的锁
    • 释放后其他事务才能访问相关数据

2PC执行流程

成功执行

阶段一

  • 事务询问:协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应
  • 执行事务(写本地Undo/Redo日志)
  • 各个参与者向协调者反馈事务询问的响应

阶段二

  • 发送提交请求:协调者向所有参与者发出 Commit 请求
  • 事务提交:参与者收到 Commit 请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行期间占用的事务资源
  • 反馈事务提交结果:参与者在完成事务提交之后,向协调者发送ACK信息
  • 完成事务:协调者接收到所有参与者反馈的ACK信息后,完成事务

中断事务

阶段一

  • 事务询问:协调者向所有参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应
  • 执行事务:(写本地的Undo/Redo日志)
  • 各参与者向协调者反馈事务询问的响应

阶段二

  • 发送回滚请求:协调者向所有参与者发出 RollBack 请求
  • 事务回滚:参与者接收到 RollBack 请求后,会利用其在阶段一中记录的Undo信息来执行回滚操作,并在完成回滚之后释放整个事务执行期间占用的资源
  • 反馈事务回滚结果:参与者在完成事务的事务回滚之后,向协调者发送ACK消息
  • 中断事务:协调者接收到所有参与者反馈的ACK之后,完成事务中断

2PC优缺点

优点

原理简单,实现方便

缺点

  • 同步阻塞:二阶段提交协议存在最明显也是最大的一个问题就是同步阻塞,在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,无法进行其他操作,这种同步阻塞极大的限制了分布式系统的性能

  • 单点问题:协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出了问题,那么整个流程将无法运转,更重要的是,其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作。

  • 数据不一致:假设当协调者向所有的参与者发送commit请求之后,发生了局部异常或者协调者尚未发送完所有commit请求之前自身发生了奔溃,导致最终只有部分参与者收到了commit请求。这将导致严重的数据不一致问题

  • 过于保守:如果二阶段提交过程中,参与者出现故障而导致协调者始终无法获取到所有参与者的相应的话,这时协调者只能依靠自身的超时机制来判断是否需要中断事务。显然,这种策略过于的保守。换句话说,二阶段协议没有设计较为完整的容错机制,任意一个节点失败都会导致整个事务的失败。