智能查询计划生成:AI 如何让数据库优化器跳出局部最优
一、传统优化器的天花板:代价模型的系统性偏差
数据库查询优化器本质是一个组合优化问题:从等价的关系代数变换空间中,找到代价最小的执行计划。传统优化器采用动态规划或贪心搜索,在有限的搜索空间内寻找最优解。但这个搜索空间受两个硬约束限制:一是启发式剪枝过早裁剪了潜在更优的执行路径,二是代价模型对数据分布的建模精度不足。
生产环境中的典型表现:同一张表上三个可选索引,优化器基于 Cardinality 估值选择了二级索引,但实际执行时回表代价远超预期,而主键范围扫描反而更快。这类问题在多表 JOIN 场景中更加严重——JOIN 顺序的排列组合随表数呈阶乘增长,优化器在表数超过 6 时被迫降级为贪心搜索,极易陷入局部最优。
AI 驱动的查询优化正是针对这一瓶颈:用机器学习模型替代或增强代价估算环节,让优化器在更大的搜索空间中做出更准确的决策。
二、AI 查询优化的三种架构路径
当前学术界和工业界的 AI 查询优化方案,可归纳为三种架构路径:
flowchart TB subgraph 传统优化器 A[SQL] --> B[语法解析] B --> C[代价模型] C --> D[执行计划] end subgraph 路径一: 代价模型增强 E1[SQL 特征提取] --> F1[ML 代价预测模型] F1 --> G1[替换/校准传统代价] G1 --> D end subgraph 路径二: 端到端计划生成 E2[SQL 编码] --> F2[Seq2Seq / GNN] F2 --> G2[直接输出 JOIN 顺序] G2 --> D end subgraph 路径三: 强化学习探索 E3[状态: 当前计划] --> F3[RL Agent] F3 --> G3[动作: 计划变换] G3 --> H3[奖励: 执行延迟] H3 --> F3 end A -.-> E1 A -.-> E2 A -.-> E32.1 路径一:代价模型增强(Learned Cost Model)
核心思路:保留传统优化器的搜索框架,仅用 ML 模型替换代价估算函数。代表工作为 Microsoft 的 Learned Cardinality Estimator。
优势:改动最小,可渐进式替换,与现有优化器框架兼容。劣势:搜索空间未扩展,仍受启发式剪枝限制。
2.2 路径二:端到端计划生成
核心思路:将 SQL 查询编码为向量,通过序列模型或图神经网络直接输出 JOIN 顺序和算子选择。代表工作为 Neo 的 RTOS 模型。
优势:搜索空间不受启发式限制。劣势:训练数据需求大,泛化到未见过的查询模式时表现不稳定。
2.3 路径三:强化学习探索
核心思路:将查询优化建模为马尔可夫决策过程,Agent 通过与环境交互学习最优策略。状态为当前执行计划,动作为计划变换操作(交换 JOIN 顺序、更换索引等),奖励为执行延迟的负值。
优势:可在线持续学习,适应数据分布变化。劣势:训练收敛慢,冷启动阶段性能差,生产环境直接部署风险高。
三、代价模型增强的生产级实现
以路径一为例,实现一个基于梯度提升树的 Cardinality 估计模型:
import numpy as np import lightgbm as lgb from typing import List, Dict, Tuple import logging class LearnedCardinalityEstimator: """ 基于梯度提升树的基数估计器。 为什么选 LightGBM 而非神经网络: 1. 表格数据上树模型精度通常优于 NN 2. 推理延迟低(微秒级),不影响优化器决策耗时 3. 特征可解释性强,便于排查模型偏差 """ def __init__(self, model_path: str = None): self.model = None self.feature_columns = [ 'table_rows', # 表总行数 'index_cardinality', # 索引基数 'num_range_predicates', # 范围谓词数量 'num_eq_predicates', # 等值谓词数量 'selectivity_estimate', # 传统优化器的选择性估值 'histogram_skew', # 直方图偏度 'correlation_with_pk', # 与主键的相关性 ] if model_path: self.load_model(model_path) def extract_features( self, query_context: Dict ) -> np.ndarray: """ 从查询上下文中提取特征向量。 为什么不直接用 SQL 文本做特征: SQL 文本的高维稀疏性导致模型泛化差, 结构化特征能更好地捕获基数与查询条件的关联。 """ features = [] for col in self.feature_columns: val = query_context.get(col, 0.0) features.append(float(val)) return np.array(features, dtype=np.float32) def train( self, train_features: np.ndarray, train_labels: np.ndarray, val_features: np.ndarray = None, val_labels: np.ndarray = None ): """ 训练基数预测模型。 标签为 log(实际行数),因为行数跨多个数量级, 对数变换使模型在大小表上均有合理精度。 """ log_labels = np.log1p(train_labels) params = { 'objective': 'regression', 'metric': 'rmse', 'learning_rate': 0.05, 'num_leaves': 63, 'min_child_samples': 50, # 正则化防止过拟合,确保对未见查询的泛化 'lambda_l1': 0.1, 'lambda_l2': 0.2, 'feature_fraction': 0.8, } train_set = lgb.Dataset(train_features, log_labels) valid_sets = None if val_features is not None: val_set = lgb.Dataset( val_features, np.log1p(val_labels), reference=train_set ) valid_sets = [val_set] self.model = lgb.train( params, train_set, num_boost_round=500, valid_sets=valid_sets, callbacks=[ lgb.early_stopping(stopping_rounds=30), lgb.log_evaluation(period=50) ] ) logging.info( f"模型训练完成,特征数={len(self.feature_columns)}" ) def predict(self, query_context: Dict) -> float: """ 预测查询的基数(行数)。 返回 exp(prediction) - 1 还原对数变换。 """ if self.model is None: raise RuntimeError("模型未加载,请先 train 或 load_model") features = self.extract_features(query_context).reshape(1, -1) log_pred = self.model.predict(features)[0] predicted_rows = np.expm1(log_pred) # 下限保护:预测值不能小于 1 return max(1.0, predicted_rows) def load_model(self, path: str): self.model = lgb.Booster(model_file=path) def save_model(self, path: str): if self.model is None: raise RuntimeError("无可用模型") self.model.save_model(path)3.1 与优化器的集成方式
生产环境中,Learned Cardinality Estimator 不直接替换优化器,而是作为校准层:
- 优化器先用传统代价模型生成候选计划。
- 对每个候选计划,用 ML 模型重新估算基数。
- 若 ML 估算与传统估算偏差超过阈值(如 50%),以 ML 估算为准重新计算代价。
- 选择代价最低的计划执行。
这种"双模型校验"策略避免了 ML 模型冷启动时的灾难性错误。
四、AI 优化器的落地瓶颈与适用边界
4.1 训练数据的获取成本
ML 代价模型需要大量"查询-实际行数"配对数据。在生产环境中采集这些数据,需要在执行器层埋点记录每个算子的实际输出行数,这会带来 3%-5% 的性能开销。对于延迟敏感的核心链路,这个开销不可接受。
4.2 分布漂移问题
数据分布随业务变化而漂移,ML 模型的预测精度会随时间衰减。实测数据:模型训练后 2 周,Q-Error(预测值/实际值)中位数从 1.3 上升到 2.1。需要建立模型监控和自动重训练流水线,但这增加了系统复杂度。
4.3 推理延迟约束
优化器的决策时间通常在毫秒级。ML 模型的推理延迟必须控制在 1ms 以内,否则会拖慢查询编译阶段。LightGBM 在 7 维特征上的推理延迟约 50us,满足要求;但若特征维度扩展到 100+(如编码 SQL 文本),推理延迟可能超标。
4.4 适用场景总结
| 场景 | AI 优化器价值 | 风险等级 |
|---|---|---|
| 多表 JOIN(6+ 表) | 高:扩展搜索空间 | 中 |
| 数据倾斜严重 | 高:修正基数偏差 | 低 |
| 查询模式固定 | 中:可用 FORCE INDEX 替代 | 低 |
| 查询模式多变 | 高:自适应选择 | 高 |
| 低延迟 OLTP | 低:优化器耗时占比大 | 高 |
五、总结
AI 查询优化的核心价值在于突破传统优化器的两个瓶颈:搜索空间受限和代价模型精度不足。三种架构路径中,代价模型增强方案改动最小、落地风险最低,适合作为生产环境的切入点。端到端计划生成和强化学习方案潜力更大,但训练数据需求、分布漂移和推理延迟三个问题尚未完全解决。
落地路线建议:第一步,在从库上部署查询执行数据采集,积累训练数据;第二步,训练 Learned Cardinality Estimator,以影子模式运行(仅记录预测结果,不实际影响执行计划);第三步,对比 ML 预测与传统估算的偏差,确认模型精度达标后,逐步在校准层中启用 ML 估算。全程必须保留传统优化器作为降级兜底,确保 AI 模型异常时不影响业务可用性。