基于深度学习的查询代价估算:替代传统直方图统计的下一代基数估计方案
基于深度学习的查询代价估算:替代传统直方图统计的下一代基数估计方案
一、当直方图遇到数据倾斜——优化器决策的致命盲区
周六凌晨两点,我收到了一条紧急告警:核心交易查询从平时的5毫秒飙升到3秒。排查发现,优化器根据统计信息估算结果集只有几百行,选择了Nested Loop Join;实际结果是三百万行,最优方案应该是Hash Join。根本原因是统计信息中存储的是均匀分布假设,而该表的status列存在严重的倾斜——0.1%的数据是pending状态,优化器的均匀假设完全失效。
这个案例揭示了传统基数估计方法的根本局限:直方图和采样统计基于独立性和均匀性假设,而这些假设在真实数据中经常被打破。当数据存在复杂的列间关联(如订单金额与用户等级的相关性)或严重的分布倾斜时,传统统计方法的误差会急剧放大。
基于深度学习的基数估计方案,核心思路是用神经网络直接从数据中学习分布特征,替代传统的直方图和采样统计,提供更精确的行数估计。
flowchart LR
subgraph 传统方案
A1[ANALYZE TABLE] --> A2[直方图+采样]
A2 --> A3[独立性假设]
A3 --> A4[基数估计]
end
subgraph 深度学习方案
B1[数据特征提取] --> B2[训练数据集]
B2 --> B3[深度神经网络]
B3 --> B4[基数估计]
B4 --> B5[在线推理]
end
A4 -.->|误差: 10-100x| C[估计误差对比]
B5 -.->|误差: 1.5-3x| C
二、MSCN模型的原理与优势
目前学界最有代表性的方案是MSCN(Multi-Set Convolutional Network),微软研究院在2019年提出,并在SIGMOD上发表了相关成果。
模型架构:MSCN的核心是一个多集合卷积网络,将查询中的每个谓词(过滤条件)和JOIN条件编码为特征向量,通过卷积层学习条件之间的交互关系,最后通过全连接层输出基数估计。
与传统方法的最大不同在于:MSCN不需要独立性假设。网络可以直接学习"当user_level=VIP且amount>10000时,结果集大概是多少"这样的联合分布特征。多列之间的相关关系被编码在网络的权重中,而非通过"各列选择率的乘积"这个粗糙的简化来估计。
训练数据的生成:训练模型需要大量的查询-基数对。通过在实际数据上执行真实查询或抽样查询,收集查询条件与真实结果行数的映射。关键技巧是:训练查询应该覆盖尽可能多样的条件组合,而不是只采样高频查询。我们设计了一个分层抽样策略:按表的连接关系分层,每层中随机生成覆盖不同列组合和值范围的查询。
三、工程化的关键挑战与解决方案
挑战一:模型更新与数据变化。数据分布是动态的——新增数据可能导致倾斜加剧、统计特征改变。模型需要定期重新训练以反映最新数据分布。我们的方案是建立"增量训练流水线":每日在低峰期执行全量训练(维护模型精度的基线),同时在高峰期采集增量查询-基数样本,用于微调模型参数。
挑战二:推理延迟。传统基数估计(查直方图)的延迟在微秒级别,而神经网络推理(即使使用优化的ONNX Runtime)也在百微秒级别。对于优化器在计划生成过程中需要执行数百次基数估计的场景,这个延迟累积不可忽略。解决方法是批量化——将同一查询计划中的多个基数估计请求合并为一次模型推理调用。
挑战三:可解释性缺失。当优化器基于AI估计选择了一个"差"的执行计划时,DBA无法理解为什么模型给出了错误的估计。作为补偿方案,我们在模型预测的同时输出不确定性估计(预测方差),高方差的估计触发降级到传统直方图方法。
import torch
import torch.nn as nn
class QueryCardinalityEstimator(nn.Module):
"""
基于MSCN的查询基数估计模型。
输入:查询的谓词特征向量(编码了表、列、操作符、值)
输出:估计结果行数 + 不确定性
"""
def __init__(self, feature_dim: int, hidden_dim: int = 256):
super().__init__()
self.fc1 = nn.Linear(feature_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, hidden_dim // 2)
self.mean_head = nn.Linear(hidden_dim // 2, 1)
self.var_head = nn.Linear(hidden_dim // 2, 1) # 不确定性估计
self.dropout = nn.Dropout(0.2)
self.relu = nn.ReLU()
def forward(self, x):
h = self.relu(self.fc1(x))
h = self.dropout(h)
h = self.relu(self.fc2(h))
h = self.dropout(h)
h = self.relu(self.fc3(h))
mean = torch.exp(self.mean_head(h)) # 正数输出,用指数激活
var = torch.exp(self.var_head(h)) # 方差也保持正数
return mean, var
def estimate_with_confidence(self, features):
"""带置信度的基数估计,当不确定性过高时返回回退标记"""
with torch.no_grad():
mean, var = self.forward(features)
relative_uncertainty = torch.sqrt(var) / (mean + 1)
if relative_uncertainty > 0.5: # 相对不确定性 > 50%
return mean.item(), {
"needs_fallback": True,
"reason": "high_uncertainty"
}
return mean.item(), {"confidence": "high"}
四、与传统方案的共存策略
深度学习基数估计不应完全替代传统方案,而是作为增强层与直方图方法共存。
分级使用策略:简单查询(单表单条件)使用传统直方图(更快且足够精确);复杂查询(多表JOIN、多条件组合)使用深度学习模型;当模型的不确定性过高时,使用传统方法兜底。这种混合策略在保证整体性能的同时,在复杂查询上获得显著精度提升。
冷启动策略:新上线的表缺乏训练数据,此时模型退化为直方图。随着查询的执行积累训练样本,逐步切换为模型估计。通过评估模型在验证集上的Q-Error(估计值/真实值的比值),动态决定是否启用模型估计。
五、总结
基于深度学习的查询代价估算,核心突破在于解决了传统方法中"独立性假设"这个根本性局限。当数据列之间存在复杂的关联关系时,神经网络的联合分布学习能力可以显著降低基数估计误差。
但这是一个"锦上添花"而非"雪中送炭"的改进。对于数据分布均匀、查询模式简单的场景,传统直方图已经足够好。深度学习方案的价值在数据倾斜严重、查询模式复杂的OLAP场景中才能真正体现。对于正在评估该方案的团队,建议先用传统方法的Q-Error量化当前估计精度问题,再决定是否值得引入模型的复杂性。
更多推荐




所有评论(0)