mcpField
B
benchmarking-transaction-patterns

benchmarking-transaction-patterns

github.com/cockroachlabs数据与数据库23127 次安装14 周前更新
代码仓库

关于 benchmarking-transaction-patterns

面向 CockroachDB 开发者的基准测试技能,用于比较显式多语句事务与单语句 CTE 事务。内容侧重并发竞争下的公平测试、重试、延迟和吞吐量分析。

主要功能

  1. 01准备独立的基准测试数据库
  2. 02测试显式事务与 CTE 事务
  3. 03重置数据以公平比较
  4. 04比较吞吐量、重试和延迟
  5. 05验证基准测试完整性

适用场景

  1. 01比较并发竞争下的事务设计
  2. 02评估重试与尾部延迟表现
  3. 03在生产变更前检查基准结果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cockroachlabs/cockroachdb-skills --skill benchmarking-transaction-patterns

安装后在 Claude Code 中输入/benchmarking-transaction-patterns确认已加载。

详细介绍

事务模式基准测试

指导用户对 CockroachDB 中同一事务型业务工作流的两种实现进行基准测试、说明和比较:显式多语句事务与单语句 CTE 事务。重点关注竞争条件下的性能、公平的测试方法以及结果解读。

**设计技能的补充:**如需了解通用事务设计原则,请参阅 designing-application-transactions。如需了解 SQL 语法和查询模式,请参阅 cockroachdb-sql。

核心概念

在存在竞争时,事务的实现方式本身就是影响性能的主要因素。显式模式(多语句 BEGIN/COMMIT)会在多次往返期间保持事务打开,从而扩大竞争窗口。CTE 模式(单语句)将相同逻辑合并为一个原子语句,缩短事务持续时间并减少重试。

显式事务模式
BEGIN;
SELECT balance FROM accounts WHERE id = $1;
-- Application decides whether transfer is allowed
UPDATE accounts SET balance = balance - $2 WHERE id = $1;
UPDATE accounts SET balance = balance + $2 WHERE id = $3;
INSERT INTO transfers (from_acct, to_acct, amount, created_at)
VALUES ($1, $3, $2, now());
COMMIT;
CTE 事务模式

CockroachDB 默认会拒绝在单条语句中多次修改同一张表(sql.multiple_modifications_of_table.enabled),因此会使用 CASE 将借记和贷记合并到同一条 UPDATE 中。

WITH funded AS (
  SELECT 1 FROM accounts WHERE id = $1 AND balance >= $2
), upd AS (
  UPDATE accounts
  SET balance = CASE WHEN id = $1 THEN balance - $2 ELSE balance + $2 END
  WHERE id IN ($1, $3) AND EXISTS (SELECT 1 FROM funded)
  RETURNING id
), ins AS (
  INSERT INTO transfers (from_acct, to_acct, amount, created_at)
  SELECT $1, $3, $2, now() WHERE (SELECT count(*) FROM upd) = 2
  RETURNING id
)
SELECT id FROM ins;

步骤

1. 准备基准测试环境

设置专用的测试数据库和架构。不要将基准测试工作负载与其他流量混用。

CREATE DATABASE IF NOT EXISTS bankbench;
USE bankbench;

CREATE TABLE accounts (
  id INT PRIMARY KEY,
  balance DECIMAL(18,2) NOT NULL DEFAULT 0
);

CREATE TABLE transfers (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  from_acct INT NOT NULL,
  to_acct INT NOT NULL,
  amount DECIMAL(18,2) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
2. 填充测试数据

使用多行 UPSERT 来提高填充效率。单行插入会扭曲环境准备成本。

INSERT INTO accounts (id, balance)
SELECT generate_series(1, 10000), 1000.00
ON CONFLICT (id) DO UPDATE SET balance = 1000.00;
3. 运行显式事务基准测试

使用贴近实际的并发度执行。以下是使用 pgbench(兼容 PostgreSQL)的示例:

# Create a pgbench script file: explicit_transfer.sql
# \set from_id random(1, 10000)
# \set to_id random(1, 10000)
# \set amount 10.00
# BEGIN;
# SELECT balance FROM accounts WHERE id = :from_id;
# UPDATE accounts SET balance = balance - :amount WHERE id = :from_id;
# UPDATE accounts SET balance = balance + :amount WHERE id = :to_id;
# INSERT INTO transfers (from_acct, to_acct, amount, created_at) VALUES (:from_id, :to_id, :amount, now());
# COMMIT;

pgbench -n -c 64 -j 8 -T 120 -f explicit_transfer.sql \
  "postgresql://root@localhost:26257/bankbench?sslmode=disable"

记录吞吐量(tps)、重试次数、p50/p95/p99 延迟、最大延迟和失败次数。

4. 在两次运行之间重置,以确保公平比较

为确保基准测试公平,在显式模式和 CTE 模式运行之间重置账户余额,使表大小、索引大小和账户状态保持可比。

UPDATE accounts SET balance = 1000.00;
5. 运行 CTE 事务基准测试

使用与显式模式运行相同的并发度、持续时间和参数执行:

# Create a pgbench script file: cte_transfer.sql containing the CTE query above
pgbench -n -c 64 -j 8 -T 120 -f cte_transfer.sql \
  "postgresql://root@localhost:26257/bankbench?sslmode=disable"
6. 比较结果

务必并排比较以下指标:

指标关注点
吞吐量(txn/s)越高越好;CTE 通常能在竞争条件下维持更高吞吐量
总重试次数CTE 通常可将重试次数降至接近零
p50 延迟事务耗时中位数
p95 延迟中等竞争下的尾延迟
p99 延迟最差情况下的尾延迟;显式模式通常会出现峰值
最大延迟离群情况
失败次数不可重试的错误
7. 验证基准测试的完整性

解读结果前,先确认基准测试顺利完成:

-- Confirm expected transfer volume
SELECT COUNT(*) AS total_transfers FROM transfers;
# Check node liveness and start times (no node restarts mid-benchmark)
cockroach node status --certs-dir=<certs-dir>     # or --insecure for an insecure cluster

基准测试参考结果

在一项报告的高竞争测试中,两种模式的对比结果如下:

指标显式模式CTE 模式变化
吞吐量591.1 txn/s1,035.1 txn/s+75.1%
总耗时216.5s123.7s-42.9%
平均延迟202.2 ms111.3 ms-45.0%
总重试次数2,270,9770-100%

在更高总量的扩展测试中,结果方向保持一致:显式模式继续累积重试,并偶尔出现失败;而 CTE 模式始终没有重试,也没有失败。

影响摘要
维度显式多语句单语句 CTE
往返次数多次客户端/服务端交互单次请求
事务持续时间更长更短
客户端重试复杂度更高更低
原子不变量的强制执行分散在多个语句/应用逻辑中集中在 SQL 中
预期吞吐量争用时较低争用时较高
客户端可见的重试更可能发生通常会减少

决策指南

适合使用显式模式的情况
  • 业务工作流确实无法通过一条 SQL 语句清晰表达
  • 可读性或分阶段业务逻辑比峰值吞吐量更重要
  • 争用程度较低,重试放大效应并非主要成本
适合使用 CTE 模式的情况
  • 工作流存在大量争用
  • 操作天然具有原子性
  • 应用当前通过多个语句执行读取、判断和写入
  • 主要目标是提高吞吐量、减少重试,并让 p95/p99 延迟更稳定

公平的基准测试规则

  1. 每次运行之间重置数据,确保余额、表大小和索引大小保持一致,以便公平比较
  2. 将不重置数据的运行视为演示,而非同类基准测试
  3. 需要每次只处理一个业务单元时使用 --batch-size=1,以便进行清晰比较
  4. 比较恰当的指标——始终包含吞吐量、重试次数、p50、p95、p99、最大延迟和失败次数
  5. 使用多行 UPSERT 填充数据——单行填充会扭曲设置成本

常见误解

  • “CTE 总是更胜一筹”——只有在工作流对争用敏感且可以表达为单条原子语句时才是如此。
  • “SQL Activity 显示等待,就说明 CTE 失败了”——CTE 能减少争用,但无法消除争用。请比较吞吐量、尾延迟和重试情况。
  • “单语句意味着没有争用”——CTE 会缩短争用窗口,但无法消除争用。

安全注意事项

  • 在专用测试集群上运行基准测试,不要在生产环境中运行
  • 每次运行之间重置数据,以便公平比较
  • 基准测试期间监控集群健康状况
  • 使用真实但不会造成破坏的并发级别
  • 在进行生产环境变更前,验证基准测试结果是否适用于你的特定工作负载

参考资料

评论

支持 Markdown