传统关键词搜索在日志规模变大后难以回答聚合与分布类问题。本文介绍炬鲸日志检索的 SQL 化能力,从字段化解析、常用查询模板到分区与列式存储的性能保障,帮助运维把“搜日志”变成“问答案”。
排查故障,大多数人第一反应是“搜日志”:输个关键词,圈个时间范围,一页页翻。这个流程在日志量小的时候没问题,一旦上了规模,就会撞上几堵墙。
本质原因:日志被当成了“文本”,而不是“数据”。全文检索擅长“找到”,但回答不了“有多少、分布如何、趋势怎样”这类问题。
炬鲸把日志检索做成 SQL 化,目的就是让日志回到它本来的样子:一条带时间戳的结构化记录。你可以像查数据表一样查日志。
SQL 检索的前提是日志有“字段”。炬鲸的采集端在写入时做两件事:
{"level":"ERROR","service":"pay","cost_ms":1523} 拆成 level、service、cost_ms 三个字段。172.16.0.10 - - [12/Sep/2026:10:23:41 +0800] "GET /api/order?id=123 HTTP/1.1" 500 812
用一条 grok 模板就能抽成 client_ip、method、uri、status、body_bytes 五个字段,之后就能按 status、uri 做聚合。
字段建立后,检索从 ERROR AND pay AND cost_ms > 1000 变成:
SELECT host, COUNT(*) AS cnt
FROM logs
WHERE level = 'ERROR'
AND service = 'pay'
AND cost_ms > 1000
AND ts BETWEEN '2026-09-12 10:00:00' AND '2026-09-12 11:00:00'
GROUP BY host
ORDER BY cnt DESC
几个一线最常用的模板,直接套用。
错误率趋势
SELECT DATE_FORMAT(ts, '%Y-%m-%d %H:%i') AS minute,
SUM(level = 'ERROR') / COUNT(*) AS err_rate
FROM logs
WHERE service = 'order' AND ts > NOW() - INTERVAL 1 HOUR
GROUP BY minute
慢请求 Top 10
SELECT trace_id, uri, cost_ms
FROM logs
WHERE cost_ms > 2000
ORDER BY cost_ms DESC
LIMIT 10
按机房分组统计
SELECT region, COUNT(*) AS total, AVG(cost_ms) AS avg_cost
FROM logs
WHERE ts > NOW() - INTERVAL 1 DAY
GROUP BY region
耗时分位数(找长尾)
SELECT region,
PERCENTILE(cost_ms, 0.5) AS p50,
PERCENTILE(cost_ms, 0.99) AS p99
FROM logs
WHERE service = 'pay' AND ts > NOW() - INTERVAL 1 HOUR
GROUP BY region
这些查询可以存成“视图”,固定挂在告警规则上,后续一键复用。团队里的常用查询沉淀成共享视图,新人上手直接调用,不用每次重写。
SQL 化最大的顾虑是性能:海量日志上跑聚合,会不会把系统拖垮。炬鲸在这块做了三层。
ts 会自动下推成分区裁剪。COUNT(*) 不会把整行内容解出来。实测在 10 亿级日志量下,单表 GROUP BY 聚合查询的 P95 在 2 秒内;如果不加时间过滤直接全表扫,会被优化器拦截提示“请加时间范围”。
SQL 检索不是孤立的功能,它和另外两块能力是连通的。
trace_id 的日志,可以一键跳到链路追踪,看这条日志前后的完整调用链。GROUP BY 看聚合结果,而不是把几万条明细拉出来翻。对一线同学来说,这套东西的核心价值很朴素:排查问题时,从“搜到日志”变成“问出答案”。