← 返回文章列表
产品动态 4 分钟阅读 炬鲸团队

用 SQL 查日志:像查数据库一样做故障排查

全文检索在日志量上来后就不好使了:没法聚合、没法过滤、没法跨字段关联。本文介绍炬鲸 OBSERVE 的 SQL 风格日志检索,给出常用查询写法、与全文检索的配合方式,以及让十亿级日志秒级聚合的索引实践。

为什么日志检索需要 SQL

全文检索(关键词模糊匹配)在日志量小时够用,一旦日志量上到每天几十亿条、上百台机器,问题就暴露了:无法聚合、无法做条件过滤、无法跨字段关联。比如"过去 5 分钟 502 错误按上游服务分组、取 Top 10"这种诉求,纯全文检索要么写正则,要么导出到别处二次处理。

炬鲸 OBSERVE 把日志检索做成 SQL 语义。底层是列式存储加向量化执行引擎,日志字段(时间、级别、trace_id、pod、状态码)先做类型化,再用 SQL 查询。运维不需要学新的 DSL,会写数据库查询就能上手。因为是真正的 SQL 语义,交互式查询可以原样保存成告警条件或大屏面板,不用重写。

常用查询写法

几个高频场景的写法:

按状态码聚合错误:

SELECT status, count(*) AS cnt
FROM logs
WHERE status >= 500
  AND time >= now() - interval '5 minute'
GROUP BY status
ORDER BY cnt DESC

按上游服务定位慢请求:

SELECT service, p95(latency) AS p95
FROM logs
WHERE time >= now() - interval '1 hour'
GROUP BY service
HAVING p95 > 500

关联 trace 查看完整链路:

SELECT trace_id, span_id, message
FROM logs
WHERE trace_id = 'e4f2...'
ORDER BY time

用共享字段把访问日志和应用日志 join 起来:

SELECT a.request_id, a.status, b.message
FROM access_logs a
LEFT JOIN app_logs b
  ON a.request_id = b.request_id
WHERE a.status >= 500
  AND a.time >= now() - interval '10 minute'

这些查询可以直接在控制台执行,也能保存为告警条件定时执行。最后这条 join 是全文检索做不到的——把两条日志流按字段关联起来,正是关系模型的价值所在。

与全文检索的对比

全文检索的优势是"我不知道字段名,只想搜个关键词"。SQL 的优势是"我知道要算什么"。实际定位问题时两者是配合关系,不是二选一:

  • 先用关键词缩小范围(比如搜"timeout")
  • 再用 SQL 做聚合、排序、关联,找到根因维度
  • 把高频 SQL 存成视图,下次直接复用

一个反例:用纯关键词排查"哪些接口 P99 超过 1 秒",需要把日志全量拉下来自己算,SQL 一条语句就出结果。反过来,如果连字段长什么样都不知道,先全文搜一把再转 SQL 会更顺。

性能与索引建议

SQL 快的前提是字段有类型、有索引。几条实践建议:

  1. 高频过滤字段(time、service、trace_id、status)设为索引列
  2. 时间字段永远放进 WHERE,减少扫描范围
  3. 避免对高基数字段(如 trace_id)做前缀不匹配的模糊查询
  4. 聚合查询优先用近似函数(approx_distinct)换取速度
  5. 把大查询拆成按时间分片,避免单次扫描过多分区
  6. 计算下推:尽早过滤、延迟聚合,宽行不要 SELECT *

按照这些习惯,十亿级日志的秒级聚合是稳定可达的。我们在现场最常见的坑,是把日志当文本档案存,然后抱怨跨字段 join 慢——字段类型化、过滤列建索引,同一份数据立刻就快起来了。