**摘要:**本文系统梳理 ClickHouse 查询审计日志表 system.query_log 的字段含义与排障实践。文章先介绍表引擎定义与按月分区、TTL 等关键特性,再按事件类型、性能指标、查询内容、涉及对象、异常信息、分布式链路、客户端信息、配置画像等维度逐组详解字段,并给出排障场景速查与常用巡检 SQL,最后说明副本去重、统计口径、落盘延迟与分区裁剪等使用注意,帮助读者快速定位慢查询、负载热点与来源。
一、概述
system.query_log 是 ClickHouse 的查询审计日志表:每条查询在开始、结束时由后台线程异步写入(需 log_queries = 1,默认开启)。它是慢查询分析、负载分析、来源定位的核心数据源。
用户查询
│
▼
┌─────────────────────────────────────────────────┐
│ query_log 写入时机(一条查询可产生多条记录) │
│ ① QueryStart 开始时写入 │
│ ② QueryFinish 成功结束时写入 ← 统计用这条 │
│ ③ ExceptionBeforeStart 解析/规划期失败 │
│ ④ ExceptionWhileProcessing 执行中失败/被 kill │
└─────────────────────────────────────────────────┘
⚠️ 统计耗时/扫描量必须加 type = 'QueryFinish',否则同一条查询会被重复计算。
二、表引擎定义
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_time)
TTL event_date + toIntervalDay(30)
-
按月分区:查询必须带 event_date 条件做分区裁剪(与 event_time 双条件配合:event_date 管分区裁剪,event_time 管精确窗口)
-
主键排序(event_date, event_time):日期 + 时间范围查询局部性好
-
TTL 30 天:日志只保留一个月,长期趋势需提前导出
三、字段分组详解
3.1 事件类型与时间(何时)
| 字段 | 含义 |
|---|---|
| type | 事件类型,见上方示意图 |
| event_date | 记录写入日期,分区键 |
| event_time / event_time_microseconds | 事件发生时刻(微秒版精度更高),巡检常用 |
| query_start_time / ..._microseconds | 查询开始执行时刻,query_start_time + query_duration_ms ≈ event_time |
| initial_query_start_time / ..._microseconds | 分布式链路最初始查询的开始时刻 |
3.2 性能指标(多贵)------ 排障核心
| 字段 | 含义 |
|---|---|
| query_duration_ms | 查询耗时(毫秒) |
| read_rows / read_bytes | 扫描的行数/字节数,判断"是否扫多了"的核心指标 |
| result_rows / result_bytes | 返回结果行数/字节数,read/result 比值即读放大 |
| written_rows / written_bytes | 写入行数/字节数(INSERT/CREATE 等) |
| memory_usage | 峰值内存占用(字节),配合 max_memory_usage 排查 OOM |
3.3 查询内容(是什么)
| 字段 | 含义 |
|---|---|
| query | SQL 原文 |
| normalized_query_hash | 归一化哈希(字面值替换为占位符后计算),WHERE id=1 与 WHERE id=2 hash 相同,用于按查询模式聚合 |
| query_kind | 查询类型:Select/Insert/Create/Alter... |
| current_database | 执行时默认数据库 |
| log_comment | 通过设置项写入的备注,可携带 trace_id 做链路追踪 |
| query_cache_usage | 查询缓存:Unknown/None/Write(写入)/Read(命中) |
3.4 涉及对象(动了哪些表)
databases / tables / columns / partitions / projections / views:查询涉及的库、表、列、分区、投影、视图数组。
3.5 异常信息(怎么死的)
| 字段 | 含义 |
|---|---|
| exception_code | 错误码(如 241 = MEMORY_LIMIT_EXCEEDED) |
| exception | 错误消息文本 |
| stack_trace | 异常堆栈 |
3.6 分布式链路(谁发起、谁执行)
| 字段 | 含义 |
|---|---|
| is_initial_query | 1 = 用户直接发起的初始查询;0 = 分布式查询拆到各节点执行的分片子查询 |
| query_id / initial_query_id | 本条记录 ID / 链路根查询 ID。子查询的 initial_query_id 都指向根查询,按它聚合/去重可归并多节点记录 |
| user / initial_user | 本节点执行用户 / 初始发起用户 |
| address / port | 发起本查询的客户端(上级节点)地址 |
| initial_address / initial_port | 最初始客户端地址,定位"谁在打集群" |
| distributed_depth | 分布式转发深度:初始为 0,每转发一层 +1 |
3.7 客户端信息(从哪来)
| 字段 | 含义 |
|---|---|
| interface | 1=TCP,2=HTTP |
| is_secure | 是否 TLS |
| os_user | 客户端机器操作系统用户名 |
| client_hostname / client_name / client_revision / client_version_* | 客户端主机名、驱动名及版本 |
| http_method | 0=非HTTP,1=POST,2=GET |
| http_user_agent / http_referer / forwarded_for | HTTP 的 UA/Referer/转发链,经网关转发的查询靠它定位真实来源 |
3.8 配置与画像(怎么跑的)
| 字段 | 含义 |
|---|---|
| Settings | 本查询生效设置项 Map(含 session 覆盖) |
| ProfileEvents | 细粒度计数器 Map(SelectedBytes、FileOpen、DiskReadElapsedMicroseconds 等),深挖 IO/缓存用 |
| quota_key | 配额归组键 |
| thread_ids | 参与执行的线程 ID |
| transaction_id | 事务 ID(非事务查询为 0) |
3.9 used_* 系列(查询画像)
used_aggregate_functions / used_aggregate_function_combinators / used_database_engines / used_data_type_families / used_dictionaries / used_formats / used_functions / used_storages / used_table_functions / used_row_policies:查询用到的聚合函数、函数、存储引擎、表函数、字典等清单,主要用于审计与生态分析。
四、排障场景速查
谁在打集群? initial_user, initial_address, http_user_agent, os_user
哪条 SQL 慢? query, query_duration_ms, read_rows, read_bytes
为什么慢? read_rows vs result_rows(读放大), ProfileEvents, partitions
哪张表遭殃? tables, partitions, databases
失败的查询? type != 'QueryFinish' → exception, exception_code
去重统计次数? initial_query_id, normalized_query_hash
同类查询频率? normalized_query_hash GROUP BY count()
某次请求全链路? log_comment 或 initial_query_id
CPU 尖刺/负载热点归因路径:
CPU 尖刺时间点 t
├─ 对齐慢查询分钟桶 → t 处 cnt/total_sec 有尖峰?
│ ├─ 是 → 查该分钟 query_log 明细(按 read_rows 排序)→ 定位 SQL 和发起方
│ └─ 否 → system.merges / system.mutations → 后台任务嫌疑
└─ 都否 → 主机层面(其他进程/宿主干扰)
五、常用巡检 SQL
5.1 上一个整点小时的慢查询明细(>10s)
SELECT
hostName() AS node,
event_time,
query_kind,
query_duration_ms,
read_rows,
formatReadableSize(read_bytes) AS io,
substring(query, 1, 1000) AS sql
FROM clusterAllReplicas('default_cluster', system.query_log)
WHERE event_time >= toStartOfHour(now()) - INTERVAL 1 HOUR
AND event_time < toStartOfHour(now())
AND type = 'QueryFinish'
AND query_duration_ms > 10000
ORDER BY query_duration_ms DESC
LIMIT 2000;
5.2 最近 24 小时负载最重的 30 个分钟(含全部查询)
SELECT toStartOfMinute(event_time) AS minute,
count() AS queries,
round(sum(query_duration_ms)/1000, 1) AS total_query_sec,
round(max(query_duration_ms)/1000, 1) AS max_query_sec,
formatReadableQuantity(sum(read_rows)) AS read_rows
FROM clusterAllReplicas('default_cluster', system.query_log)
WHERE event_date >= today() - 1
AND event_time >= now() - INTERVAL 24 HOUR
AND type = 'QueryFinish'
GROUP BY minute
ORDER BY total_query_sec DESC
LIMIT 30;
结果解读:
-
total_query_sec 高 + queries 少 + max 高 → 个别重型查询拖累
-
total_query_sec 高 + queries 多 + max 低 → 小查询风暴
-
top 分钟集中在固定时刻 → 定时任务扎堆,需错峰
5.3 定位热点时刻的元凶明细
SELECT hostName() AS node, event_time, user, initial_user,
query_id, initial_query_id, query_duration_ms, read_rows,
formatReadableSize(read_bytes) AS io,
substring(query, 1, 500) AS sql
FROM clusterAllReplicas('default_cluster', system.query_log)
WHERE event_date = toDate('尖刺日期')
AND event_time >= '尖刺时间-2分钟' AND event_time <= '尖刺时间+1分钟'
AND type = 'QueryFinish'
ORDER BY read_rows DESC
LIMIT 100;
六、使用注意
-
副本重复计数:clusterAllReplicas 会把同一条分布式查询在各节点的执行记录都计入。看明细是特性;做条数/耗时统计需按 initial_query_id 去重或改用 cluster()。
-
只统计正常结束:QueryFinish 排除被 kill/报错的查询;要含失败的用 type IN ('QueryFinish','ExceptionWhileProcessing') 并补 exception 列。
-
落盘延迟:query_log 异步 flush(默认数秒级),刚结束的查询可能查不到;定时巡检放整点后 1~2 分钟执行更稳。
-
分区裁剪:务必带 event_date 条件(如 event_date >= today() - 1),否则全表扫描。
-
大表 COUNT(*) 是慢查询常见根因:非精确场景可用 system.parts 元数据计数(毫秒级):
SELECT table, sum(rows) AS total_rows, formatReadableSize(sum(bytes_on_disk)) AS size
FROM clusterAllReplicas('default_cluster', system.parts)
WHERE active AND database = 'xxx' AND table LIKE 'xxx%'
GROUP BY table;