事务处理完全指南
文档类型: 数据库事务与分布式事务
更新时间: 2025-10-28
目录
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)
解决方案:
- 按固定顺序获取锁
- 减小事务粒度
- 添加重试机制
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:
核心机制:
-
隐藏字段:
trx_id: 事务IDroll_ptr: 回滚指针delete_bit: 删除标记
-
Undo Log:保存旧版本数据
-
Read View:事务快照
m_ids: 活跃事务列表min_trx_id: 最小活跃事务IDmax_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:
定义:跨多个数据库、服务的事务
解决方案:
-
2PC(两阶段提交)
- 强一致性
- 性能低、单点故障
-
TCC(Try-Confirm-Cancel)
- 手动补偿
- 实现复杂、性能好
-
SAGA
- 长事务
- 最终一致性
-
本地消息表
- 简单可靠
- 有延迟
-
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;
}
总结
核心要点
- ACID特性:原子性、一致性、隔离性、持久性
- 隔离级别:RU、RC、RR、S及其权衡
- MVCC:多版本并发控制原理
- 分布式事务:2PC、TCC、SAGA、本地消息表
- 实战:电商下单、库存扣减、幂等性
面试建议
- 理解ACID原理,不只是记概念
- 掌握不同隔离级别的适用场景
- 熟悉分布式事务的常用解决方案
- 准备实际项目中的事务处理案例
- 了解性能优化技巧
推荐资源:
- 《MySQL技术内幕:InnoDB存储引擎》
- 《分布式系统原理与范型》
- 《高性能MySQL》
- 《数据密集型应用系统设计》(DDIA)