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

用 SQL 查日志:把海量日志当数据表来检索

传统关键词搜索在日志规模变大后难以回答聚合与分布类问题。本文介绍炬鲸日志检索的 SQL 化能力,从字段化解析、常用查询模板到分区与列式存储的性能保障,帮助运维把“搜日志”变成“问答案”。

排查故障,大多数人第一反应是“搜日志”:输个关键词,圈个时间范围,一页页翻。这个流程在日志量小的时候没问题,一旦上了规模,就会撞上几堵墙。

  • 关键词搜出来几万条,只能一页页翻,没法直接回答“有多少台机器报了同样的错”。
  • 想同时按“错误码 + 服务名 + 耗时”三个条件过滤,只能反复叠加关键词,逻辑是 AND 还是 OR 说不清。
  • 想做聚合——比如“每分钟错误率”“按机房分组的慢请求”——传统检索做不到,得把日志导出到别处再算。
  • 想算“99 分位耗时”这类统计,关键词检索完全没有概念。

本质原因:日志被当成了“文本”,而不是“数据”。全文检索擅长“找到”,但回答不了“有多少、分布如何、趋势怎样”这类问题。

炬鲸把日志检索做成 SQL 化,目的就是让日志回到它本来的样子:一条带时间戳的结构化记录。你可以像查数据表一样查日志。

字段化:SQL 能跑起来的前提

SQL 检索的前提是日志有“字段”。炬鲸的采集端在写入时做两件事:

  1. 自动解析:识别 JSON 日志,把 {"level":"ERROR","service":"pay","cost_ms":1523} 拆成 level、service、cost_ms 三个字段。
  2. 自定义解析:对非 JSON 的纯文本日志,配置 grok/正则模板,把关键片段抽成字段。比如一条 Nginx 访问日志:
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 化最大的顾虑是性能:海量日志上跑聚合,会不会把系统拖垮。炬鲸在这块做了三层。

  1. 时间分区:日志按小时/天分片存储,查询只扫命中的时间分片,WHERE 里的 ts 会自动下推成分区裁剪。
  2. 列式存储 + 压缩:字段按列存储,聚合只读需要的列,COUNT(*) 不会把整行内容解出来。
  3. 查询限流与超时:每条查询有执行预算,超时或超内存会被打断并提示,避免一条慢查询影响整个集群。

实测在 10 亿级日志量下,单表 GROUP BY 聚合查询的 P95 在 2 秒内;如果不加时间过滤直接全表扫,会被优化器拦截提示“请加时间范围”。

和告警、链路打通

SQL 检索不是孤立的功能,它和另外两块能力是连通的。

  • 检索结果可以“另存为告警”,把 SQL 的 WHERE 条件变成告警触发条件,阈值、静默、通知渠道沿用现有告警体系。
  • 检索命中带 trace_id 的日志,可以一键跳到链路追踪,看这条日志前后的完整调用链。

几条使用建议

  • 先建字段后写 SQL:字段没建好之前,SQL 只能对原始文本做 LIKE,又慢又没意义。接入时花半小时把关键字段抽出来,后面省事得多。
  • 能聚合就别拉明细:排查“错误分布”这类问题,优先用 GROUP BY 看聚合结果,而不是把几万条明细拉出来翻。
  • 时间范围是必填习惯:每次查询先圈时间窗口,既是性能要求,也能避免被历史脏数据误导。
  • 统一字段命名:level、service、cost_ms 这类字段名要全团队统一,否则不同服务写出来的字段五花八门,跨服务查询根本没法写。接入时定一份字段字典,按字典抽字段。

对一线同学来说,这套东西的核心价值很朴素:排查问题时,从“搜到日志”变成“问出答案”。