CardBench:关系数据库学习型基数估计基准——训练数据集构建流水线与查询图格式实战指南
【免费下载链接】google-researchGoogle Research项目地址: https://gitcode.com/gh_mirrors/go/google-research
CardBench 是一个面向关系数据库学习型基数估计(Learned Cardinality Estimation)的基准(Benchmark),其核心交付物有两类:一是可直接用于训练和评估模型的带注释查询图(Annotated Query Graphs)训练数据集,二是用于复现或生成新训练数据集的完整代码流水线。本文以 CardBench README 为主线,结合仓库内的configuration.py、statistics_sql_tables_definition.sql、各流水线脚本与训练数据格式文档,系统讲解 CardBench 的架构、五步数据处理流水线、配置方法、训练数据集格式以及读取查询图的完整实操。
一、CardBench 是什么
CardBench(论文详见 arXiv: 2408.16170)是一个专为关系数据库中学习型基数估计设计的基准。基数估计是查询优化器估算一条 SQL 查询(尤其是含 JOIN 与过滤谓词的查询)结果行数的核心步骤,估计精度直接影响执行计划的质量。CardBench 的目标是为该领域提供规模化、多样化的训练数据与可复现的生成流程。
仓库中 CardBench 由两部分构成:
- 训练数据集(training datasets):位于 training_datasets 目录,包含 Single Table、Binary Join、Multi Join 三类查询图数据(npz 文件)以及对应的示意图。
- 生成训练数据集的代码:即仓库根目录下的各 Python 脚本与库目录,用于在既有或全新数据集上复现现有训练数据或生成新的工作负载。
CardBench 的两种使用方式
- 训练或评估基数估计模型(最常见):直接使用已生成的查询训练图(Query Training Graphs)。这种方式不需要运行任何 CardBench 代码,只依赖
sparse-deferred、numpy等轻量依赖即可读取数据。查询图由不同复杂度的查询生成,具体细节见 TrainingQueryGraphs.md。 - 生成新工作负载 / 使用不同数据集:需要运行 CardBench 代码,完整走一遍从统计计算到查询图生成的流水线。
二、CardBench 代码:五步数据处理流水线
CardBench 代码用于为基数估计生成训练数据集,是一个多步骤流水线,README 中给出的大致流程如下:
- 如有必要,创建数据库的下采样版本(down sampled versions)
- 计算表/列统计信息(收集表与列信息、计算表与列统计量)
- 生成训练 SQL 查询
- 运行查询以收集真实基数(actual cardinalities)
- 创建训练数据集(查询图)
由于运行整条流水线的成本较高,作者计划在发布代码之外,同时发布最终产物(训练数据集),并额外发布 CardBench 工作负载(SQL 查询)、数据集与数据集元数据(统计信息、schema)。原始数据以 CSV 文件提供,仓库同时给出建表与从 CSV 导入数据的脚本(已在 PostgreSQL 上测试通过)。所有制品的下载方式见 DowloadArtifacts.md。
三、通用信息与系统设置
3.1 双数据库架构
CardBench 收集或计算出的所有统计信息都存储在一组数据库表中,供后续生成查询图与工作负载使用。整个系统围绕两类数据库展开:
- 数据数据库(data database):存放被收集基数的业务表(即待估计基数的数据表)。
- 元数据/统计数据库(metadata / statistics database):存放计算得到的统计信息。
这两类数据库可以存放在不同的系统中。
从源码结构看,这种双库设计体现在 calculate_statistics_and_save_to_database.py 的
dbs字典中,其中data_dbclient用于查询业务数据库,metadata_dbclient用于存储统计结果;run_queries.py 也采用了完全相同的dbs结构。
3.2 数据库后端与连接器
代码最初为BigQuery设计,数据与元数据数据库均以 BigQuery 为后端。但仓库在 database_connector.py 中提供了一个可扩展的数据库连接器:当前DBType枚举只包含BIGQUERY,create_database_connection、run_query、table_exists、get_query_cardinality等接口均为按数据库类型分发的桥接函数,可通过新增枚举值与对应的xxx_bigquery实现来扩展其他数据库。
同时需要注意:扩展数据库并不只是替换连接器——部分统计功能(例如百分位数计算需要数据库提供 percentile SQL 函数,或发现表 schema 需要调用数据库专有 API 返回列名与类型)还需要修改流水线其他部分。
3.3 初始化步骤
使用 CardBench 代码前需要完成以下初始化:
- 运行 statistics_sql_tables_definition.sql 创建全部必要的统计表。该脚本定义了
tables_info、columns_info、columns_stats、各类型的columns_*_extra_stats表、pk_fk、histograms_table等表结构,脚本头部注释明确要求把project_name.dataset_name.替换为与configuration.py一致的项目与数据集名。 - 更新 configuration.py 中的表 id 与有效文件路径。
- 如果使用预计算统计(precalculated statistics),还需更新
calculate_statistics_library/pre_calculated_statistics目录下文件中的表 id(详见下文)。
3.4 configuration.py 关键配置项
configuration.py 是整条流水线的配置中心,其头部注释列出了使用前必须替换的四类内容。核心配置项包括:
| 配置项 | 作用 | 默认占位 |
|---|---|---|
DATA_DBTYPE/METADATA_DBTYPE | 数据库与元数据库类型,需替换为database_connector.DBType.X | None |
TABLES_INFO_TABLE等统计表名 | 各统计结果存储表,需替换X.为project_name.dataset_name | "X.tables_info"等 |
SAMPLE_PROJECTNAME_DATASET_NAME_4K | 4k 行样本表数据集名(用于皮尔逊相关性计算) | "X_sampled_tables" |
WORKLOAD_DEFINITION_TABLE | 存储工作负载定义(workload id 与生成参数) | "X.workload_definition" |
QUERY_RUN_INFORMATION_TABLE | 存储查询执行结果(SQL 与真实基数) | "X.query_run_information" |
DIRECTORY_PATH_JSON_FILES | JSON 统计文件目录 | None |
DIRECTORY_PATH_QUERY_FILES | 工作负载查询文件目录 | None |
DIRECTORY_TRAINING_QUERYGRAPH_OUTPUT | 训练查询图输出目录 | None |
PROJECT_NAME/DATASET_NAMES | 待处理数据数据库的项目名与数据集名列表 | [] |
此外,TYPES_TO_TABLES将各列类型(INT64、FLOAT64、NUMERIC、STRING、DATE等)映射到对应的额外统计表;TYPES_TO_COLLECT_STATS将列类型映射为 definitions.py 中Datatype枚举(INT、FLOAT、NUMERIC、STRING、DATE 等);is_type_we_dont_collect_stats()则识别 ARRAY、STRUCT、GEOGRAPHY、JSON、BOOLEAN 等不采集统计信息的列类型。
四、步骤一:计算统计(Calculate Statistics)
流水线第一步是计算统计信息并收集数据库信息,由 calculate_statistics_and_save_to_database.py 执行,相关代码位于calculate_statistics_library。
从源码看,对每个数据集依次执行以下子步骤(见 calculate_statistics_and_save_to_database.py):
collect_and_write_table_information:收集表级信息collect_and_write_column_information:收集列级信息(列名、类型)calculate_and_write_column_statistics:计算列统计(如null_frac、num_unique、row_count)calculate_and_write_extra_column_statistics:按类型计算额外统计calculate_and_write_percentiles:计算百分位数(依赖数据库的 percentile 函数)calculate_and_write_unique_values:统计唯一值calculate_and_write_frequent_words:统计字符串列高频词calculate_and_write_column_histograms:计算列直方图create_table_samples_fixsize(..., target_row_number=4000):创建 4000 行定长样本表calculate_and_write_pearson_correlation:在样本表上计算列间皮尔逊相关性
4.1 预计算统计(Precalculated Statistics)
由于该步骤复现成本较高,作者同时发布了已收集的统计结果(下载方式见 DowloadArtifacts.md)。
calculate_statistics_library/pre_calculated_statistics/pk_fk.sql用于将主键与外键信息插入configuration.PK_FK_TABLE表。
五、步骤二:生成查询(Generate Queries)
查询生成器基于此前发布的 zero-shot cost estimation 查询生成器改造而来(修改版见 generate_queries_library/query_generator.py)。
- generate_queries_and_save_to_file.py 生成查询并一行一条写入文件,生成器参数直接在该文件内定义。
- 一个查询文件即一个工作负载(workload),每个工作负载由整数workload id标识;workload id 与生成参数一起存储在
configuration.WORKLOAD_DEFINITION_TABLE表中。
5.1 查询生成器的输入:JSON 统计文件
查询生成器接收一组 JSON 文件,内含目标数据库的 schema、列统计与字符串列统计。这些 JSON 可通过 save_dataset_statistics_to_json_files.py 从calculate_statistics_and_save_to_database.py收集的统计结果转换而来。
为方便试验,仓库已内置生成好的 JSON 文件(generate_queries_library/dataset_statistics_jsons目录)。如果直接使用现有 JSON 文件,则可以跳过"计算统计"和"保存统计到 JSON"两步,直接开始生成查询。
5.2 路径配置
- JSON 文件存放在
configuration.DIRECTORY_PATH_JSON_FILES指定的目录 - 工作负载查询文件存放在
configuration.DIRECTORY_PATH_QUERY_FILES指定的目录
六、步骤三:执行查询(Execute Queries)
run_queries.py 接收一个参数:workload id,即 Generate Queries 阶段生成的工作负载(查询文件)的整数标识符。代码读取该工作负载中的全部查询并逐条执行(源码中workload_id_to_run = argv[1],未传参时会打印用法提示,见 run_queries.py)。
执行结果存储在configuration.QUERY_RUN_INFORMATION_TABLE表中,每条记录包含:
- 查询的 SQL 字符串
- 查询的真实基数(cardinality)
- workload id(用于标识所属工作负载)
- query run id:一次运行的整数标识符。每次运行一个工作负载都会创建一个新的 run,因此同一工作负载被多次运行时会产生多个 query run。
七、步骤四:生成带注释的查询图(Generate Annotated Query Graphs)
generate_training_querygraphs_and_save_to_file.py 是整个流程的最后一步。查询执行完成后,SQL 查询被翻译为图结构并用数据集统计信息进行注释,转换链为:
SQL 字符串 → 关系运算符(relational operators)→ 查询计划(query plan)→ 图(graph)生成的图以sparse_deferred 格式保存为.npz文件。
八、CardBench 训练数据集(Training Datasets)
训练数据集的完整说明见 TrainingQueryGraphs.md。每个训练实例(training instance)是一条被表示为带注释图的 SQL 查询:通过在 Google BigQuery 上执行查询获得真实基数,并作为上下文(context)包含在图中;使用的 SQL 查询本身也包含在图的上下文中。
8.1 三类训练数据集
| 数据集 | 查询构成 |
|---|---|
| Single Table | 单表上的 1–4 个过滤谓词 |
| Binary Join | 两表连接,每表 1–3 个过滤谓词 |
| Multi Join | 1–7 个连接,每表 0–2 个过滤谓词 |
示例查询:
-- Single Table 示例 SELECT count(*) FROM tpch_10G.nation as nation WHERE nation.n_nationkey <= 6 AND nation.n_comment IS NULL AND nation.n_regionkey >= 1; -- Binary Join 示例 SELECT count(*) FROM tpch_10G.region as region JOIN tpch_10G.nation as nation ON region.r_regionkey = nation.n_regionkey WHERE nation.n_comment IS NOT NULL AND nation.n_nationkey != 5;8.2 文件命名与数据集规模
文件命名规则为database_name_<single_table/binary_join/multi_join>.npz。数据库名中出现~表示该库已做下采样(down sampled)。
仓库文档给出了 20 个数据集的表数量与各类查询图数量:
| 数据集 | # 表 | # Single Table | # Binary Join | # Multi-Join |
|---|---|---|---|---|
| accidents | 3 | 9125 | 8454 | 29242 |
| airline | 19 | 6568 | 13096 | 10467 |
| consumer | 3 | 5961 | 5571 | 11857 |
| employee | 6 | 12675 | 10417 | 11041 |
| movielens | 13 | 14488 | 15757 | 17067 |
| sample_cms_synthetic_patient_data_omop | 24 | 9574 | 6536 | 10038 |
| sample_covid19_weathersource_com | 4 | 9366 | 10186 | 16075 |
| sample_crypto_bitcoin_cash | 2 | 14315 | 12404 | 17114 |
| sample_ethereum_blockchain | 7 | 19756 | 17424 | 19962 |
| sample_geo_openstreetmap | 16 | 16346 | 15000 | 10979 |
| sample_github_repos | 9 | 5268 | 4513 | 10564 |
| sample_human_variant_annotation | 26 | 13862 | 14085 | 12162 |
| sample_idc_v10 | 19 | 8609 | 6464 | 12081 |
| sample_open_targets_genetics | 13 | 9734 | 9058 | 10025 |
| sample_samples | 8 | 8893 | 11369 | 10832 |
| sample_stackoverflow | 14 | 14305 | 12773 | 11399 |
| sample_usfs_fia | 11 | 13822 | 11787 | 10980 |
| sample_uspto_oce_claims | 12 | 5925 | 6123 | 11968 |
| sample_wikipedia | 25 | 6659 | 7375 | 10739 |
| tpch_10G | 8 | 11727 | 13181 | 16318 |
8.3 训练数据 Schema
每个训练实例是一个异构图(heterogeneous graph),节点类型包括g(图级节点)、tables、attributes、predicates、ops、correlations,边类型如下:
table_to_attr: (tables, attributes) # 表-属性 attr_to_pred: (attributes, predicates) # 属性-谓词 pred_to_pred: (predicates, predicates) # 谓词-谓词 attr_to_op: (attributes, ops) # 属性-算子 op_to_op: (ops, ops) # 算子-算子 pred_to_op: (predicates, ops) # 谓词-算子 attr_to_corr: (attributes, correlations) # 属性-相关性 corr_to_pred: (correlations, predicates) # 相关性-谓词主要节点特征:
- 图级节点 g:
cardinality(真实基数)、exec_time(执行时间)、query_id、query(SQL 字符串) - tables:
rows(行数)、name - attributes:
null_frac、num_unique、data_type、name、percentiles_100_numeric、percentiles_100_string、min_numeric/max_numeric、min_string/max_string - predicates:
predicate_operator、estimated_selectivity、offset、constant、encoded_constant - ops:
operator(如scan、join) - correlations:
type、correlation、validity
注意:特征根据属性类型填充——字符串类型属性填充percentiles_str与字符串极值,数值属性填充percentiles_num与数值极值;未填充的特征统一用-1填充(例如示例中字符串列的percentiles_100_numeric全为-1)。
九、如何读取训练数据(Python 实操)
训练数据使用 Sparse Deferred 库定义的 Graph Struct 编码。Sparse Deferred 提供了一套简洁的写入、读取与存储接口,并可将数据序列化为 TF/JAX 友好的格式。运行示例代码需要Python >= 3.10、sparse-deferred、numpy,均可通过 pip 安装。
9.1 加载数据集
from sparse_deferred.structs import graph_struct GraphStruct = graph_struct.GraphStruct InMemoryDB = graph_struct.InMemoryDB # 训练数据集按分片(sharded)存储,即拆分为多个文件; # 加载 consumer_single_table 数据集 filename = "single_table/consumer_single_table.npz" db = InMemoryDB.from_file(filename)9.2 查看实例数量与 schema
# 打印训练实例数量 print("Number of training instances:", db.size) # Number of training instances: 5571 # 打印训练实例的 schema print("Schema:", db.schema)输出示例:
Schema: {'table_to_attr': ('tables', 'attributes'), 'attr_to_pred': ('attributes', 'predicates'), 'pred_to_pred': ('predicates', 'predicates'), 'attr_to_op': ('attributes', 'ops'), 'op_to_op': ('ops', 'ops'), 'pred_to_op': ('predicates', 'ops'), 'attr_to_corr': ('attributes', 'correlations'), 'corr_to_pred': ('correlations', 'predicates')}9.3 查看节点类型与特征
first_training_example = db.get_item(0) # 打印节点类型 print("Node types:", first_training_example.nodes.keys()) # Node types: dict_keys(['g', 'tables', 'attributes', 'predicates', 'ops', # 'correlations']) # 打印表节点特征 print("Table node features:", db.get_item(0).nodes["tables"].keys()) # Table node features: dict_keys(['rows', 'name']) # 打印边类型 print("Edge types:", first_training_example.edges.keys())9.4 读取表级信息
# 打印第一个表节点的行数与名称 print("First table number of rows:", db.get_item(0).nodes["tables"]["rows"][0]) print("First table name:", db.get_item(0).nodes["tables"]["name"][0])输出示例:
First table number of rows: 340872 First table name: b'bq-cost-models-exp.consumer.HOUSEHOLDS'9.5 读取图级信息(基数、执行时间、查询)
# 打印查询基数、查询、query_id、执行时间、SQL 字符串(图级特征) print("Query cardinality:", first_training_example.nodes["g"]["cardinality"][0]) print("Execution time:", first_training_example.nodes["g"]["exec_time"][0]) print("Query id:", first_training_example.nodes["g"]["query_id"][0]) print("Query:", first_training_example.nodes["g"]["query"][0])输出示例:
Query cardinality: 824130 Execution time: 2390.0 Query id: 14211 Query: b'SELECT count(*) as rwcnt FROM `bq-cost-models-exp.consumer.HOUSEHOLDS` as HOUSEHOLDS JOIN `bq-cost-models-exp.consumer.HOUSEHOLD_MEMBERS` as HOUSEHOLD_MEMBERS ON HOUSEHOLDS.HOUSEHOLD_ID = HOUSEHOLD_MEMBERS.HOUSEHOLD_ID;\n'从上述输出可以看出,该示例是一个包含两表 JOIN 的查询,真实基数为 824130,执行时间 2390 毫秒,图中tables.rows直接给出了两张表的行数(340872 与 824130),attributes节点携带null_frac、num_unique、百分位数等统计特征,未使用的特征以-1填充——这些信息正是训练学习型基数估计模型所需的输入信号。
十、CardBench 制品下载指南
DowloadArtifacts.md 列出了四类可下载制品(均托管在 Google Cloud Storage 的gresearch/CardBench_zero_shot_cardinality_training目录下):
- 数据集 CSV:各数据集的原始数据 CSV 文件。
- 建表脚本(create_schema_scripts):每个数据集对应一个
*_create_tables_pg_oss.sql脚本,用于在 PostgreSQL 兼容数据库中创建表(如accidents_create_tables_pg_oss.sql、tpch_10G_create_tables_pg_oss.sql等 20 个数据集)。 - 拷贝脚本(copy_to_db_scripts):
*_copy_to_postgres.sql用于把 CSV 导入对应表。脚本中包含DATASET_PATH_PREFIX占位符,使用前需替换为下载 CSV 文件所在父目录的实际路径。 - 数据集元数据(datasets_metadata):每个数据集三个 JSON 文件:
<dataset>.schema.json、<dataset>.column_statistics.json、<dataset>.string_statistics.json——这些正是查询生成器所需的输入 JSON。 - 训练查询图(cardbench_query_graphs_for_github):按
single_table/、binary_join/、multi_join/三个目录组织,文件名为<dataset>_<type>.npz,共 20 数据集 × 3 类型。
这些脚本与 JSON 与本文介绍的代码流水线一一对应:建表脚本对应数据数据库的初始化,元数据 JSON 对应查询生成阶段输入,查询图 npz 对应最终训练数据。
十一、小结与最佳实践
综合 README.md、TrainingQueryGraphs.md 与 DowloadArtifacts.md 的内容,使用 CardBench 的建议路径如下:
- 仅训练/评估模型:直接下载查询图 npz,用 Sparse Deferred 的
InMemoryDB.from_file读取,无需运行任何流水线代码——这是大多数研究者的首选路径。 - 复现现有工作负载:下载数据集 CSV 与元数据 JSON,若使用仓库内置 JSON 可跳过统计计算;随后依次运行
generate_queries_and_save_to_file.py→run_queries.py→generate_training_querygraphs_and_save_to_file.py。 - 全新数据集:先运行
statistics_sql_tables_definition.sql建表并更新configuration.py,再运行calculate_statistics_and_save_to_database.py走完整条流水线。
无论哪条路径,都需要注意:预计算统计/内置 JSON 可以显著降低复现成本;BigQuery 是开箱即用的后端,扩展到其他数据库需自行扩展 database_connector.py 中的分发函数,并补齐百分位数等数据库特有功能。
【免费下载链接】google-researchGoogle Research项目地址: https://gitcode.com/gh_mirrors/go/google-research
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考