TPC-H 3.0.1 数据仓库压测实战:dbgen造数与22条查询校验
简介TPC-H V 3.0.1 是面向数据仓库与企业级数据库性能评测的标准化基准测试套件适合数据库开发、数据分析及性能调优人员用来模拟复杂商业查询场景验证系统在数据处理与查询响应上的表现。压缩包共约2000个文件、24.07MB包含27个SQL查询脚本、多组表数据分片及配套参数与说明文档覆盖查询定义、数据集组织和测试配置等环节便于按目录快速定位与复现实验。已有335人学习下载。其价值在于提供完整的基准测试素材读者可据此搭建测试环境、运行标准查询、观察不同数据库的执行差异并借助参数文件与数据分片开展规模调整和结果对比为性能评估、教学演示和方案选型提供可复用的参考依据尤其适合需要理解TPC-H查询集与数据集生成逻辑的读者进行二次实验。1. 数据仓库压测别只看跑分TPC-H 3.0.1 这套包拆开到底给了什么上周帮某团队复盘一次数据仓库选型两边报的 TPC-H 跑分差了 3 倍继续排查发现一边是 SF1 单流跑、一边是 SF100 开并行口径根本不在一条线上。这类对比要想站得住手上得有一套谁都能复现的造数工具和固定的查询集TPC-H 3.0.1 做的就是这件事dbgen 按规模因子生成 8 张关系表配套 22 条固定查询、一份规范文档和校验脚本。它适合正在做数据仓库选型、SQL 引擎调优、或者要给团队定一套回归基线的人也适合做数据分析时想要一份结构规整、量级可控的样本数据练手。2. 拆包dbgen、22 条 SQL、规范与校验脚本各管一段先把包里的东西摆清楚不然很容易把“跑通一条 SQL”当成“跑完了 TPC-H”。这份资源本质是一套可复现的测试协议 配套工具工具负责把数据造出来、把参数填进去协议负责规定你这套动作算不算数。两者的边界感很重要工具给你自由协议约束你不能随便改。2.1 四个组成部分和它们各自的职责拿到包先看目录不用急着编译。常见的结构大致是这样不同发行包的目录名会略有出入以包里 README 为准目录/文件作用什么时候用dbgen/数据生成器源码含makefile.suite、dists.dss造数阶段编译一次反复用queries/22 条查询模板参数写成:1、:2占位跑查询、做回归reference/或规范文档协议正文定义规模因子、计时口径、校验规则要对结果负责、要对外报数时校验/期望结果相关文件SF1 的期望结果或校验脚本验证自己没把数据搞坏这四块里最容易被忽略的是最后一块。我见过不止一次数据造歪了、加载时字段错位查询照样出结果只是数字不对。所以我的习惯是造完数第一件事不是连库而是拿校验文件对一遍确认这堆.tbl是“标准的那一堆”。dists.dss这个文件单独说一句。它是数据分布的取值字典dbgen和qgen都依赖它且必须在当前工作目录或通过-d指到。很多人换机器后直接./dbgen报找不到分布文件就是这个原因。它同时决定了生成数据的取值集合你在里面改动一个词条生成的数据就不再是标准 TPC-H 数据了——这点在做「自定义测试数据」时是特性在做「标准压测」时是玄学别乱动。2.2 规模因子 SF 换算成 GB 和行数一张能对得上的表规模因子Scale FactorSF是整套东西的锚点。SF1 名义上是 1GB 原始数据.tbl文本实际磁盘占用更大灌进库里的体积又不一样——有压缩、有索引、有副本所以别拿“SF1 就是 1GB”到处套。真正可靠的是行数行数是确定的SF名义体积lineitem 行数orders 行数备注0.01~10MB约 6 万1.5 万调试 SQL 用跑得快1~1GB6,001,2151,500,000事实上的入门标准结果可对校验10~10GB约 6000 万1500 万单机压力测试的常见档位100~100GB约 6 亿1.5 亿需要分片生成别单进程硬扛1000~1TB约 60 亿15 亿真·集群档考虑并行和分区几张维表的行数是固定的跟 SF 无关nation25 行、region5 行、supplier 10000 × SF、customer 150000 × SF、part 200000 × SF、partsupp 800000 × SF。这个规律很好用——你只要能背下来nation必须是 25 行加载完随手select count(*)就能判断有没有重复导入。lineitem一家占了总量的七成左右所以几乎所有性能讨论最后都落到它身上。SF 往上走的时候最先撑不住的是生成时间和磁盘 IO不是数据库。SF100 用单进程dbgen在我这儿跑过将近一小时后来改成按片并行才把时间压下来。2.3 3.0.1 和 2.x 的差别查询变体与校验口径3.0 系列最实质的变化是引入了查询变体query variant。简单说部分查询除了“标准写法”还有若干语义等价、写法不同的版本目的是防止有人针对某一版 SQL 做硬编码特化优化——你把这版 SQL 的特征写进优化器规则里换个变体就露馅。这对做引擎的人来说是好事对只想跑个分的人来说是多了一倍的活。另一块是校验和计时口径的规定更细什么算一次有效的 power run、refresh stream 怎么跑、什么情况下结果作废条款比 2.x 严。3.0.1 是在这个系列上的修订主要是措辞和脚本层面的修正不会让数据本身变样。具体条款只能看包里的规范正文我上面说的是实际使用中能感受到的差别真要对外报数还是得逐条对。这里有个很现实的坑用 2.x 的基线去比 3.0.1 的结果本身就不成立。查询写法变了、变体引入了、校验规则变了数字有差异是正常的。要么统一版本要么在报告里写清楚版本号别含糊。3. 编译 dbgen 造数从 SF1 到 SF100 的命令、分片与核对造数是整条链路上最“一次性”但最容易出错的一环。这一步做歪了后面所有调优结论都是假的。所以下面每个命令我都带上“跑完看什么”。3.1 改 makefile.suite 的四个变量再编译dbgen是老派的 C 代码构建系统是手写 makefile。包里给的是makefile.suite得先复制成makefile再改四个变量其他保持默认cd dbgen cp makefile.suite makefile # 只改这四行其余别动 sed -i s/^CC[[:space:]]*.*/CC gcc/ makefile sed -i s/^DATABASE[[:space:]]*.*/DATABASE ORACLE/ makefile sed -i s/^MACHINE[[:space:]]*.*/MACHINE LINUX/ makefile sed -i s/^WORKLOAD[[:space:]]*.*/WORKLOAD TPCH/ makefile make # 别加 -j老式规则并发会打架 ls -l dbgen qgen逻辑说明DATABASE这个宏切换的是目标库相关的类型与日期处理分支常见做法是按你实际要灌的库类型选ORACLE / DB2 / SQLSERVER 之类选错最典型的表现是日期字段解析报错或者大数被截断。MACHINELINUX影响的是字节序和路径分隔符在 Linux/macOS 上基本都是这个值。WORKLOADTPCH必须对选错会编出别的基准的工具。编译出来两个可执行文件dbgen造数据qgen生成查询参数。如果make报一堆隐式声明警告不用管能出二进制就行报dists.dss not found是运行期的事跟编译无关。3.2 dbgen 参数逐个说清-s、-T、-f、-C、-S、-rdbgen的参数不多但每个都直接影响数据正确性# 最常用生成 SF1 全量数据-f 覆盖已有文件-v 打印详细过程 ./dbgen -s 1 -f -v # 只生成 lineitem 一张表调 SQL 时省时间 ./dbgen -s 1 -T l -f # 造一份非标准数据用于压测边界换随机种子 ./dbgen -s 10 -r 42 -f参数含义与取舍-s规模因子可以是小数-s 0.1但只有标准档位才有内置校验值。-T只生成指定表取值是c(customer)、l(lineitem)、o(orders)、p(part)、P(partsupp大写)、s(supplier)、n(nation)、r(region)。注意p和P是两个不同的表这个大小写坑我踩过。-f覆盖已存在的.tbl。不加的话遇到同名文件会报错退出脚本里忘了加就是半夜任务挂掉。-r换随机种子。默认种子是固定的所以同一 SF 每次生成的数据完全一致——这是好事做回归时数据不变变量只有一个。-C和-S配合做分片见下一节。生成完当前目录会多出 8 个.tbl文件分隔符是|每行末尾还有一个|。这个末尾分隔符是后面加载阶段最大的坑先记住。3.3 多进程分片生成与合并SF 上到 100 之后单进程太慢用-C分成几片和-S生成第几片并行。关键规则-C的值在所有进程里必须一致-S从 1 递增到-C。#!/usr/bin/env bash set -euo pipefail SF100 CHUNKS8 mkdir -p sf${SF} cd dbgen for i in $(seq 1 $CHUNKS); do # -b 让每个分片写成独立文件带编号后缀避免互相覆盖 ./dbgen -s $SF -C $CHUNKS -S $i -f -b done wait mv *.tbl.* ../sf${SF}/逻辑说明-b是分片模式的关键不加的话 8 个进程会往同一批.tbl文件里写最后得到一堆互相截断的垃圾。加了-b每片产出形如lineitem.tbl.1…lineitem.tbl.8的文件。用wait等全部结束再mv不然可能搬到半截文件。合并阶段lineitem、orders、partsupp这类大表可以直接cat lineitem.tbl.* lineitem.tbl小维表nation、region分片行为不一样可能会重复或者只有一片有内容一定先数行数再合并别闭眼 cat。分片还有一层含义如果你要做多流并发测试保留分片文件、按片灌进不同节点比合并后再切分省事得多。3.4 生成完先核对行数、校验和、日期边界三段核对缺一不可。第一段用 dbgen 自带的校验# -v 会打印各表行数和校验和跟内置期望值比对 ./dbgen -v -s 1对标准规模因子主要是 SF1它会直接告诉你哪张表对不上非标 SF 会提示无法校验那就走第二段自己数# check_rows.py —— 数 .tbl 行数跟理论值比 import sys, glob, os # SF1 的标准行数其它 SF 按倍数换算 EXPECT_SF1 { customer.tbl: 150_000, orders.tbl: 1_500_000, lineitem.tbl: 6_001_215, part.tbl: 200_000, partsupp.tbl: 800_000, supplier.tbl: 10_000, nation.tbl: 25, # 固定值不随 SF 变 region.tbl: 5, # 固定值不随 SF 变 } data_dir, sf sys.argv[1], float(sys.argv[2]) for name, base in EXPECT_SF1.items(): paths glob.glob(os.path.join(data_dir, name *)) if not paths: print(fMISSING {name}) continue total sum(sum(1 for _ in open(p, rb)) for p in paths) # nation/region 是固定行数其余按 SF 放大 expect base if name in (nation.tbl, region.tbl) else int(base * sf) flag OK if total expect else FAIL print(f{flag} {name:16s} got{total:12d} expect{expect:12d})逻辑说明glob带通配是为了兼容分片文件lineitem.tbl.1也能匹配到。nation/region不做 SF 放大这是最容易写错的一行。用二进制模式读避免编码问题拖慢速度。第三段是抽样看内容特别是日期字段。TPC-H 的订单日期区间是有明确边界的lineitem的l_shipdate大致落在 1992 到 1998 之间。随便head -3 lineitem.tbl看一眼格式再抽一行确认日期没被写成奇怪格式——这步花 10 秒能省掉后面两小时的困惑。4. 建表、加载、跑 22 条查询把参数替换和计时口径做对数据对了接下来是把它变成一次“能对外说”的测试。这里面的自由度比造数大得多也更容易自欺欺人。4.1 建表 DDL 的三个取舍类型、主键、分区先给一份能直接用的最小 DDL以lineitem为例其余表同构create table lineitem ( l_orderkey bigint not null, l_partkey bigint not null, l_suppkey bigint not null, l_linenumber integer not null, l_quantity decimal(15,2) not null, l_extendedprice decimal(15,2) not null, l_discount decimal(15,2) not null, l_tax decimal(15,2) not null, l_returnflag char(1) not null, l_linestatus char(1) not null, l_shipdate date not null, l_commitdate date not null, l_receiptdate date not null, l_shipinstruct char(25) not null, l_shipmode char(10) not null, l_comment varchar(44) not null );三个取舍点类型别自作聪明。金额一律decimal(15,2)别改double。Q1 里有sum(l_extendedprice * (1 - l_discount))这类聚合浮点和定点在结果末几位会有差异校验对不上时你根本分不清是数据错了还是类型错了。l_comment是varchar(44)改成text在大表上会明显影响扫描宽度跑出来的耗时就没法跟别人比。主键和外键看目的决定。规范里对键有定义但纯性能测试时很多人会省略因为维护唯一性索引本身要花时间。我的做法是做结果校验时按规范建键做引擎调优时可以不建但报告里必须写清楚建没建。含糊其辞是最要命的。分区和分布键。分布式库上lineitem按l_shipdate做范围分区、按l_orderkey做哈希分布是很常见的组合因为 Q1 到 Q22 里大量谓词压在日期上而 join 走l_orderkey。分区策略一旦定下就别在中途改改了之后前后两次的数字不可比。4.2 加载从 .tbl 到库里的分隔符与 NULL 处理这就是前面埋的那个坑。.tbl每行结尾多一个|直接导入会多出一列# 方案一导入前把行尾分隔符削掉最省心 for f in *.tbl; do sed -i s/|$// $f done-- 方案二PostgreSQL 里用 \copy注意这是 text 格式不是 csv \copy lineitem from lineitem.tbl with (delimiter |, null ) \copy orders from orders.tbl with (delimiter |, null ) \copy customer from customer.tbl with (delimiter |, null )逻辑说明用text格式 delimiter |而不是format csv是因为 csv 模式会把末尾分隔符解释成“又一个空字段”报extra data after last expected column。削掉行尾|之后两种模式都能用我更倾向方案一因为它跟目标库无关换库不用重写脚本。加载顺序没有强制要求但先灌维表再灌事实表出错时排查范围小。加载过程中把约束、索引、外键都关掉灌完再建这是常规操作不丢人。灌完必须做一次行数核对——就是 3.4 那张表只是这次数的是库里的count(*)。4.3 22 条查询的结构与 qgen 参数替换queries/下的 22 个模板里参数写成:1、:2。别手动填用qgencd queries # 生成第 1 条查询参数用默认分布取值 qgen -d -s 1 1 # 一次生成全部 22 条落成独立文件 for i in $(seq 1 22); do qgen -d -s 1 $i q${i}.sql done-d表示参数取默认值来自dists.dss这是做可复现测试时必须加的不加的话参数随机两次跑出来的耗时没法直接比。-s跟数据规模对齐。参数生成完还是要人工扫一眼比如 Q1 的:1是个天数会拼进date 1998-12-01 - interval :1 day这种表达式里某些库对字符串形式的 interval 支持不好需要手工改成interval 90 day之类。Q1 长这样可以拿来当模板对照select l_returnflag, l_linestatus, sum(l_quantity) as sum_qty, sum(l_extendedprice) as sum_base_price, sum(l_extendedprice * (1 - l_discount)) as sum_disc_price, sum(l_extendedprice * (1 - l_discount) * (1 l_tax)) as sum_charge, avg(l_quantity) as avg_qty, avg(l_extendedprice) as avg_price, avg(l_discount) as avg_disc, count(*) as count_order from lineitem where l_shipdate date 1998-12-01 - interval :1 day group by l_returnflag, l_linestatus order by l_returnflag, l_linestatus;22 条里有几条要特别注意。Q15 依赖一个叫revenue0的视图建库时得先创建否则报对象不存在。Q13 有left outer join加子查询某些优化器会给出很不一样的计划值得单独看。Q18 的in子查询在数据量大的时候容易翻车是排查内存问题的首选目标。4.4 计时口径power run 与 throughput run 别混着比规范里定义了两种跑法。power run是单流、按指定顺序串行跑完 22 条衡量的是单条复杂查询的能力throughput run是多流并发还要跑 refresh stream一边灌新数据一边删旧数据模拟真实数仓的滚动窗口。最终对外报的复合指标是两者的几何平均单位是每小时查询数。这意味着你拿一个“22 条串行跑一遍加起来多少秒”的数字去跟别人报的复合指标比是两回事。前者只能叫“我自己环境的参考耗时”。真要自己计时一个最简脚本#!/usr/bin/env bash # 简化版单流计时仅供自测不等于规范的 power run set -euo pipefail PSQLpsql -X -q -A -t -d bench total0 for i in $(seq 1 22); do start$(date %s.%N) $PSQL -f q${i}.sql /dev/null # 结果丢掉只看时间 end$(date %s.%N) cost$(echo $end - $start | bc) total$(echo $total $cost | bc) printf Q%-2s %8.3fs\n $i $cost done echo TOTAL ${total}s跑之前做三件事预热一遍第一次跑吃冷缓存数字没意义、关掉其他负载、把结果集导出关掉 /dev/null的作用。同一个查询至少跑三次取中位数方差大的那条单独查原因。5. 避坑跑 TPC-H 最容易翻车的五件事这一章是血泪经验汇总按“现象 → 原因 → 解决”写遇到对得上的直接照做。5.1 造数阶段的三类问题现象一生成到一半报dists.dss not found退出。原因dbgen默认在当前工作目录找分布文件脚本里cd到别处或者用绝对路径调用却没加-d就找不到。 解决要么在dbgen目录下执行要么显式给-d /path/to/dists.dss。批处理脚本里我统一用后者免得被cd绕晕。现象二分片生成后合并lineitem行数明显偏多或偏少且偏差是整片的倍数。原因-C和-S没配对。常见写法是循环里-C用了变量、-S写死成 1结果同一片生成了 8 次或者漏了-b多个进程往同一文件写导致互相截断。 解决-C在循环外定义成常量-S严格从 1 到-C一定加-b合并前先用 3.4 的脚本数一遍分片文件总行数。现象三partsupp.tbl是空的或者只有几行。原因-T参数大小写搞混-T p生成的是partpartsupp要用-T P。 解决单独补生成一次./dbgen -s $SF -T P -f。这类问题在 shell 里不区分大小写的别名下更隐蔽脚本里给表名加引号并写注释。5.2 加载与查询阶段的两类问题现象四导入报extra data after last expected column或者导入成功但最后一列全是空。原因.tbl行尾多一个|被当成额外字段或空字段。 解决导入前sed -i s/|$// *.tbl统一削掉如果已经导进去了别想着修补直接 truncate 重来修补的成本远高于重灌。这是我在这个问题上唯一推荐的策略。现象五Q1 结果跟校验文件对不上但行数和数据量看着都对。原因多半是金额字段类型选错用了浮点导致聚合末位偏差或者l_shipdate被当成字符串比较、隐式转换把边界日期算进去了。 解决先desc lineitem确认类型金额必须是decimal(15,2)再把 Q1 的where单独拿出来select min(l_shipdate), max(l_shipdate)看边界。如果类型和边界都对还差那就是数据本身的问题回到 3.4 重跑校验。6. 进阶用查询变体和执行计划把「能跑」推到「能解释」能跑完 22 条不算本事能解释每一条为什么是这个耗时才算。两个手段我常用。6.1 查询变体是对付“特化优化”的后悔药3.0 系列引入的查询变体价值不只是防作弊对调优的人同样有用。做法很简单同一条查询的标准写法和变体写法各跑一次比耗时和执行计划。如果标准写法 3 秒、变体写法 40 秒说明你的优化器对某种 SQL 形态有强依赖——这在真实业务里是隐患因为业务方不会照着你的模板写 SQL。变体文件在包里的位置各版本略有差异找 README 或目录里带变体标记的那批文件就行。我一般会固定参数qgen -d后跑变体这样差异只来自 SQL 形态本身变量唯一。6.2 从执行计划反推哪一步在骗你三种验证手段用途不同别混着用手段看什么判据结果校验查询输出与期望结果逐行比对必须完全一致差一行都算失败执行计划扫描方式、join 顺序、是否有溢写计划里出现全表扫描 大表广播基本可以定性分段计时单条查询内各算子耗时定位到具体算子而不是笼统说“这条慢”执行计划里我最先看两件事大表有没有走分区裁剪、join 有没有退化成嵌套循环。Q1 这种纯扫描聚合的查询如果计划里出现回表那说明索引建歪了Q13、Q18 这类带子查询的如果出现中间结果溢写磁盘先查内存参数而不是急着加机器。一个具体技巧把 22 条按耗时排序取最慢的三条只对这三条做计划分析。全跑一遍计划毫无意义你不会有时间看完 22 份。从那以后我每次拿到一份跑分都会先问三件事SF 多少、几条流、refresh 跑没跑。答不上来的数字我当参考值看不当结论用。这套 TPC-H 3.0.1 的工具和协议就摆在那儿能不能跑出可信的数字取决于你愿不愿意把上面这些核对步骤老老实实走一遍。希望帮到你。本文还有配套的精品资源点击获取