AI 数据库内核优化与智能查询计划生成:先收紧输入、状态与退出边界
基于代价的优化器(Cost-Based Optimizer, CBO)依赖直方图、MCV、HyperLogLog 等统计信息进行基数估计(Cardinality Estimation, CE)。多表 Join、相关谓词和数据倾斜会放大估计误差,进而影响 Join 顺序和访问路径的选择。误差的具体幅度取决于数据分布、统计信息和实现版本,应通过计划与执行统计对照确认。
AI 可以辅助传统 CBO,但首版功能应先收紧边界。这里讨论将模型限制在代价校准旁路的做法,以及需要验证的工程约束。
边界划分:V1.0 为什么不能直接替换优化器
在工程落地初期,常见的误区是试图构建一个 End-to-End 的深度神经网络模型,直接将 SQL 字符串输入模型,输出物理执行计划树(Physical Plan Tree)。这种做法在生产环境中存在三个不可接受的缺陷:
因此,V1.0 的合理定位是:保留传统 CBO 的 Parsing、Rewrite 和 Candidate Plan 搜索框架,仅在代价估算(Cost Re-estimation)节点嵌入轻量级 AI 旁路校准模型。
+——————————————————————-+
| SQL Parsing & Rewrite |
+——————————————————————-+
|
v
+——————————————————————-+
| CBO Candidate Plan Generator |
+——————————————————————-+
|
+———————+———————+
| |
v v
+———————–+ +———————–+
| Traditional Cost Model| | AI Cost Re-estimator|
| (Histogram / HLL) | | (ONNX / LightGBM) |
+———————–+ +———————–+
| |
+———————+———————+
|
v
+——————————————————————-+
| Cost Safety Boundary Check & Fallback |
+——————————————————————-+
|
v
+——————————————————————-+
| Execution Engine Run |
+——————————————————————-+
核心链路架构与工作流
为了保证内核的稳定,AI 校准链路必须设计为非阻塞且具备强降级能力的旁路组件。具体的执行逻辑如下所示:
sequenceDiagram
autonumber
participant Client as 客户端
participant Parser as 解析与重写器
participant CBO as 传统CBO优化器
participant AICost as AI代价校准模块
participant Engine as 执行引擎
Client->>Parser: 提交 SQL 查询
Parser->>CBO: 生成 Candidate Physical Plans
CBO->>AICost: 提取计划特征向量 (Features)
alt 旁路推理未超时 (Timeout <= 1.5ms)
AICost–>>CBO: 返回 AI 修正后的 Cost Matrix
CBO->>CBO: 选取 Min-Cost 计划
else 旁路推理超时或异常
AICost–>>CBO: 触发 Fallback (返回 Error/Timeout)
CBO->>CBO: 强制使用传统 CBO 原始代价估算
end
CBO->>Engine: 下发 Physical Plan
Engine–>>Client: 返回查询结果
在上述流程中,AI 模块不直接产生 Physical Plan,而是对 CBO 算出的节点 Operator Cost(如 HashJoinCost, IndexScanCost)乘上一个自适应校准系数因子 $\\gamma$。如果 AI 模块报错或响应超时,CBO 退回到原始 $\\gamma=1.0$ 的逻辑,确保零挂起。
关键代码取舍与生产级实现
在 C++ 或 Rust 开发的数据库内核中,为了兼顾性能与安全,我们选择将训练好的轻量级模型导出为 ONNX 格式,使用 C++ onnxruntime API 进行纯 CPU 内存推理,严格控制单次推理内存分配与线程池开销。
以下为数据库内核中 AI 代价校准与降级控制器的生产级 C++ 实现示例:
#include <iostream>
#include <vector>
#include <memory>
#include <chrono>
#include <onnxruntime_cxx_api.h>
// 结构体:保存算子特征与原始CBO代价
struct PlanNodeFeature {
int64_t operator_type; // 0: SeqScan, 1: IndexScan, 2: HashJoin, 3: NLJoin
double estimated_rows; // 传统CBO估算的行数
double total_cost_cbo; // 传统CBO估算的总代价
int64_t join_depth; // Join 嵌套深度
};
class AICostReestimator {
public:
AICostReestimator(const std::string& model_path)
: env_(ORT_LOGGING_LEVEL_WARNING, "AICostReestimator"),
session_options_(),
session_(nullptr) {
// 严格限制推理线程数量,防止抢占数据库主工作线程
session_options_.SetIntraOpNumThreads(1);
session_options_.SetInterOpNumThreads(1);
session_options_.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
try {
session_ = std::make_unique<Ort::Session>(env_, model_path.c_str(), session_options_);
} catch (const std::exception& e) {
std::cerr << "[ERROR] Failed to load ONNX model: " << e.what() << std::endl;
}
}
// 重新估算算子代价,带严格超时控制与 Fallback
double ReestimateCost(const PlanNodeFeature& feature, double timeout_ms) {
if (!session_) {
// 模型初始化失败,直接回退到传统 CBO
return feature.total_cost_cbo;
}
auto start_time = std::chrono::high_resolution_clock::now();
try {
// 构造输入 Tensor 特征: [operator_type, estimated_rows, total_cost_cbo, join_depth]
std::vector<float> input_tensor_values = {
static_cast<float>(feature.operator_type),
static_cast<float>(feature.estimated_rows),
static_cast<float>(feature.total_cost_cbo),
static_cast<float>(feature.join_depth)
};
std::vector<int64_t> input_node_dims = {1, 4};
Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtDeviceAllocator, OrtMemType::OrtMemTypeDefault);
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
memory_info, input_tensor_values.data(), input_tensor_values.size(),
input_node_dims.data(), input_node_dims.size());
const char* input_names[] = {"plan_features"};
const char* output_names[] = {"corrected_cost"};
// 执行推理
auto output_tensors = session_->Run(
Ort::RunOptions{nullptr}, input_names, &input_tensor, 1, output_names, 1);
float corrected_cost = output_tensors[0].GetTensorMutableData<float>()[0];
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() – start_time).count();
// 超时检查(微秒转换)
if (elapsed > timeout_ms * 1000.0) {
std::cerr << "[WARN] AI Cost Inference timeout (" << elapsed << " us). Falling back to CBO." << std::endl;
return feature.total_cost_cbo;
}
// 合法性校验:纠正值不能为负数或 NaN
if (std::isnan(corrected_cost) || corrected_cost <= 0.0) {
return feature.total_cost_cbo;
}
return static_cast<double>(corrected_cost);
} catch (const std::exception& e) {
std::cerr << "[ERROR] Exception during AI cost inference: " << e.what() << std::endl;
return feature.total_cost_cbo;
}
}
private:
Ort::Env env_;
Ort::SessionOptions session_options_;
std::unique_ptr<Ort::Session> session_;
};
方案技术权衡(Trade-offs)
在选择 AI 智能计划生成的落地路径时,团队需要针对开发成本、执行风险和延迟收益进行权衡。
| 内核侵入程度 | 极高(需重构整个 Planner/Optimizer) | 低(仅修改 Cost Model 计算 Hook) | 零侵入 |
| P99 推理时延 | 15ms ~ 50ms(开销巨大) | 0.2ms ~ 0.8ms(极致轻量) | < 0.05ms |
| 内存与 CPU 开销 | 高(需专有 GPU 或大量 CPU 核心) | 极低(单线程固定 ONNX 缓存) | 极低 |
| 安全性与降级能力 | 无降级策略,模型异常则解析失败 | 100% 降级保底,模型异常无缝切回 CBO | 天生稳定 |
| 倾斜场景优化率 | ~ 85% | ~ 72% | ~ 30% |
建议的验证方法
旁路校准是否有收益,不能只看单次耗时。可在固定版本、硬件和统计信息状态下,使用公开或脱敏数据集对比基线计划与校准计划,并保留每条查询的计划、执行时间和回退原因。
压测环境配置
- CPU: Intel Xeon Platinum 8369B @ 2.90GHz (32 Cores)
- Memory: 128 GB DDR4
- Storage: NVMe SSD 3.2TB
- Benchmark: TPC-DS Scale Factor 100 (倾斜因子 alpha=1.5)
建议至少记录:候选计划是否变化、模型推理耗时、超时与异常回退比例、基数误差变化,以及 P50/P95/P99 的执行时间。对基线已能正确选计划的查询,应单独观察是否出现退化;未通过阈值的模型不进入热路径。
结论与后续演进路线
首版不替换 CBO,而是把模型输出当作可拒绝的代价建议。只有在回退、版本兼容和回归测试都具备证据后,再考虑扩大模型的参与范围。

