关于 benchmarking-transaction-patterns
面向 CockroachDB 开发者的基准测试技能,用于比较显式多语句事务与单语句 CTE 事务。内容侧重并发竞争下的公平测试、重试、延迟和吞吐量分析。
主要功能
- 01准备独立的基准测试数据库
- 02测试显式事务与 CTE 事务
- 03重置数据以公平比较
- 04比较吞吐量、重试和延迟
- 05验证基准测试完整性
适用场景
- 01比较并发竞争下的事务设计
- 02评估重试与尾部延迟表现
- 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/s | 1,035.1 txn/s | +75.1% |
| 总耗时 | 216.5s | 123.7s | -42.9% |
| 平均延迟 | 202.2 ms | 111.3 ms | -45.0% |
| 总重试次数 | 2,270,977 | 0 | -100% |
在更高总量的扩展测试中,结果方向保持一致:显式模式继续累积重试,并偶尔出现失败;而 CTE 模式始终没有重试,也没有失败。
影响摘要
| 维度 | 显式多语句 | 单语句 CTE |
|---|---|---|
| 往返次数 | 多次客户端/服务端交互 | 单次请求 |
| 事务持续时间 | 更长 | 更短 |
| 客户端重试复杂度 | 更高 | 更低 |
| 原子不变量的强制执行 | 分散在多个语句/应用逻辑中 | 集中在 SQL 中 |
| 预期吞吐量 | 争用时较低 | 争用时较高 |
| 客户端可见的重试 | 更可能发生 | 通常会减少 |
决策指南
适合使用显式模式的情况
- 业务工作流确实无法通过一条 SQL 语句清晰表达
- 可读性或分阶段业务逻辑比峰值吞吐量更重要
- 争用程度较低,重试放大效应并非主要成本
适合使用 CTE 模式的情况
- 工作流存在大量争用
- 操作天然具有原子性
- 应用当前通过多个语句执行读取、判断和写入
- 主要目标是提高吞吐量、减少重试,并让 p95/p99 延迟更稳定
公平的基准测试规则
- 每次运行之间重置数据,确保余额、表大小和索引大小保持一致,以便公平比较
- 将不重置数据的运行视为演示,而非同类基准测试
- 需要每次只处理一个业务单元时使用
--batch-size=1,以便进行清晰比较 - 比较恰当的指标——始终包含吞吐量、重试次数、p50、p95、p99、最大延迟和失败次数
- 使用多行 UPSERT 填充数据——单行填充会扭曲设置成本
常见误解
- “CTE 总是更胜一筹”——只有在工作流对争用敏感且可以表达为单条原子语句时才是如此。
- “SQL Activity 显示等待,就说明 CTE 失败了”——CTE 能减少争用,但无法消除争用。请比较吞吐量、尾延迟和重试情况。
- “单语句意味着没有争用”——CTE 会缩短争用窗口,但无法消除争用。
安全注意事项
- 在专用测试集群上运行基准测试,不要在生产环境中运行
- 每次运行之间重置数据,以便公平比较
- 基准测试期间监控集群健康状况
- 使用真实但不会造成破坏的并发级别
- 在进行生产环境变更前,验证基准测试结果是否适用于你的特定工作负载
参考资料
- CockroachDB 事务文档
- 高级客户端事务重试
- 性能最佳实践
- 比较多语句与单语句事务
- 使用 CockroachDB 执行基于集合的操作
- 深入解析事务重试失败
- 排查 CockroachDB 性能问题
- CockroachDB 事务演示
- CockroachDB 最佳实践与反模式演示 -- 演示 1-2 展示并发情况下的重试模式和争用扩展情况