news 2026/9/20 8:48:55

CardBench:关系数据库学习型基数估计基准——训练数据集构建流水线与查询图格式实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CardBench:关系数据库学习型基数估计基准——训练数据集构建流水线与查询图格式实战指南

CardBench:关系数据库学习型基数估计基准——训练数据集构建流水线与查询图格式实战指南

【免费下载链接】google-researchGoogle Research项目地址: https://gitcode.com/gh_mirrors/go/google-research

CardBench 是一个面向关系数据库学习型基数估计(Learned Cardinality Estimation)的基准(Benchmark),其核心交付物有两类:一是可直接用于训练和评估模型的带注释查询图(Annotated Query Graphs)训练数据集,二是用于复现或生成新训练数据集的完整代码流水线。本文以 CardBench README 为主线,结合仓库内的configuration.pystatistics_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 的两种使用方式

  1. 训练或评估基数估计模型(最常见):直接使用已生成的查询训练图(Query Training Graphs)。这种方式不需要运行任何 CardBench 代码,只依赖sparse-deferrednumpy等轻量依赖即可读取数据。查询图由不同复杂度的查询生成,具体细节见 TrainingQueryGraphs.md。
  2. 生成新工作负载 / 使用不同数据集:需要运行 CardBench 代码,完整走一遍从统计计算到查询图生成的流水线。

二、CardBench 代码:五步数据处理流水线

CardBench 代码用于为基数估计生成训练数据集,是一个多步骤流水线,README 中给出的大致流程如下:

  1. 如有必要,创建数据库的下采样版本(down sampled versions)
  2. 计算表/列统计信息(收集表与列信息、计算表与列统计量)
  3. 生成训练 SQL 查询
  4. 运行查询以收集真实基数(actual cardinalities)
  5. 创建训练数据集(查询图)

由于运行整条流水线的成本较高,作者计划在发布代码之外,同时发布最终产物(训练数据集),并额外发布 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枚举只包含BIGQUERYcreate_database_connectionrun_querytable_existsget_query_cardinality等接口均为按数据库类型分发的桥接函数,可通过新增枚举值与对应的xxx_bigquery实现来扩展其他数据库。

同时需要注意:扩展数据库并不只是替换连接器——部分统计功能(例如百分位数计算需要数据库提供 percentile SQL 函数,或发现表 schema 需要调用数据库专有 API 返回列名与类型)还需要修改流水线其他部分。

3.3 初始化步骤

使用 CardBench 代码前需要完成以下初始化:

  1. 运行 statistics_sql_tables_definition.sql 创建全部必要的统计表。该脚本定义了tables_infocolumns_infocolumns_stats、各类型的columns_*_extra_stats表、pk_fkhistograms_table等表结构,脚本头部注释明确要求把project_name.dataset_name.替换为与configuration.py一致的项目与数据集名。
  2. 更新 configuration.py 中的表 id 与有效文件路径。
  3. 如果使用预计算统计(precalculated statistics),还需更新calculate_statistics_library/pre_calculated_statistics目录下文件中的表 id(详见下文)。

3.4 configuration.py 关键配置项

configuration.py 是整条流水线的配置中心,其头部注释列出了使用前必须替换的四类内容。核心配置项包括:

配置项作用默认占位
DATA_DBTYPE/METADATA_DBTYPE数据库与元数据库类型,需替换为database_connector.DBType.XNone
TABLES_INFO_TABLE等统计表名各统计结果存储表,需替换X.project_name.dataset_name"X.tables_info"
SAMPLE_PROJECTNAME_DATASET_NAME_4K4k 行样本表数据集名(用于皮尔逊相关性计算)"X_sampled_tables"
WORKLOAD_DEFINITION_TABLE存储工作负载定义(workload id 与生成参数)"X.workload_definition"
QUERY_RUN_INFORMATION_TABLE存储查询执行结果(SQL 与真实基数)"X.query_run_information"
DIRECTORY_PATH_JSON_FILESJSON 统计文件目录None
DIRECTORY_PATH_QUERY_FILES工作负载查询文件目录None
DIRECTORY_TRAINING_QUERYGRAPH_OUTPUT训练查询图输出目录None
PROJECT_NAME/DATASET_NAMES待处理数据数据库的项目名与数据集名列表[]

此外,TYPES_TO_TABLES将各列类型(INT64FLOAT64NUMERICSTRINGDATE等)映射到对应的额外统计表;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):

  1. collect_and_write_table_information:收集表级信息
  2. collect_and_write_column_information:收集列级信息(列名、类型)
  3. calculate_and_write_column_statistics:计算列统计(如null_fracnum_uniquerow_count
  4. calculate_and_write_extra_column_statistics:按类型计算额外统计
  5. calculate_and_write_percentiles:计算百分位数(依赖数据库的 percentile 函数)
  6. calculate_and_write_unique_values:统计唯一值
  7. calculate_and_write_frequent_words:统计字符串列高频词
  8. calculate_and_write_column_histograms:计算列直方图
  9. create_table_samples_fixsize(..., target_row_number=4000):创建 4000 行定长样本表
  10. 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 Join1–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
accidents39125845429242
airline1965681309610467
consumer35961557111857
employee6126751041711041
movielens13144881575717067
sample_cms_synthetic_patient_data_omop249574653610038
sample_covid19_weathersource_com493661018616075
sample_crypto_bitcoin_cash2143151240417114
sample_ethereum_blockchain7197561742419962
sample_geo_openstreetmap16163461500010979
sample_github_repos95268451310564
sample_human_variant_annotation26138621408512162
sample_idc_v10198609646412081
sample_open_targets_genetics139734905810025
sample_samples888931136910832
sample_stackoverflow14143051277311399
sample_usfs_fia11138221178710980
sample_uspto_oce_claims125925612311968
sample_wikipedia256659737510739
tpch_10G8117271318116318

8.3 训练数据 Schema

每个训练实例是一个异构图(heterogeneous graph),节点类型包括g(图级节点)、tablesattributespredicatesopscorrelations,边类型如下:

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) # 相关性-谓词

主要节点特征:

  • 图级节点 gcardinality(真实基数)、exec_time(执行时间)、query_idquery(SQL 字符串)
  • tablesrows(行数)、name
  • attributesnull_fracnum_uniquedata_typenamepercentiles_100_numericpercentiles_100_stringmin_numeric/max_numericmin_string/max_string
  • predicatespredicate_operatorestimated_selectivityoffsetconstantencoded_constant
  • opsoperator(如scanjoin
  • correlationstypecorrelationvalidity

注意:特征根据属性类型填充——字符串类型属性填充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_fracnum_unique、百分位数等统计特征,未使用的特征以-1填充——这些信息正是训练学习型基数估计模型所需的输入信号。

十、CardBench 制品下载指南

DowloadArtifacts.md 列出了四类可下载制品(均托管在 Google Cloud Storage 的gresearch/CardBench_zero_shot_cardinality_training目录下):

  1. 数据集 CSV:各数据集的原始数据 CSV 文件。
  2. 建表脚本(create_schema_scripts):每个数据集对应一个*_create_tables_pg_oss.sql脚本,用于在 PostgreSQL 兼容数据库中创建表(如accidents_create_tables_pg_oss.sqltpch_10G_create_tables_pg_oss.sql等 20 个数据集)。
  3. 拷贝脚本(copy_to_db_scripts)*_copy_to_postgres.sql用于把 CSV 导入对应表。脚本中包含DATASET_PATH_PREFIX占位符,使用前需替换为下载 CSV 文件所在父目录的实际路径
  4. 数据集元数据(datasets_metadata):每个数据集三个 JSON 文件:<dataset>.schema.json<dataset>.column_statistics.json<dataset>.string_statistics.json——这些正是查询生成器所需的输入 JSON。
  5. 训练查询图(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 的建议路径如下:

  1. 仅训练/评估模型:直接下载查询图 npz,用 Sparse Deferred 的InMemoryDB.from_file读取,无需运行任何流水线代码——这是大多数研究者的首选路径。
  2. 复现现有工作负载:下载数据集 CSV 与元数据 JSON,若使用仓库内置 JSON 可跳过统计计算;随后依次运行generate_queries_and_save_to_file.pyrun_queries.pygenerate_training_querygraphs_and_save_to_file.py
  3. 全新数据集:先运行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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 8:48:35

SSM+Vue构建轻量级进销存系统实战

1. 项目背景与核心需求中小制造企业在数字化转型过程中面临一个典型困境&#xff1a;既无法承担大型ERP系统的高额成本&#xff0c;又难以用Excel纸质单据满足日益复杂的业务管理需求。我在为本地一家五金配件厂做技术咨询时&#xff0c;亲眼目睹仓库管理员每天要手工核对三套表…

作者头像 李华
网站建设 2026/9/20 8:47:59

Logisim中文免安装版:零Java依赖的数字电路教学方案

1. 项目概述&#xff1a;为什么“Logisim中文版 免JAVA环境 免安装”能成为数字电路教学的破局点&#xff1f;Logisim——这个在高校数字逻辑、计算机组成原理课程里被反复提起的名字&#xff0c;几乎等同于“门电路拖拽连线仿真”的代名词。但过去十年里&#xff0c;几乎所有学…

作者头像 李华
网站建设 2026/9/20 8:47:26

DeskcommCRM实战:从客户档案到跟进流程的聚焦型销售管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 8:46:44

光伏设计工具iSolarBP核心功能与避坑指南

1. 光伏新手入门避坑指南&#xff1a;iSolarBP三大核心功能解析刚接触光伏行业时&#xff0c;面对复杂的系统设计和参数配置&#xff0c;很多新手都会感到无从下手。iSolarBP作为光伏设计领域的专业工具&#xff0c;其内置的三大核心功能能帮你快速跨越入门阶段的技术门槛。我在…

作者头像 李华
网站建设 2026/9/20 8:45:37

117、MLIR的Unrolling(循环展开)与Jam(循环合并)

MLIR的Unrolling(循环展开)与Jam(循环合并) 从一次性能调优的“翻车”说起 去年调一个AI推理引擎的卷积算子,手写了一个循环展开的pass,信心满满地跑benchmark——结果延迟反而涨了15%。当时盯着MLIR的IR dump看了三个小时,发现循环展开后寄存器压力爆了,L1 cache mi…

作者头像 李华
网站建设 2026/9/20 8:42:32

突破LLM记忆瓶颈:DeepSeek V4 LTM架构解析与实践

1. 记忆瓶颈的本质与挑战在大型语言模型的发展历程中&#xff0c;记忆能力一直是制约模型性能的关键因素。传统模型的上下文窗口通常局限在几千个token范围内&#xff0c;这导致在处理长文档、复杂对话和跨文档推理时面临严重的信息丢失问题。记忆瓶颈主要体现在三个方面&#…

作者头像 李华