还在维护 ES + ClickHouse 两套系统做日志搜索和分析?本教程带你从零开始,在 SelectDB(基于 Apache Doris)中用
search()函数替代 ES 全文检索功能,实现搜索 + 分析同一条 SQL、同一份数据。含完整建表、索引配置、数据导入、15 种查询算子用法和 ES 迁移指南。
关键词:SelectDB · Apache Doris · search() · 全文检索 · Elasticsearch 迁移 · 倒排索引 · 日志分析 · 实战教程
前言:你的日志架构还能更简单吗?
当前主流日志架构:
应用日志 → Kafka → Logstash → Elasticsearch(搜索)
└──→ ClickHouse(分析)
维护两套集群 + 一条同步链路,成本高、运维累。本教程的目标:用一套 SelectDB 集群替代这个双系统,搜索和分析用同一条 SQL 完成。
第 1 步:环境准备
| 组件 | 最低版本 | 作用 |
|---|---|---|
| SelectDB 或 Apache Doris | 2.1+(需支持 V3 倒排索引) | 核心引擎 |
| MySQL Client 或其他 SQL 客户端 | 任意 | 执行 SQL |
确保 SelectDB 集群已启动并可通过 MySQL 协议连接(默认端口 9030)。
第 2 步:创建带倒排索引的日志表
sql
-- 1. 创建数据库
CREATE DATABASE IF NOT EXISTS log_search;
-- 2. 创建日志表 + 倒排索引
CREATE TABLE IF NOT EXISTS log_search.inference_logs (
log_time DATETIME, -- 日志时间
request_id VARCHAR(64), -- 请求 ID
model_name VARCHAR(32), -- 模型名称
level VARCHAR(16), -- 日志级别
error_msg TEXT, -- 错误信息(需全文检索)
context TEXT, -- 上下文(需全文检索)
prompt_tokens INT, -- prompt token 数
completion_tokens INT, -- 回复 token 数
latency_ms INT, -- 延迟(毫秒)
-- ===== 倒排索引定义 =====
INDEX idx_level(level) USING INVERTED,
INDEX idx_error(error_msg) USING INVERTED PROPERTIES(
"parser" = "unicode", -- 中英文混合分词
"support_phrase" = "true" -- 支持精确短语搜索
),
INDEX idx_context(context) USING INVERTED PROPERTIES(
"parser" = "unicode",
"support_phrase" = "true"
),
INDEX idx_model(model_name) USING INVERTED
) ENGINE = OLAP
DUPLICATE KEY(log_time)
DISTRIBUTED BY HASH(request_id) BUCKETS 16
PROPERTIES (
"inverted_index_storage_format" = "V3", -- V3 格式,压缩效率高
"compression" = "ZSTD"
);
配置说明:
USING INVERTED:为字段创建倒排索引,search() 函数依赖此索引加速检索support_phrase = "true":如需搜索"CUDA out of memory"这样的精确短语,必须开启parser = "unicode":中英文混合分词,不需要像 ES 那样额外安装 IK 分词器V3倒排格式:支持 ZSTD 字典压缩,索引体积比 ES 减少约 20%
重点 :SelectDB 的倒排索引加减不需要重写数据文件 ------可以先导入数据,再根据查询需求针对性添加索引,也可以随时删除冗余的索引释放空间。整个操作无需停服、无需重建表。
第 3 步:导入测试数据
sql
-- Stream Load 方式导入(示例)
-- 也可以通过 INSERT INTO SELECT 从其他表导入
INSERT INTO log_search.inference_logs VALUES
('2026-03-20 10:00:01', 'req_001', 'gpt-4', 'ERROR', 'CUDA out of memory at layer 12', 'model inference context...', 2048, 512,3200),
('2026-03-20 10:00:02', 'req_002', 'gpt-4', 'ERROR', 'connection refused to backend', 'routing context...', 512, 128, 500),
('2026-03-20 10:00:03', 'req_003', 'gpt-4o', 'ERROR', 'CUDA out of memory at layer 8', 'model inference context...', 4096, 1024,5800),
('2026-03-20 10:00:04', 'req_004', 'gpt-4o', 'WARN', 'high latency detected', 'monitoring context...', 1024, 256, 4500),
('2026-03-20 10:00:05', 'req_005', 'gpt-4', 'ERROR', 'timeout waiting for response', 'routing context...', 256, 64, 8000),
('2026-03-20 10:00:06', 'req_006', 'gpt-4o', 'ERROR', 'CUDA out of memory at layer 24', 'model inference context...', 8192, 2048,9200);
第 4 步:基础搜索 ------ 你的第一个 search() 查询
sql
-- 搜索所有 GPU OOM 错误
SELECT request_id, model_name, error_msg, latency_ms
FROM log_search.inference_logs
WHERE search('level:ERROR AND error_msg:"CUDA out of memory"')
ORDER BY latency_ms DESC;
结果解读 :这条 SQL 在倒排索引中先定位包含 "CUDA out of memory" 短语 + level:ERROR 的行,再按延迟降序排列。语法和 ES query_string 几乎一致。
第 5 步:进阶查询 ------ 15 种算子实战
5.1 多条件组合定位故障
vbnet
-- 5 种算子同时使用:TERM + PHRASE + RANGE + NOT + IN
SELECT *
FROM log_search.inference_logs
WHERE search('
level:ERROR
AND error_msg:"out of"
AND latency:[1000 TO *]
AND NOT model_name:gpt-4o
AND request_id:IN(req_001 req_002)
');
5.2 正则匹配
sql
-- 抓住所有 CUDA 相关错误,不管具体描述是什么
SELECT request_id, error_msg
FROM log_search.inference_logs
WHERE search('error_msg:/CUDA.*error/ AND level:ERROR');
5.3 BM25 相关性打分排序
sql
-- 按搜索相关性排序,最匹配的排前面
SELECT request_id, error_msg, score() AS relevance_score
FROM log_search.inference_logs
WHERE search('error_msg:"memory allocation" OR error_msg:"CUDA error"')
ORDER BY relevance_score DESC
LIMIT 10;
score() 列暴露 BM25 评分(IDF 加权 + 文档长度归一化),配合存储层 TopN 优化,全量排序性能无忧。
5.4 搜索 + 聚合分析(融合模式)
sql
-- 一条 SQL:搜索 + 统计分析,无需跨系统搬运数据
SELECT
model_name,
COUNT(*) AS error_count,
AVG(prompt_tokens) AS avg_prompt_tokens,
AVG(latency_ms) AS avg_latency
FROM log_search.inference_logs
WHERE search('level:ERROR AND error_msg:"out of"')
GROUP BY model_name
ORDER BY error_count DESC;
这一步是本教程的核心亮点:传统方案中,你需要在 ES 搜索 → 导出结果 → 在 OLAP 聚合。现在同一条 SQL 就能完成,延迟从分钟级降到秒级。
5.5 窗口函数:追踪错误趋势
sql
-- 按小时统计错误数量,并计算环比变化
SELECT
model_name,
DATE_TRUNC('hour', log_time) AS hour,
COUNT(*) AS error_count,
LAG(COUNT(*)) OVER (PARTITION BY model_name ORDER BY DATE_TRUNC('hour', log_time)) AS prev_hour_errors
FROM log_search.inference_logs
WHERE search('level:ERROR')
GROUP BY model_name, DATE_TRUNC('hour', log_time)
ORDER BY hour;
第 6 步:从 Elasticsearch 迁移到 SelectDB
6.1 索引映射对照表
| Elasticsearch | SelectDB |
|---|---|
"type": "text" |
TEXT + INDEX ... USING INVERTED |
"type": "keyword" |
VARCHAR(32) + INDEX ... USING INVERTED |
"type": "long" |
BIGINT |
"type": "date" |
DATETIME |
6.2 查询改写对照
sql
-- === ES 查询 ===
GET /logs/_search
{
"query": {
"query_string": {
"query": "level:ERROR AND error_msg:"timeout""
}
}
}
-- === SelectDB 查询 ===
SELECT * FROM log_search.inference_logs
WHERE search('level:ERROR AND error_msg:"timeout"');
90% 的查询只需要:REST API → SQL WHERE + 加个 search()
6.3 迁移收益
| 指标 | 双系统(ES + OLAP) | SelectDB search() |
|---|---|---|
| 集群数量 | 2 套 | 1 套 |
| 同步链路 | Kafka → Logstash → ES | 无(直写) |
| 存储膨胀 | ES 侧 2-3 倍 | ZSTD 压缩 |
| TB 级存储成本 | 基准 | 降 50% |
| 查询延迟(搜+析) | 分钟级(跨系统) | 秒级(同引擎) |
常见问题
Q:倒排索引对写入性能有影响吗? A:有一定开销,但在 DUPLICATE KEY 模型下日志场景以追加写为主,影响可控。V3 格式的压缩和索引构建性能已针对性优化。
Q:search() 和 ES query_string 语法有差异吗? A:query_string 模式下语法兼容,大部分查询可以直接移植。Lucene 模式支持 MUST/SHOULD/MUST_NOT 语义。
Q:能处理嵌套 JSON 日志吗? A:可以。配合 VARIANT 类型 + search() 的 NESTED 算子,可直接在数组内部检索,无需拆表。
Q:需要像 ES 一样提前规划索引 mapping 吗? A:不需要严格规划。先导入数据,后续可以随时添加或删除索引,无需停服、无需重建表。
扩展阅读
- Apache Doris 倒排索引文档
- SelectDB 官网 --- 基于 Apache Doris 的企业级产品
- BM25 打分优化详解
关于 Apache Doris :Apache Doris 是高性能实时分析数据库,支持 PB 级数据亚秒级查询,广泛应用于报表分析、Ad-hoc 查询、统一数仓等场景。SelectDB 是 Apache Doris 的商业化公司,提供企业级支持和云服务。欢迎加入 Doris 社区 交流更多实践。