学习型基数估计如何在动态数据库里落地:SWatcher+ 的选择性监控与主动纠偏
原文信息:本文译自 arXiv 论文《SWatcher+: A Holistic Approach to Deploying Learned Cardinality Estimators in a Dynamic Setting》,作者 Yuxi Liu、Vincent Capol、Xiao Hu、Pankaj K. Agarwal、Jun Yang(杜克大学、滑铁卢大学),2026 年 10 月 20 日提交(arXiv:2610.28270)。原文链接:https://arxiv.org/abs/2610.28270 。原文采用 CC BY 4.0 许可。本文为翻译(节译):摘要、引言、方法与结论全文翻译,实验部分按原文要点节译,相关工作一节从略;公式改写为文字,图表与参考文献请见原文。文末附译者注。
译者导语:用机器学习做基数估计(Cardinality Estimation,CE),论文里的准确率一个比一个漂亮,可一放进数据天天在变的生产库,模型就会慢慢“过时”。什么时候重训?重训不起怎么办?这篇论文不再执着于把估计误差压得更低,而是只盯那些“估错了真会让执行计划变慢”的子查询,并在重训之前就主动纠偏。结论对每个关心查询优化、在评估 AI4DB 能否落地的数据库工程师都很有参考价值。
摘要
基数估计是数据库查询优化的关键环节。尽管近年来准确率不断提升,但在动态数据库中部署机器学习基数估计模型仍然困难:数据在更新,负载在漂移。重训代价不小,现有做法又很少说明该在何时、以何种方式更新模型,结果大多只能采用简单的周期性重训,效果并不理想。
我们提出 SWatcher+:一个通过监控数据更新和查询反馈、让学习型基数估计适应动态负载的框架。SWatcher+ 只选择性地监控“高影响”子查询——这些子查询一旦估错,就会导致执行计划变差;它还会在查询优化阶段主动修正估计值。这样一来,系统无需等待模型完整重训,就能立即适应数据和负载的变化,同时仔细权衡监控与修正的收益和成本、以及重训的收益和成本。实验表明,与现有方法相比,SWatcher+ 在查询性能与额外开销之间取得了更好的平衡,是在动态环境中部署学习型基数估计更实用的方案。
一、引言
大多数数据库查询优化器都高度依赖基数估计来生成执行计划。近年来的学习型基数估计(在数据库实例和/或查询执行反馈上训练机器学习模型)在提升估计准确率方面很有前景。然而部署仍是难题:数据库在动态变化,模型可能逐渐跟不上。细粒度的增量更新往往代价高昂,所以大多数学习型模型靠重训或(在适用时)微调来保持更新。可是,昂贵的刷新该安排在什么时候,并没有明确答案。常见做法是周期性刷新,周期可以按时间算,也可以按自上次刷新以来的数据更新条数算,但如何设定周期,通常缺乏指导。我们需要一个更聪明的框架,来在动态环境中部署学习型基数估计。
本文探讨了几种思路。第一是监控。我们希望同时监控数据更新和查询反馈,从中寻找刷新模型的线索。忽略数据更新会让系统反应过慢——数据已经大幅变化,但还没有足够多的查询暴露出问题;而忽略查询负载则可能导致不必要的刷新——数据库里常有一些很少被查询的部分,为它们的变化去刷新模型并不能提升查询性能。第二是估计修正。与其用从数据更新和查询反馈中收集到的信息去刷新模型(代价高),不如用它们直接修正过时模型给出的估计值。早在现代学习型模型出现之前,LEO 就尝试过这一思路:它会记住超过 5% 的估计误差,并用来修正后续的估计。这样不必等到下次刷新就能受益。
但要把这些思路组合成一个能带来端到端实际收益的方案,并不简单。我们从整体(holistic)视角看待这个问题:不只盯着基数估计的准确率,而是关注估计值对查询性能的最终影响,以及包括监控、刷新和修正在内的总运维成本。首先,监控有开销,但收益并不总是明确——很多估计误差其实并不影响计划质量。其次,只修正部分子查询(例如只修正单表选择)的估计误差,有时反而会得到更差的计划。这一反直觉现象已有文献指出,我们的实验也证实了它。最后,重训模型并不能保证在真正要紧的地方估得更准。总之,在动态环境中部署学习型基数估计,需要在收益和成本之间精细拿捏。
主要贡献
在基本的监控与修正思路之上,我们提出了 SWatcher+。
- “+”代表主动使用监控信息以放大监控的收益:在优化阶段就修正模型估计,不必等待模型刷新。
- “S”代表选择性:依据收益来决定监控和修正哪些对象,以降低开销。我们只监控那些修正后预计会切实影响计划性能的子查询的基数。
重要的是,我们注意到只修正选择基数,效果可能很小(甚至为负),因此也监控与连接(join)有关的信息。我们证明:单表选择的精确基数可以高效维护,但维护连接子查询的精确基数并不划算。因此,我们只为连接子查询缓存最近的补偿修正因子(compensating correction factor)。这类信息虽然不那么精确,但即便是三表以上的连接也能低成本获得,并且有助于避开“只修正选择基数”的陷阱。
此外,SWatcher+ 触发模型刷新的依据,是发现自己正在盯着太多“值得修正”的基数,而不是发现总体估计误差偏高。这一策略让 SWatcher+ 能够限制处理数据更新的时间,并及时意识到模型犯下的“后果严重的错误”太多了。
SWatcher+ 的设计具有通用性,不绑定任何特定的学习型模型或数据库系统。我们用 SWatcher+ 在 PostgreSQL 上部署了三种不同的基数估计模型——MSCN、JGMP 和 Naru,并与其他部署策略在收益和成本上做了全面比较。结果显示,SWatcher+ 在固定配置、几乎无需调参的情况下,端到端性能更优:
- 与常用的周期性刷新相比(模型为 MSCN),SWatcher+ 的端到端运行时间为周期性刷新的 44.3%–101.4%,在 16 个查询模板中的 14 个上胜出——即使周期性刷新的周期是事后选出的最优值。使用 JGMP 和 Naru 时,这一比例分别为 56.1%–99.5% 和 87.84%–98.84%,SWatcher+ 全面领先。
- SWatcher+ 的选择性监控与修正策略,轻松胜过更简单的监控与修正策略,包括我们尽力复现的 LEO 策略。
- 为连接子查询维护补偿修正因子而不是精确基数,能有效控制开销,同时带来查询性能提升,即使对涉及多表连接的复杂查询也是如此。
- SWatcher+ 能自然地适应负载:面对复杂的 OLAP 类查询,它会监控和修正更多,以换取更大的性能提升;面对提升空间很小的简单 OLTP 类查询,它会自动减少监控和修正,避免额外开销。
二、框架与预备知识
在介绍 SWatcher+ 之前,我们先描述一个在动态环境中为数据库部署学习型基数估计模型的通用框架,称为 Watcher;第三节的基础监控策略和第四节的 SWatcher+ 都在这个框架内实现。
学习型基数估计模型。Watcher 假设可以使用一个能被刷新的学习型模型来预测子查询的基数。模型刷新一般可以利用查询反馈(即查询及其在执行中观察到的真实基数),必要时也可以利用当前数据库状态。我们的方法面向刷新耗时较长的模型,但也评估了它与支持增量更新的模型相比如何、能否互补。重要的是,Watcher 不对模型内部做任何假设。我们的目标不是改造某个模型让它变得更好,而是在绕开模型局限的前提下,以最聪明的方式部署给定的模型。因此,本方法与“让模型本身在动态环境中更稳健”的研究是正交且互补的。
数据与查询的访问。Watcher 假设可以访问数据更新流(例如通过监控数据库日志):每条更新都会通知框架,框架可以读取其内容;需要时也可以查询数据库获取额外信息。Watcher 还假设可以访问发往数据库的查询流,并能检查某个查询的执行计划及其执行过程,获得每个子计划输出结果集的基数。以 PostgreSQL 为例,这些信息可以通过 EXPLAIN ANALYZE 命令获得。不过,由于执行计划有多种可能,Watcher 并不假设每个可能的子查询的基数都能从执行中观察到——计划里可能根本没有与之对应的子计划。因此它可能向数据库发出额外查询来获取子查询基数。
与查询优化器的接口。与学习型基数估计研究中的典型设置一样,Watcher 假设模型位于数据库优化器外部(而非集成在内)。它可以调用优化器,用一组子查询的基数估计来优化某个查询。按照文献中的常见做法,它并不为所有可能的子查询指定估计值(代价太高),而只覆盖“相关”的一部分,其余仍交给数据库优化器自己估计。拿到计划后,Watcher 请数据库按该计划执行查询。SWatcher+ 还额外假设可以调用优化器,在给定一组基数估计时估算某个计划的代价,而无需真正执行。在基于 PostgreSQL 的实现中,我们沿用已有工作的机制做基数注入,并用 pg_hint_plan 指定要执行(EXPLAIN ANALYZE)和估算代价(EXPLAIN)的计划。
架构概览。Watcher 是基数估计模型与底层数据库之间的中间层(原文图 1)。它维护一个精心挑选的监视集(watchset),由负载中的若干子查询组成,并使这些子查询的基数随数据库状态保持最新。这类似于数据库增量维护直方图等数据摘要,但我们在“维护什么”上要谨慎得多,会同时依据查询负载和“哪些估计误差真正影响查询性能”来挑选。它还可能维护其他随数据更新和/或查询反馈而更新的信息,用于查询优化和触发模型刷新。Watcher 的行为分为三个方面:
- 处理每条数据更新:用数据更新增量维护监视集中各子查询的基数,使之保持最新。
- 处理每个查询:拦截每个到来的查询,利用自己维护的信息,有选择地修正模型直接给出的基数估计;同时从数据库获取执行反馈(包括观察到的真实基数);可能向监视集加入或从中移除子查询;也可能向数据库请求额外信息来更新内部状态。
- 触发并准备模型刷新:根据内部状态决定何时刷新模型;利用已有信息、必要时再查询数据库,为刷新准备训练数据。
我们先介绍只用监控思路的基础实现 Watcher1D(第三节),再介绍主方法 SWatcher+:它维护的监视集经过更仔细的挑选、规模小得多,并为连接子查询维护“修正因子”的粗略近似,从而取得好得多的成本收益平衡(第四节)。
三、基础监控:Watcher1D
Watcher1D 用负载中单表选择子查询的蓄水池抽样(reservoir sample)作为监视集。例如,负载中出现了下面这个查询(第五节使用的 TJQ013a 模板实例):
SELECT * FROM store_sales, store, customer_demographics, household_demographics, customer_address, date_dim
WHERE s_store_sk = ss_store_sk
AND ss_sold_date_sk = d_date_sk AND ss_hdemo_sk = hd_demo_sk
AND cd_demo_sk = ss_cdemo_sk AND ss_addr_sk = ca_address_sk
AND ss_sales_price BETWEEN 32.78 AND 207.53
AND hd_dep_count BETWEEN 1 AND 4;
监视集就可能包含 store_sales 上 ss_sales_price 的范围选择,以及 household_demographics 上 hd_dep_count 的范围选择这两个子查询。对于监视集中的每个子查询,Watcher1D 记录模型估计的基数和真实基数,并按所引用的表建立索引,以便快速取出与某张表相关的子查询。
借助数据更新流,Watcher1D 跟踪监视集查询的真实基数,用来评估模型准确率、支持模型刷新:它监控监视集上的一个总体误差指标,据此触发重训;训练数据直接来自监视集,因为其中的真实基数一直是最新的。但实验表明,即便数据结构设计和实现都很用心,维护开销也很容易超过潜在收益。这一局限正是第四节 SWatcher+ 采用选择性监控与修正的动机。
处理查询。对每个查询,把其中的单表选择子查询作为监视集候选,按标准蓄水池抽样决定是否纳入,必要时淘汰旧成员以保持监视集大小。用模型估计调用优化器得到计划,记录每个候选的估计基数;执行后,从 EXPLAIN ANALYZE 的输出中解析出真实基数。有时计划并不会把某个子查询作为独立部分执行(例如该表作为索引嵌套循环连接的内表、由连接条件驱动索引扫描),这时就额外执行一次计数查询来获得真实基数,这部分计入方法的开销。
处理数据更新。对表上某一行的每条更新,逐个检查与该表相关的监视集查询:若该行满足选择条件,插入则计数加一,删除则减一。这样就保证了监视集查询的真实基数始终准确、最新。这种线性时间的做法实现简单、开销低,由于监视集通常不大,实践中效果不错;监视集很大时,可以用更复杂的数据结构把查询和更新代价降到对数级。
重训模型。处理完每条数据更新和每个查询后,都检查一次总体误差指标,因为两者都会影响模型表现;如果已知数据分布或查询分布稳定,可相应降低检查频率。误差指标采用从监视集中抽样计算的 Q-error 的某个百分位数。与平均误差或均方根误差相比,基于分位数的指标对少数离群值不敏感——学习型模型的预测里离群值很常见,但它们未必说明模型整体不行;分位数阈值能避免这些离群值引发不必要的重训。当总体误差超过阈值时触发重训,训练集从监视集中抽样构造。由于真实基数一直在跟踪,无需重跑这些查询来获取训练数据,大大缩短了重训的端到端延迟。重训也不保证误差一定降到阈值以下;一旦未达标,就对阈值做指数退避(乘以一个系数);若之后重训误差回落到更低的水平,可以恢复原阈值。
分析。通过监控从负载中抽样的查询的真实基数,Watcher1D 的重训决策能同时参考数据分布和查询分布,而不像一些方法那样依赖不太可靠的启发式规则;它还能零额外成本地提供最新训练数据。但第五节实验表明,实践中省下的查询执行时间抵不上维护监视集的高昂开销。例如,在查询/更新比为 1/100、监视集大小为 1500 的设置下,Watcher1D 的开销中 62.7% 用于处理数据更新,4.3% 用于处理查询(全部是模型推理),其余 33.0% 用于重训。总体而言,与从不重训的基线相比,Watcher1D 多花了 116 秒开销,却只把计划执行时间缩短了 66 秒。
我们把这种不划算归结为两点局限。其一,Watcher1D 只把监控信息用于触发和支持模型刷新,严重浪费了一项昂贵的资源——应当让主动维护的真实基数立刻发挥更大作用,而不是等到刷新。其二,Watcher1D 只关注单表选择子查询的估计误差,而连接子查询的估计误差往往更关键;同时,为连接子查询维护真实基数比为选择子查询维护更贵(见第五节对其扩展版 Watcher2D 的实验)。因此,需要一种比增量维护更划算的连接基数估计方法。
四、SWatcher+
SWatcher+ 维护两个主要数据结构:
- 监视集:与 Watcher1D 一样,是负载中出现过的单表选择子查询的子集。但它不是蓄水池抽样、也不只用于刷新,而是按其对执行计划效率的收益决定纳入或淘汰,并主动用其中的真实基数修正模型估计。
- 补偿修正因子缓存:条目以某个已执行计划的“签名”为索引。签名由两部分组成:该计划连接的表集合(必要时加上连接谓词),以及得到该计划时用于修正的那些单表选择子查询。对每个签名,缓存记录这些表上每个连接子计划对应的补偿修正因子——用它乘以该子计划(已修正选择基数后的)估计值,就能得到当时观察到的输出基数。条目还记录缓存时间、生成它的查询及其执行/规划时间。
SWatcher+ 的三个方面概括如下:
- 处理数据更新:与 Watcher1D 相同。
- 处理查询:执行前,查监视集和修正因子缓存来修正模型估计,不等刷新;执行后,根据反馈更新监视集与缓存,并决定是否纳入或淘汰子查询,这可能需要一些额外分析开销。
- 模型刷新:依据监视集的大小决定时机——监视集很大,说明很多估计误差正在影响查询性能,刷新可能有益。
SWatcher+ 共有五个阈值参数(原文表 1):相似度阈值(判定两个单表选择条件“近似匹配”的最低相似度,用于修正);最短执行时间与最长优化时间(决定一个查询是否值得纳入监控);最低预估加速比(纳入监控所需的最低计划代价加速);以及监视集规模上限(超过即触发刷新)。
4.1 处理查询
执行前。收到查询后,SWatcher+ 先用模型得到基数估计,再分两步修正。
第一步,修正单表选择。对查询中的每个单表选择子查询,在监视集里找条件与之完全匹配或近似匹配的成员。两个条件近似匹配,是指它们的 Jaccard 相似度不低于相似度阈值。例如对同一列上的两个范围条件,若两个区间重叠,相似度取交集长度除以并集长度,否则为 0。若有多个成员近似匹配,取相似度最高的那个,然后用启发式方法修正估计值:修正后的基数 = 模型对“当前条件中不在已监控条件内的部分”的估计 + 已监控条件的真实基数 × 两个条件交集占已监控条件的比例。直观地说,第一项估计当前条件落在已监控范围之外的部分,第二项把已监控条件的真实基数按比例缩到与当前条件的交集。两个条件完全匹配时,修正就退化为直接用真实基数替换估计值。(原文注:这一修正假设查询范围内数据近似均匀分布,与基于直方图的估计类似,实践中并不总成立。)
例 4.1:如原文图 2 所示,查询 Q2 在 Q1 之后到达。假设 Q1 的某个单表选择子查询已被纳入监视集且尚未淘汰。若它的条件与 Q2 对应条件最相似——相似度 87.12%,超过 80% 的阈值——就用它来修正 Q2 中对应子查询的估计。修正后的估计进入最终的估计集合。
第二步,在必要时用修正因子缓存修正连接基数。只要第一步用到了监视集成员,就可能需要修正连接估计。SWatcher+ 先找签名与当前查询完全一致的缓存条目;若没有,就找一组签名,使它们涉及的表互不相交、各自与当前查询在这些表上的限制一致,并且合起来尽可能覆盖当前查询的所有表。(若有多种分解都能达到最大覆盖但给出不同的修正,就优先选“所涉单表子查询与当前查询的最低相似度最大”的那个缓存查询,即最接近的历史实例。要求各部分互不相交,是为了避免用多次历史执行的因子去修正同一个连接而导致不一致和过度修正;又因为观察到的修正因子高度依赖于当时修正了哪些选择,所以要求签名与当前查询所做的选择修正一致。)
修正自底向上进行。修正完单表选择后,先看两表连接:只要至少一个输入的基数被修正过,就先根据修正后的输入得到新的输出估计,再乘以缓存中记录的该连接的补偿修正因子,作为修正后的估计。然后对三表连接、四表连接依次重复,直到覆盖全部表的连接。
例 4.2:接例 4.1,store_sales 与 household_demographics 连接(ss ⋈ hd)的估计,由修正后的 ss 基数和优化器估计的连接选择率算出,再用执行 Q1 后缓存的补偿修正因子加以修正;随后对更大的连接重复这一过程。最终把这条自底向上路径上的修正结果注入优化器,得到最优计划。值得注意的是,它不一定与 Q1 的计划相同。
完成全部修正后,SWatcher+ 用修正后的估计集合调用优化器得到执行计划,并记录其估算代价和规划时间。
执行后。先做基本的记账:记录总执行时间;如果做过任何修正,就为该计划的签名在缓存里新建一条映射(覆盖同签名的旧条目):对计划中每个连接子计划,修正因子 = 观察到的输出基数 ÷ 仅用选择修正、不做连接修正时得到的估计值。也就是说,把该因子应用到经选择修正的输入上,就能复现本次观察到的连接基数。同时记录创建时间、查询以及执行/规划时间等元数据。
接着,对查询中的每个单表选择子查询,决定是纳入监视集(若尚未在内)还是淘汰(若已在内)。从概念上说,一个子查询值得监控,当且仅当用真实基数修正它的估计能让计划大幅变快——加速至少达到最低加速比阈值。由于真实基数可以从执行中拿到,理论上可以用修正后的基数重新优化、执行新计划、再与未修正的计划比较速度,但这样开销无法接受。因此我们不执行任何新查询,只估算潜在提升。为了进一步减少重新优化的次数,先用两个阈值做快速检查,排除不值得监控的情况:直观地说,已经跑得很快的查询没必要去折腾;优化本身就很耗时的查询,折腾的开销又太大。具体如下:
若子查询不在监视集中: 1. 快速检查:若查询执行时间低于最短执行时间阈值,或优化时间高于最长优化时间阈值,不纳入。 2. 加速检查:调用优化器分别得到基线计划(仅用模型估计)和“只纳入这一个子查询”时会得到的计划;然后用本次执行中免费观察到的真实基数,比较两者的代价,得到预估加速比。当且仅当预估加速比不低于阈值时纳入,并记录其真实基数与加速比。
若子查询已在监视集中: 1. 快速检查:用之前记录的加速比,估算“如果没有监控它”时的执行时间;若低于最短执行时间阈值,或优化时间高于最长优化时间阈值,则淘汰。 2. 加速检查:按上述方法重新计算预估加速比,当且仅当低于阈值时淘汰。淘汰时,同时清除缓存中签名涉及该子查询的条目;否则更新其记录。
4.2 重训模型
每纳入一个新的监视集成员后,SWatcher+ 检查监视集大小;若超过规模上限,就触发模型刷新。训练集先加入所有监视集查询及其最新真实基数(与第三节相同)。由于此时监视集很小,也不再是负载的无偏样本,所以还要从负载中随机抽样查询补足训练集。(我们假设 SWatcher+ 能从某种负载表示中抽样,比如查询日志;最坏情况下它可以单独维护一个历史查询的蓄水池样本,由于不跟踪基数,开销可以忽略。)这些补充查询未被监控过,因此需要查询数据库获取真实基数。
重训完成后,SWatcher+ 依据新模型重新初始化监视集,过程与 4.1 节中“子查询尚未在监视集中”的情况几乎相同,区别在于:数据可能已更新、新模型也可能改变计划,拿不到新鲜的执行与优化时间,因此为避免执行查询,改用缓存里最近的记录值。最后清空修正因子缓存。重新初始化不保证监视集规模会降到上限以下;若刷新后立即再次超限(这种情况不太可能出现),就像第三节那样对上限做指数退避。
4.3 成本分析与讨论
与 Watcher1D 一样,SWatcher+ 的成本也由处理数据更新、处理查询和重训三部分组成。由于挑选监控对象更严格,SWatcher+ 的监视集比 Watcher1D 小得多——第五节实验中通常小 90%。处理查询时,用监视集和缓存做修正、记账的额外开销很小;但对含 k 个单表选择子查询的查询,加速检查最多需要 k+1 次优化器调用(一次得到基线计划,每个子查询各一次)以及 k+1 次代价估算调用。好在实践中,快速检查(判断查询在不修正时是否跑得够久、优化时间是否够短)能省掉其中 40%–80% 的调用。如果执行的计划没有用到某个选择子查询,可能需要额外的计数查询,但如第三节所述,这种情况很少。另一方面,由于主动修正估计,SWatcher+ 通常能比 Watcher1D 更有效地改进计划、缩短执行时间。不过,SWatcher+ 每次刷新模型的代价更高:可能需要执行额外查询来凑够训练集,还要做与训练集规模成线性关系的优化器调用来重新初始化监视集。
表 1 的五个参数设置简单,几乎无需调优。例外是监视集规模上限,它主要取决于用户希望把数据更新处理开销限制在什么程度。我们认为可以扩展 SWatcher+ 来彻底去掉这个参数:不设上限,而是跟踪每个监视集成员的收益(对查询性能)和成本(处理数据更新时产生),确保长期净收益为正;一旦观察到监视集“颠簸”(thrashing),就强制重训模型。这留待未来研究。
五、实验(节译)
实验设置。Watcher 框架用 Python 3 实现,运行在 PostgreSQL 16.2 上,基数注入沿用已有工作的方法,计划提示借助 pg_hint_plan;报告的执行时间均为三次重复的中位数;服务器为 8 核 Intel Core i7-7700 @ 3.60GHz、1TB 磁盘。测试了三种模型:MSCN(复杂度、训练与推理开销都低,这里关闭其位图,作为纯查询驱动模型)、JGMP(结合各表数据样本和 PostgreSQL 最新的单表选择率估计,用更少训练样本就能达到相当或更高的准确率,但训练和推理开销更高)、Naru(直接学习数据分布,天然支持增量更新)。
数据与负载。主要使用 DSB 基准(规模因子 2;规模因子 10 的结果见完整版,结论相似),并用 IMDB/JOB-light 做真实数据验证。为模拟分布漂移,对带日期列的事实表采用 60 天滑动窗口:窗口前移时删除最早一天的数据、插入下一天的数据,60 天后数据库已是完全不同的状态;每个模板相关的数据更新量为 8 万到 155 万条。IMDB 按 production_year 做同样处理,共 245 万条更新。DSB 模板连接 5–9 张表,包括 12 个单选择条件模板(Q 开头)、2 个双条件(TJQ)、1 个三条件(THJQ)、1 个四条件(FJQ)。共生成 3000 个查询,与数据更新按三种节奏混合:3 批(每批 20 天更新 + 1000 个查询)、6 批(每批 10 天更新 + 500 个查询)、60 批(每天更新 + 50 个查询)。
对比方法。True(总是免费注入真实基数,不计开销,用来衡量“知道真相”的收益上限);Baseline(同一模型、从不更新);Periodic(每 X 天更新后刷新一次);LEO(按原论文尽量忠实复现:估计偏差超过 5% 就记录调整,相似度超过 80% 就应用调整,并随数据更新维护调整);Watcher1D;SWatcher+(相似度阈值 0.8,监视集上限 600,最长优化时间取最短执行时间阈值的 10%;最短执行时间阈值取训练集中快慢查询的分界点,不明显时取第 50 或 75 百分位;最低加速比 MSCN/Naru 取 101%、JGMP 取 100.01%)。另外做了一个不修正连接的 SWatcher+ 版本作为消融实验,默认版本(带连接修正)整体更好。
MSCN 上的结果。论文按现象把单条件模板分成四组:
- 注入真值有效组(Q013a、Q013b、Q100):True 与 Baseline 相差约 10%。SWatcher+ 端到端时间最低,优于 Baseline、Periodic、Watcher1D 和 LEO;它靠修正而非刷新,执行时间往往比用 Periodic 更新后的模型更接近 True。Watcher1D 和 LEO 的维护开销则抵消了执行时间上的节省。以 Q013b 为例,总执行时间 True 为 985.08 秒,Baseline 为 1087.64 秒;Watcher1D 处理数据更新的开销超过 90 秒,LEO 在数据更新上的开销达 172.67 秒。
- 注入真值反而变慢组(Q018、Q040、Q072):给了真实的选择基数,PostgreSQL 仍然依赖错误的连接选择率假设,最终计划可能更差。SWatcher+ 同时用缓存修正连接估计,缓解了这一问题;只盯选择估计的 Periodic、Watcher1D 和 LEO 则不太可靠——Periodic 的最佳设置竟是只刷新一次。SWatcher+ 在避免“越修越慢”方面最稳定,开销也可接受。
- 对估计不敏感组(Q019、Q099):各方法执行时间相近,差别在开销。SWatcher+ 靠选择性监控,开销比 Periodic 和 Watcher1D 低 5%–20%,远低于 LEO;在 Q019 上它根本没往监视集里加东西。
- 优化主导组(执行很快、整个负载执行时间不超过 100 秒,规划时间是执行时间的 2–4 倍):没有查询通过纳入检查,SWatcher+ 表现与 Baseline 相同,几乎没有额外开销;其他方法却在不必要的刷新或调整上浪费资源。例如在 Q101 上,Periodic 一次刷新周期要 39.18 秒,Watcher1D 一次刷新 20.51 秒、维护监视集 99.78 秒,LEO 调整花了 165.47 秒——都超过了不到 90 秒的总执行时间。
多条件连接(TJQ/THJQ/FJQ)。SWatcher+ 优于 Periodic 和“Watcher1D + Watcher2D”(后者增量维护连接基数),执行时间接近甚至优于 True,因为缓存修正因子能捕捉不止一种连接模式的修正。而 Watcher2D 维护连接基数占了 86% 的开销,超过端到端时间的 60%,即使是两表连接也不现实。在 8 表连接模板上,SWatcher+ 基于缓存的连接修正虽然是启发式、近似的,仍然轻量有效,端到端表现明显好于其他方法。
JGMP 与 Naru。换模型时,SWatcher+ 只需调整训练集规模(或训练/增量更新轮数)和最低加速比。JGMP 更准,更多模板变得对估计不敏感;SWatcher+ 在注入真值有效、注入真值反而变慢、优化主导三组中都优于 Baseline、Watcher1D 和 Periodic,在对估计不敏感组的 5 个模板中赢了 4 个;在 Q019、Q099、Q102 三个模板上 LEO 略胜,因为 SWatcher+ 要花些开销评估后才确认修正不值得,但差距很小。对于只支持插入、不支持删除增量更新的 Naru,单用增量更新版本表现不稳定,配合 SWatcher+ 后更稳定,端到端时间在各模板上都更优——例如在 Q013a 上,SWatcher+ 主动修正了 Naru 因忽略删除而产生的误差。
IMDB/JOB-light。Watcher1D 和 LEO 在 Q36 上的维护开销分别占总执行时间(约 1000 秒)的约 70% 和 90%,由于开销随更新量线性增长,在其他模板上同样不可接受。在所有分组中,SWatcher+ 都稳定优于 Periodic;Periodic 只有在最低刷新频率下才能与之相当,而这个设置只能事后才知道。
六、结论与未来工作
本文研究了学习型基数估计模型在动态环境中的部署问题。我们在设计和评估上都采取整体视角,既考虑基数估计对查询性能的最终影响,也考虑维护、重训和使用模型的开销。结果揭示了两个陷阱:一是执着于改进单表估计(仅仅因为它容易做),二是在维护连接估计上投入过度(仅仅因为它可能带来更大的性能提升)。SWatcher+ 则通过基于收益的分析审慎决定维护什么,有效降低了开销。按照设计,它能自动识别值得专门监控的高影响估计,更好地处理因过度依赖单维修正而“越修越慢”的查询,并避免不必要的重训。实验表明,SWatcher+ 即使没有事后视角也表现出色,胜过事后选出最优周期的周期性重训;在收益空间极小的困难场景中,也能交出合理的表现。
SWatcher+ 仍有若干改进方向,例如显式监控/建模更新成本、对更新和计数查询做批处理、用概要(sketch)结构在更新下维护基数估计、用估算代价之外的信号评估修正收益等,详见完整版。尽管仍有提升空间,我们相信 SWatcher+ 及其核心原则——选择性监控、主动修正估计、关注端到端运行时间而非单纯的估计误差指标——为部署学习型基数估计模型提供了一个实用方案,也为这一领域的后续研究提供了有价值的启示。
译者注
- 为什么值得关注:基数估计是基于代价的优化器(CBO)的“地基”,估错一个中间结果的行数,就可能选错连接顺序或连接算法。这篇论文最有价值的观点不是某个新模型,而是一个工程判断:衡量标准应该是端到端运行时间,而不是 Q-error。估得更准但计划更慢(文中的“注入真值反而变慢”组)在真实系统里并不罕见。
- 对国内数据库工程师的启发:SWatcher+ 的思路——只为“估错代价大”的子查询付出监控成本、用执行反馈就地修正、按“需要修正的东西有多少”而不是固定周期来触发重训——同样适用于传统统计信息的维护策略,例如决定哪些表、哪些列需要更频繁地收集统计信息,或为哪些慢 SQL 做执行计划干预。落地时要特别评估数据更新路径上的额外开销:论文里 Watcher1D 的开销有六成多花在处理数据更新上,在写多读少的 OLTP 系统中这一点尤其敏感。
- 与华为云产品的关系:论文原型基于 PostgreSQL 实现(EXPLAIN ANALYZE 获取真实基数、pg_hint_plan 指定计划)。华为云 GaussDB 源自 openGauss 内核,同样采用基于代价的优化器并依赖统计信息,若在其上尝试类似的“执行反馈修正”思路,可以从慢查询分析与执行计划对比入手,先找出估计偏差最大且最影响耗时的那一小部分 SQL,这正是本文“选择性”的精髓。
- 点赞
- 收藏
- 关注作者
评论(0)