05-事务处理完全指南

22 阅读8分钟

事务处理完全指南

文档类型: 数据库事务与分布式事务
更新时间: 2025-10-28


目录

  1. 事务基本概念
  2. 数据库事务
  3. 分布式事务
  4. 事务隔离级别
  5. 事务实战
  6. 性能优化
  7. 常见问题
  8. 面试高频题

1. 事务基本概念

1.1 什么是事务?

事务(Transaction):
一组数据库操作,要么全部成功,要么全部失败。

示例:银行转账
┌─────────────────────────────┐
│  BEGIN TRANSACTION          │
│    1. 扣减A账户100元         │ ─┐
│    2. 增加B账户100元         │  │ 原子操作
│  COMMIT                     │ ─┘
└─────────────────────────────┘

如果步骤2失败 → 回滚步骤1
保证数据一致性

1.2 ACID特性

┌─────────────────────────────────────────┐
│  A - Atomicity (原子性)                  │
│  所有操作要么全部成功,要么全部失败        │
│                                         │
│  示例:转账必须扣款和入账同时成功         │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│  C - Consistency (一致性)                │
│  事务前后数据保持一致状态                 │
│                                         │
│  示例:转账前后总金额不变                │
│  A(1000) + B(500) = 1500                │
│  转账后:A(900) + B(600) = 1500         │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│  I - Isolation (隔离性)                  │
│  并发事务之间相互隔离,互不干扰           │
│                                         │
│  示例:事务A看不到事务B未提交的修改       │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│  D - Durability (持久性)                 │
│  事务提交后永久保存,不会丢失             │
│                                         │
│  示例:提交后即使断电也能恢复数据         │
└─────────────────────────────────────────┘

2. 数据库事务

2.1 事务基本操作

-- 开启事务
BEGIN;
-- 或
START TRANSACTION;

-- 执行SQL
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 提交事务
COMMIT;

-- 或回滚事务
ROLLBACK;

2.2 PHP中使用事务

// PDO事务
try {
    $pdo->beginTransaction();
    
    // 扣款
    $stmt = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");
    $stmt->execute([100, 1]);
    
    // 入账
    $stmt = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?");
    $stmt->execute([100, 2]);
    
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    throw $e;
}
// Laravel事务
DB::transaction(function () {
    // 扣款
    DB::table('accounts')
        ->where('id', 1)
        ->decrement('balance', 100);
    
    // 入账
    DB::table('accounts')
        ->where('id', 2)
        ->increment('balance', 100);
});

// 自动提交/回滚
// 成功 → COMMIT
// 异常 → ROLLBACK
// Hyperf事务
use Hyperf\DbConnection\Db;

Db::beginTransaction();

try {
    // 业务逻辑
    Db::table('accounts')
        ->where('id', 1)
        ->decrement('balance', 100);
    
    Db::table('accounts')
        ->where('id', 2)
        ->increment('balance', 100);
    
    Db::commit();
} catch (\Exception $e) {
    Db::rollBack();
    throw $e;
}

2.3 事务的实现原理

Redo Log(重做日志)
作用:保证持久性(D)

写入流程:
1. 修改数据页(内存)
2. 写入Redo Log(磁盘)
3. 返回成功
4. 后台线程刷脏页到磁盘

崩溃恢复:
┌────────────┐
│  Redo Log  │ → 重放日志 → 恢复数据
└────────────┘

优势:
- 顺序写(快)
- 批量刷盘(减少IO)
Undo Log(回滚日志)
作用:
1. 回滚事务(原子性A)
2. MVCC(隔离性I)

示例:
BEGIN;
UPDATE users SET age = 30 WHERE id = 1;  -- 原值: 25

Undo Log记录:
[UPDATE users SET age = 25 WHERE id = 1]  ← 回滚用

ROLLBACK时:
执行Undo Log中的SQL → 恢复原值
Bin Log(二进制日志)
作用:
1. 主从复制
2. 数据恢复
3. 数据库审计

内容:
所有DDL、DML语句

格式:
- STATEMENT: SQL语句
- ROW: 行变更(推荐)
- MIXED: 混合模式
两阶段提交(2PC)
Redo Log和Bin Log的一致性保证

阶段1: Prepare
┌──────────────┐
│  写Redo Log  │
│  (Prepare)   │
└──────┬───────┘
       │
       ▼
阶段2: Commit
┌──────────────┐
│  写Bin Log   │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│  Redo Log    │
│  (Commit)    │
└──────────────┘

崩溃恢复:
- Redo Log Prepare + Bin Log存在 → 提交
- 只有Redo Log Prepare → 回滚

3. 分布式事务

3.1 为什么需要分布式事务?

单体应用:
┌─────────────┐
│  Database   │  ← 本地事务
└─────────────┘

微服务架构:
┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│  OrderDB    │   │  StockDB    │   │  AccountDB  │
└─────────────┘   └─────────────┘   └─────────────┘
       ▲                ▲                 ▲
       │                │                 │
       └────────────────┴─────────────────┘
              跨数据库操作 → 分布式事务

场景:电商下单
1. 创建订单(OrderDB)
2. 扣减库存(StockDB)
3. 扣减余额(AccountDB)

要求:要么全部成功,要么全部失败

3.2 CAP理论

┌──────────────────────────────────────┐
│  C - Consistency (一致性)             │
│  所有节点同一时间看到相同数据          │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│  A - Availability (可用性)            │
│  每次请求都能得到响应                 │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│  P - Partition Tolerance (分区容错性) │
│  网络分区时系统仍能工作                │
└──────────────────────────────────────┘

CAP定理:最多同时满足两个

选择:
- CA: 单机数据库(无分区)
- CP: 强一致性(牺牲可用性)→ ZooKeeper
- AP: 高可用性(牺牲一致性)→ Cassandra

3.3 BASE理论

CAP的妥协:牺牲强一致性,换取可用性

┌──────────────────────────────────────┐
│  BA - Basically Available (基本可用)  │
│  允许部分功能暂时不可用                │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│  S - Soft State (软状态)              │
│  允许中间状态存在                      │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│  E - Eventually Consistent (最终一致)  │
│  经过一段时间后达到一致                │
└──────────────────────────────────────┘

示例:
下单时库存-1(立即)
订单服务收到通知(延迟1秒)
最终:订单和库存一致

3.4 两阶段提交(2PC)

协调者(Coordinator) + 参与者(Participant)

阶段1: Prepare (准备阶段)
协调者                参与者A        参与者B
   │                    │             │
   ├─── Prepare ───────►│             │
   ├─── Prepare ───────────────────►  │
   │                    │             │
   │◄─── Yes ───────────┤             │
   │◄─── Yes ───────────────────────  │
   │                    │             │

阶段2: Commit (提交阶段)
   │                    │             │
   ├─── Commit ────────►│             │
   ├─── Commit ────────────────────►  │
   │                    │             │
   │◄─── ACK ───────────┤             │
   │◄─── ACK ───────────────────────  │

优点:
✅ 强一致性

缺点:
❌ 同步阻塞(性能低)
❌ 单点故障(协调者)
❌ 数据不一致(Commit阶段网络异常)
// PHP实现2PC
class TwoPhaseCommit
{
    private $participants = [];
    
    public function addParticipant(Participant $participant)
    {
        $this->participants[] = $participant;
    }
    
    public function execute()
    {
        // 阶段1: Prepare
        foreach ($this->participants as $participant) {
            if (!$participant->prepare()) {
                // 有参与者prepare失败
                $this->abort();
                return false;
            }
        }
        
        // 阶段2: Commit
        try {
            foreach ($this->participants as $participant) {
                $participant->commit();
            }
            return true;
        } catch (Exception $e) {
            // Commit失败,尝试回滚
            $this->abort();
            throw $e;
        }
    }
    
    private function abort()
    {
        foreach ($this->participants as $participant) {
            $participant->rollback();
        }
    }
}

// 使用示例
$coordinator = new TwoPhaseCommit();
$coordinator->addParticipant(new OrderService());
$coordinator->addParticipant(new StockService());
$coordinator->addParticipant(new AccountService());

$coordinator->execute();

3.5 TCC模式

TCC = Try-Confirm-Cancel

Try阶段: 预留资源
Confirm阶段: 确认提交
Cancel阶段: 取消回滚

流程:
┌────────────────────────────────────┐
│  订单服务    库存服务    账户服务   │
├────────────────────────────────────┤
│  Try: 创建   Try: 冻结   Try: 冻结  │
│  待支付订单   库存-1     余额-100   │
│             stock_frozen  balance_frozen
│                                    │
│  Confirm:   Confirm:    Confirm:   │
│  订单已支付  扣减库存    扣减余额    │
│                                    │
│  Cancel:    Cancel:     Cancel:    │
│  删除订单    释放库存    释放余额    │
└────────────────────────────────────┘
// TCC实现
interface TCCService
{
    public function try(): bool;
    public function confirm(): bool;
    public function cancel(): bool;
}

class OrderTCCService implements TCCService
{
    public function try(): bool
    {
        // 创建待支付订单
        DB::table('orders')->insert([
            'status' => 'PENDING',
            'amount' => 100,
        ]);
        return true;
    }
    
    public function confirm(): bool
    {
        // 确认订单
        DB::table('orders')
            ->where('id', $orderId)
            ->update(['status' => 'PAID']);
        return true;
    }
    
    public function cancel(): bool
    {
        // 取消订单
        DB::table('orders')
            ->where('id', $orderId)
            ->update(['status' => 'CANCELLED']);
        return true;
    }
}

class StockTCCService implements TCCService
{
    public function try(): bool
    {
        // 冻结库存
        $affected = DB::table('products')
            ->where('id', $productId)
            ->where('stock', '>=', $quantity)
            ->update([
                'stock' => DB::raw('stock - ' . $quantity),
                'stock_frozen' => DB::raw('stock_frozen + ' . $quantity),
            ]);
        
        return $affected > 0;
    }
    
    public function confirm(): bool
    {
        // 确认扣减(解冻)
        DB::table('products')
            ->where('id', $productId)
            ->update([
                'stock_frozen' => DB::raw('stock_frozen - ' . $quantity),
            ]);
        return true;
    }
    
    public function cancel(): bool
    {
        // 释放库存
        DB::table('products')
            ->where('id', $productId)
            ->update([
                'stock' => DB::raw('stock + ' . $quantity),
                'stock_frozen' => DB::raw('stock_frozen - ' . $quantity),
            ]);
        return true;
    }
}

// TCC协调器
class TCCCoordinator
{
    private $services = [];
    
    public function addService(TCCService $service)
    {
        $this->services[] = $service;
    }
    
    public function execute(): bool
    {
        // Try阶段
        foreach ($this->services as $service) {
            if (!$service->try()) {
                $this->cancelAll();
                return false;
            }
        }
        
        // Confirm阶段
        try {
            foreach ($this->services as $service) {
                $service->confirm();
            }
            return true;
        } catch (Exception $e) {
            $this->cancelAll();
            throw $e;
        }
    }
    
    private function cancelAll()
    {
        foreach ($this->services as $service) {
            $service->cancel();
        }
    }
}

3.6 SAGA模式

SAGA = 长事务拆分 + 补偿机制

流程:
T1 → T2 → T3 → ... → Tn

失败时:
T1 → T2 → T3(失败)
     ↓
C3 ← C2 ← C1 (补偿)

示例:电商下单
正向:
1. 创建订单 (T1)
2. 扣减库存 (T2)
3. 扣减余额 (T3)
4. 发送通知 (T4)

补偿:
1. 删除通知 (C4)
2. 退还余额 (C3)
3. 恢复库存 (C2)
4. 取消订单 (C1)
// SAGA实现
class SagaOrchestrator
{
    private $steps = [];
    
    public function addStep(callable $action, callable $compensation)
    {
        $this->steps[] = [
            'action' => $action,
            'compensation' => $compensation,
        ];
    }
    
    public function execute(): bool
    {
        $executedSteps = [];
        
        // 正向执行
        foreach ($this->steps as $index => $step) {
            try {
                $step['action']();
                $executedSteps[] = $index;
            } catch (Exception $e) {
                // 失败,执行补偿
                $this->compensate($executedSteps);
                return false;
            }
        }
        
        return true;
    }
    
    private function compensate(array $executedSteps)
    {
        // 逆序补偿
        foreach (array_reverse($executedSteps) as $index) {
            try {
                $this->steps[$index]['compensation']();
            } catch (Exception $e) {
                // 补偿失败,记录日志
                Log::error("Compensation failed for step {$index}");
            }
        }
    }
}

// 使用示例
$saga = new SagaOrchestrator();

// 步骤1: 创建订单
$saga->addStep(
    function () {
        $orderId = DB::table('orders')->insertGetId([...]);
        Context::set('order_id', $orderId);
    },
    function () {
        $orderId = Context::get('order_id');
        DB::table('orders')->where('id', $orderId)->delete();
    }
);

// 步骤2: 扣减库存
$saga->addStep(
    function () {
        $affected = DB::table('products')
            ->where('id', $productId)
            ->where('stock', '>=', $quantity)
            ->decrement('stock', $quantity);
        
        if ($affected === 0) {
            throw new Exception('库存不足');
        }
    },
    function () {
        DB::table('products')
            ->where('id', $productId)
            ->increment('stock', $quantity);
    }
);

// 步骤3: 扣减余额
$saga->addStep(
    function () {
        $affected = DB::table('accounts')
            ->where('id', $userId)
            ->where('balance', '>=', $amount)
            ->decrement('balance', $amount);
        
        if ($affected === 0) {
            throw new Exception('余额不足');
        }
    },
    function () {
        DB::table('accounts')
            ->where('id', $userId)
            ->increment('balance', $amount);
    }
);

$saga->execute();

3.7 本地消息表

原理:利用本地事务 + 消息队列

流程:
1. 本地事务:
   - 执行业务操作
   - 插入消息表
   - 提交事务

2. 定时任务:
   - 扫描消息表
   - 发送到消息队列

3. 消费者:
   - 处理消息
   - 更新消息状态
// 本地消息表实现
class LocalMessageTable
{
    public function createOrder(array $orderData)
    {
        DB::beginTransaction();
        
        try {
            // 1. 创建订单
            $orderId = DB::table('orders')->insertGetId($orderData);
            
            // 2. 插入消息表
            DB::table('local_messages')->insert([
                'message_id' => uniqid(),
                'topic' => 'order.created',
                'payload' => json_encode(['order_id' => $orderId]),
                'status' => 'PENDING',
                'created_at' => time(),
            ]);
            
            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
    
    /**
     * 定时任务:发送待发送的消息
     */
    public function sendPendingMessages()
    {
        $messages = DB::table('local_messages')
            ->where('status', 'PENDING')
            ->where('created_at', '<', time() - 60)  // 1分钟前的消息
            ->limit(100)
            ->get();
        
        foreach ($messages as $message) {
            try {
                // 发送到消息队列
                $this->producer->send($message->topic, $message->payload);
                
                // 更新状态
                DB::table('local_messages')
                    ->where('id', $message->id)
                    ->update(['status' => 'SENT']);
            } catch (Exception $e) {
                Log::error("发送消息失败: {$message->id}");
            }
        }
    }
}

// 消费者
class OrderCreatedConsumer
{
    public function consume($message)
    {
        $data = json_decode($message, true);
        $orderId = $data['order_id'];
        
        // 处理业务(幂等性)
        $this->stockService->decrease($orderId);
        $this->accountService->deduct($orderId);
    }
}

3.8 Seata框架

Seata: 阿里开源的分布式事务解决方案

架构:
┌──────────────┐
│   TC (事务协调器)  │  ← Seata Server
└───────┬──────┘
        │
┌───────┼──────────┐
│       │          │
▼       ▼          ▼
TM      RM         RM
(事务管理器)(资源管理器)

模式:
1. AT模式: 自动补偿(推荐)
2. TCC模式: 手动补偿
3. SAGA模式: 长事务
4. XA模式: 强一致性
// Hyperf + Seata
use Hyperf\Seata\Annotation\GlobalTransactional;

class OrderService
{
    #[GlobalTransactional(timeoutMills: 60000)]
    public function createOrder(array $orderData)
    {
        // 1. 创建订单
        $orderId = $this->saveOrder($orderData);
        
        // 2. RPC调用:扣减库存
        $this->stockService->decrease($orderData['product_id'], $orderData['quantity']);
        
        // 3. RPC调用:扣减余额
        $this->accountService->deduct($orderData['user_id'], $orderData['amount']);
        
        return $orderId;
    }
}

// Seata自动处理:
// - 成功:提交所有分支事务
// - 失败:回滚所有分支事务

4. 事务隔离级别

4.1 并发问题

脏读(Dirty Read)
-- 读到未提交的数据

时刻  事务A                    事务B
T1    BEGIN;
T2                             BEGIN;
T3                             UPDATE users SET age = 30 WHERE id = 1;
T4    SELECT age FROM users WHERE id = 1;  -- 读到30 ❌
T5                             ROLLBACK;  -- 回滚
T6    -- 读到的30是无效数据

问题:事务B回滚,事务A读到的是脏数据
不可重复读(Non-Repeatable Read)
-- 同一事务内多次读取,结果不同

时刻  事务A                    事务B
T1    BEGIN;
T2    SELECT age FROM users WHERE id = 1;  -- 读到25
T3                             BEGIN;
T4                             UPDATE users SET age = 30 WHERE id = 1;
T5                             COMMIT;
T6    SELECT age FROM users WHERE id = 1;  -- 读到30
T7    COMMIT;

问题:同一事务内,两次读取结果不同
幻读(Phantom Read)
-- 同一事务内,读到不同数量的记录

时刻  事务A                    事务B
T1    BEGIN;
T2    SELECT COUNT(*) FROM users WHERE age > 20;  -- 10条
T3                             BEGIN;
T4                             INSERT INTO users (age) VALUES (25);
T5                             COMMIT;
T6    SELECT COUNT(*) FROM users WHERE age > 20;  -- 11条
T7    COMMIT;

问题:同一事务内,读到新增的记录

4.2 隔离级别

隔离级别          脏读    不可重复读    幻读
─────────────────────────────────────────
READ UNCOMMITTED   ✓        ✓          ✓
READ COMMITTED     ✗        ✓          ✓
REPEATABLE READ    ✗        ✗          ✗  ← MySQL默认
SERIALIZABLE       ✗        ✗          ✗
READ UNCOMMITTED(读未提交)
-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 特点:
-- ✓ 性能最高
-- ✗ 可能脏读
-- 使用场景:日志、统计(对一致性要求低)
READ COMMITTED(读已提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 特点:
-- ✓ 避免脏读
-- ✗ 可能不可重复读
-- 使用场景:Oracle、PostgreSQL默认级别
REPEATABLE READ(可重复读)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 特点:
-- ✓ 避免脏读、不可重复读
-- ✓ MySQL通过MVCC+Next-Key Lock避免幻读
-- 使用场景:MySQL默认级别
SERIALIZABLE(串行化)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 特点:
-- ✓ 最高隔离级别,完全避免并发问题
-- ✗ 性能最低(所有操作串行)
-- 使用场景:金融系统(强一致性要求)

4.3 MVCC实现原理

MVCC (Multi-Version Concurrency Control)
多版本并发控制

核心:每个事务看到的是数据的一个快照

实现:
1. 每行记录有隐藏字段:
   - trx_id: 事务ID
   - roll_ptr: 回滚指针
   - delete_bit: 删除标记

2. Undo Log保存旧版本

3. Read View决定可见性

示例:
初始: id=1, name='Tom', trx_id=100

T1(trx_id=101): BEGIN;
T2(trx_id=102): BEGIN;
T2: UPDATE users SET name='John' WHERE id=1;  -- trx_id=102
T2: COMMIT;

T1: SELECT * FROM users WHERE id=1;
-- REPEATABLE READ: name='Tom' (trx_id=100 < 101)
-- READ COMMITTED: name='John' (trx_id=102已提交)

版本链:
当前版本: id=1, name='John', trx_id=102, roll_ptr → 旧版本
旧版本:   id=1, name='Tom', trx_id=100, roll_ptr → NULL

5. 事务实战

5.1 电商下单

class OrderService
{
    /**
     * 创建订单(本地事务)
     */
    public function createOrder(int $userId, int $productId, int $quantity): int
    {
        return DB::transaction(function () use ($userId, $productId, $quantity) {
            // 1. 锁定商品,检查库存
            $product = DB::table('products')
                ->where('id', $productId)
                ->lockForUpdate()
                ->first();
            
            if ($product->stock < $quantity) {
                throw new Exception('库存不足');
            }
            
            // 2. 锁定用户,检查余额
            $user = DB::table('users')
                ->where('id', $userId)
                ->lockForUpdate()
                ->first();
            
            $amount = $product->price * $quantity;
            
            if ($user->balance < $amount) {
                throw new Exception('余额不足');
            }
            
            // 3. 扣减库存
            DB::table('products')
                ->where('id', $productId)
                ->decrement('stock', $quantity);
            
            // 4. 扣减余额
            DB::table('users')
                ->where('id', $userId)
                ->decrement('balance', $amount);
            
            // 5. 创建订单
            $orderId = DB::table('orders')->insertGetId([
                'user_id' => $userId,
                'product_id' => $productId,
                'quantity' => $quantity,
                'amount' => $amount,
                'status' => 'PAID',
                'created_at' => time(),
            ]);
            
            // 6. 创建订单明细
            DB::table('order_items')->insert([
                'order_id' => $orderId,
                'product_id' => $productId,
                'price' => $product->price,
                'quantity' => $quantity,
            ]);
            
            return $orderId;
        });
    }
}

5.2 分布式下单(SAGA)

class DistributedOrderService
{
    public function createOrder(array $orderData)
    {
        $saga = new SagaOrchestrator();
        
        // 步骤1: 创建订单
        $saga->addStep(
            function () use ($orderData) {
                $orderId = $this->rpcClient->call('OrderService', 'create', [$orderData]);
                Context::set('order_id', $orderId);
                return $orderId;
            },
            function () {
                $orderId = Context::get('order_id');
                $this->rpcClient->call('OrderService', 'cancel', [$orderId]);
            }
        );
        
        // 步骤2: 扣减库存
        $saga->addStep(
            function () use ($orderData) {
                $result = $this->rpcClient->call('StockService', 'decrease', [
                    $orderData['product_id'],
                    $orderData['quantity']
                ]);
                
                if (!$result) {
                    throw new Exception('库存不足');
                }
            },
            function () use ($orderData) {
                $this->rpcClient->call('StockService', 'increase', [
                    $orderData['product_id'],
                    $orderData['quantity']
                ]);
            }
        );
        
        // 步骤3: 扣减余额
        $saga->addStep(
            function () use ($orderData) {
                $result = $this->rpcClient->call('AccountService', 'deduct', [
                    $orderData['user_id'],
                    $orderData['amount']
                ]);
                
                if (!$result) {
                    throw new Exception('余额不足');
                }
            },
            function () use ($orderData) {
                $this->rpcClient->call('AccountService', 'refund', [
                    $orderData['user_id'],
                    $orderData['amount']
                ]);
            }
        );
        
        // 步骤4: 发送通知
        $saga->addStep(
            function () {
                $orderId = Context::get('order_id');
                $this->rpcClient->call('NotificationService', 'send', [$orderId]);
            },
            function () {
                // 通知不需要补偿
            }
        );
        
        return $saga->execute();
    }
}

5.3 幂等性保证

/**
 * 接口幂等性实现
 */
class IdempotentService
{
    /**
     * 方案1:唯一索引
     */
    public function createOrder(string $requestId, array $orderData)
    {
        try {
            DB::table('orders')->insert([
                'request_id' => $requestId,  // 唯一索引
                'user_id' => $orderData['user_id'],
                'amount' => $orderData['amount'],
            ]);
        } catch (\PDOException $e) {
            // 唯一索引冲突,说明已经创建过
            if ($e->getCode() == 23000) {
                return DB::table('orders')
                    ->where('request_id', $requestId)
                    ->first();
            }
            throw $e;
        }
    }
    
    /**
     * 方案2:状态机
     */
    public function payOrder(int $orderId)
    {
        DB::beginTransaction();
        
        try {
            // 锁定订单
            $order = DB::table('orders')
                ->where('id', $orderId)
                ->lockForUpdate()
                ->first();
            
            // 检查状态
            if ($order->status !== 'PENDING') {
                throw new Exception('订单状态错误');
            }
            
            // 支付逻辑
            $this->doPayment($order);
            
            // 更新状态
            DB::table('orders')
                ->where('id', $orderId)
                ->where('status', 'PENDING')  // 再次检查
                ->update(['status' => 'PAID']);
            
            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
    
    /**
     * 方案3:Token机制
     */
    public function submitWithToken(string $token, array $data)
    {
        // 验证并删除token(原子操作)
        $script = <<<LUA
        if redis.call('get', KEYS[1]) == ARGV[1] then
            return redis.call('del', KEYS[1])
        else
            return 0
        end
        LUA;
        
        $result = $this->redis->eval($script, ["token:{$token}", 1], 1);
        
        if ($result === 0) {
            throw new Exception('Token无效或已使用');
        }
        
        // 执行业务逻辑
        return $this->doSubmit($data);
    }
}

6. 性能优化

6.1 减少事务范围

// ❌ 事务过大
DB::transaction(function () {
    // 耗时操作
    $data = $this->fetchExternalAPI();  // 1秒
    sleep(2);  // 模拟计算
    
    // 数据库操作
    DB::table('orders')->insert($data);
});

// ✅ 缩小事务范围
$data = $this->fetchExternalAPI();  // 在事务外
sleep(2);

DB::transaction(function () use ($data) {
    // 只在事务内执行必要的数据库操作
    DB::table('orders')->insert($data);
});

6.2 批量操作

// ❌ 逐条插入
DB::transaction(function () {
    foreach ($users as $user) {
        DB::table('users')->insert($user);  // N次
    }
});

// ✅ 批量插入
DB::transaction(function () use ($users) {
    DB::table('users')->insert($users);  // 1次
});

6.3 降低隔离级别

// 对一致性要求不高的场景
DB::statement('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED');

DB::transaction(function () {
    // 统计查询
    $count = DB::table('orders')->count();
});

// 性能提升:避免锁等待

6.4 异步化

// ❌ 同步处理
DB::transaction(function () {
    // 创建订单
    $orderId = DB::table('orders')->insertGetId([...]);
    
    // 发送邮件(耗时)
    $this->mailer->send(...);  // 2秒
    
    // 发送短信(耗时)
    $this->sms->send(...);  // 1秒
});

// ✅ 异步处理
$orderId = DB::transaction(function () {
    return DB::table('orders')->insertGetId([...]);
});

// 异步发送通知
$this->queue->push(new SendEmailJob($orderId));
$this->queue->push(new SendSmsJob($orderId));

7. 常见问题

7.1 长事务问题

问题:
- 长时间持有锁
- 阻塞其他事务
- 回滚日志增大

排查:
SELECT * FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 10;

解决:
1. 拆分事务
2. 异步化
3. 设置超时
// 设置事务超时
DB::statement('SET SESSION MAX_EXECUTION_TIME=5000');  // 5秒

DB::transaction(function () {
    // ...
});

7.2 死锁排查

-- 查看最近死锁
SHOW ENGINE INNODB STATUS\G

-- 输出:
LATEST DETECTED DEADLOCK
------------------------
2025-10-28 10:00:00
*** (1) TRANSACTION:
TRANSACTION 1234, ACTIVE 5 sec starting index read
UPDATE accounts SET balance = balance - 100 WHERE id = 1

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS on table `test`.`accounts` trx id 1234 lock_mode X locks rec but not gap waiting

*** (2) TRANSACTION:
TRANSACTION 1235, ACTIVE 3 sec updating
UPDATE accounts SET balance = balance + 100 WHERE id = 2

*** WE ROLL BACK TRANSACTION (1)

解决方案

  1. 按固定顺序获取锁
  2. 减小事务粒度
  3. 添加重试机制

7.3 事务回滚

class TransactionService
{
    /**
     * 手动回滚
     */
    public function processOrder()
    {
        DB::beginTransaction();
        
        try {
            // 业务逻辑
            if ($error) {
                DB::rollBack();
                return false;
            }
            
            DB::commit();
            return true;
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
    
    /**
     * 设置回滚点
     */
    public function complexTransaction()
    {
        DB::beginTransaction();
        
        try {
            // 步骤1
            DB::table('orders')->insert([...]);
            
            // 设置回滚点
            DB::statement('SAVEPOINT sp1');
            
            // 步骤2
            DB::table('order_items')->insert([...]);
            
            if ($error) {
                // 回滚到回滚点
                DB::statement('ROLLBACK TO SAVEPOINT sp1');
                // 只回滚步骤2,保留步骤1
            }
            
            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
}

8. 面试高频题

Q1: 事务的ACID特性?

A: 见1.2节

核心记忆

  • Atomicity: 要么全成功,要么全失败
  • Consistency: 数据前后一致
  • Isolation: 事务互不干扰
  • Durability: 提交后永久保存

Q2: 事务隔离级别有哪些?有什么区别?

A: 见4.2节

隔离级别脏读不可重复读幻读性能
RU可能可能可能最高
RC不会可能可能
RR不会不会不会
S不会不会不会最低

选择依据

  • 金融系统:SERIALIZABLE
  • 一般应用:REPEATABLE READ(MySQL默认)
  • 高并发:READ COMMITTED

Q3: MySQL如何实现MVCC?

A:

核心机制

  1. 隐藏字段

    • trx_id: 事务ID
    • roll_ptr: 回滚指针
    • delete_bit: 删除标记
  2. Undo Log:保存旧版本数据

  3. Read View:事务快照

    • m_ids: 活跃事务列表
    • min_trx_id: 最小活跃事务ID
    • max_trx_id: 下一个事务ID

可见性判断

if (trx_id < min_trx_id) {
    可见  // 在当前事务之前提交
} else if (trx_id >= max_trx_id) {
    不可见  // 在当前事务之后开始
} else if (trx_id in m_ids) {
    不可见  // 活跃事务(未提交)
} else {
    可见  // 已提交事务
}

Q4: 什么是分布式事务?有哪些解决方案?

A:

定义:跨多个数据库、服务的事务

解决方案

  1. 2PC(两阶段提交)

    • 强一致性
    • 性能低、单点故障
  2. TCC(Try-Confirm-Cancel)

    • 手动补偿
    • 实现复杂、性能好
  3. SAGA

    • 长事务
    • 最终一致性
  4. 本地消息表

    • 简单可靠
    • 有延迟
  5. Seata

    • 自动化
    • 需要额外组件

选择

  • 强一致性:2PC、XA
  • 高性能:TCC、SAGA
  • 简单可靠:本地消息表

Q5: 如何解决分布式事务?

A: 实战案例

// 场景:电商下单(订单、库存、账户三个服务)

// 方案1:SAGA模式(推荐)
$saga = new SagaOrchestrator();

$saga->addStep(
    fn() => $orderService->create($orderData),
    fn() => $orderService->cancel($orderId)
);

$saga->addStep(
    fn() => $stockService->decrease($productId, $quantity),
    fn() => $stockService->increase($productId, $quantity)
);

$saga->addStep(
    fn() => $accountService->deduct($userId, $amount),
    fn() => $accountService->refund($userId, $amount)
);

$saga->execute();

// 方案2:本地消息表(简单)
DB::transaction(function () {
    // 1. 创建订单
    $orderId = DB::table('orders')->insertGetId([...]);
    
    // 2. 插入消息表
    DB::table('messages')->insert([
        'topic' => 'order.created',
        'payload' => json_encode(['order_id' => $orderId]),
        'status' => 'PENDING',
    ]);
});

// 定时任务发送消息
// 消费者处理后续操作(库存、账户)

// 方案3:Seata(自动化)
#[GlobalTransactional]
public function createOrder($orderData) {
    $orderId = $orderService->create($orderData);
    $stockService->decrease(...);
    $accountService->deduct(...);
    return $orderId;
}

总结

核心要点

  1. ACID特性:原子性、一致性、隔离性、持久性
  2. 隔离级别:RU、RC、RR、S及其权衡
  3. MVCC:多版本并发控制原理
  4. 分布式事务:2PC、TCC、SAGA、本地消息表
  5. 实战:电商下单、库存扣减、幂等性

面试建议

  • 理解ACID原理,不只是记概念
  • 掌握不同隔离级别的适用场景
  • 熟悉分布式事务的常用解决方案
  • 准备实际项目中的事务处理案例
  • 了解性能优化技巧

推荐资源:

  • 《MySQL技术内幕:InnoDB存储引擎》
  • 《分布式系统原理与范型》
  • 《高性能MySQL》
  • 《数据密集型应用系统设计》(DDIA)