生产环境里最怕遇到这么一种局面:线上数据半天刷不出来,首页一直转圈,后台接口一个接一个超时。大家第一反应是“数据库又被谁跑爆了”,冲到集群管理页面一看,CPU 已经到 95%,内存也快见底,可就是不知道哪条 SQL 在作怪。等你好不容易抓到现场,那句慢查询已经跑完,凶手趁你不注意悄悄退场。这种“事后想复盘,但连案发录像都没留下”的感觉,真的很不舒服。
StarRocks 本身自带不少监控能力,只是我们平时不太会专门用。很多时候,只需要用几段简单的 SQL,就能把“谁在跑、跑多久、为什么慢、怎么优化”这条链路串起来。下面这套实时查询监控思路,就是围绕这个目标展开的。
一、应用场景:这套方案到底在解决什么
最常见的场景有两个。
一个是大白天业务高峰期,用户开始反馈页面转圈、报表打不开、导出的数据迟迟生成不了。这时候我们最需要知道的是:现在哪个查询正在跑?它占了多少资源?是偶发还是持续?
另一个场景是凌晨跑批任务失败,第二天早上一看日志,发现有一句 SQL 跑了 20 分钟都没跑完,最后超时被干掉。可是当时没人盯着,失败现场已经没了。这种惨案发生一次两次,你就会明白,留下案底比什么都重要。
这套方案要解决的,就是两条事:第一,实时看见当下正在执行的查询;第二,把每次查询的关键信息留下来,事后能按时间、按用户、按数据库、按 SQL 文本进行复盘和统计。
二、动手前,先把两个“开关”打开
2.1 打开 Query Profile 开关
Query Profile 是 StarRocks 给查询生成的一份“体检报告”,里面会记录扫描了多少行、用了多少内存、每个算子耗时多少分钟。平时默认可能是关闭的,因为完整记录会有一些额外开销。但在排查慢查询时,这份报告非常救命。
我们可以在会话级别临时打开,只对当前连接生效,不影响到别人。
下面所有示例统一使用 StarRocks 自带的 SQL 技术栈。
-- 技术栈:StarRocks SQL
-- 打开当前会话的 Query Profile,执行完查询后会留下更细致的执行信息
SET enable_profile = true;
要注意,这个设置不需要全局打开。如果你在正常业务连接里跑,只是临时看看,用会话级别的就够了。
2.2 确认审计日志已经在默默工作
StarRocks 的审计日志表会记录每一次查询的元信息,比如查询 ID、谁发的、什么时候、跑了多久、扫描了多少数据、成功还是失败。这相当于一架装在机房里的摄像机。
用下面两句 SQL 先确认一下当前集群里有没有这个库和表:
-- 技术栈:StarRocks SQL
-- 先看集群里有没有 audit 相关的数据库
SHOW DATABASES LIKE 'starrocks%';
-- 再查看审计表结构,确认你版本里的列名
DESC starrocks_audit_db__.starrocks_audit_tbl__;
不同小版本的列名可能有细微差别,所以先跑一下 DESC,看一下实际列名。常用的列名基本就是这些:query_id、timestamp、user、db、query、query_time、scan_rows、scan_bytes、state。
三、实时查询监控的“三板斧”
3.1 第一板斧:看当下,谁正在跑
当有人喊“线上崩了”的时候,第一件事不是翻日志,而是直接看当前有哪些查询在执行。
-- 技术栈:StarRocks SQL
-- 实时查看当前所有查询会话,列名一般包括 Id、User、Host、Db、Command、Time、State、Info
SHOW PROCESSLIST;
执行结果里会有一条条正在跑的 SQL。最有用的两个字段,一个是 Time,代表已经跑了多少秒;另一个是 Info,代表 SQL 文本。看到 Time 特别大、Info 又是个很重的聚合查询,基本就能锁定嫌疑对象。
如果是一条大查询卡了十几分钟,你还可以把它的 query_id 记下来,后面去审计表里捞全文。
3.2 第二板斧:翻旧账,最近谁最慢
有时候我们发现问题时,慢查询已经跑完了。这时候可以去审计表里把刚才那 5 分钟、10 分钟甚至整天的慢查询都拉出来,看看到底是哪句话在拖后腿。
-- 技术栈:StarRocks SQL
-- 查看最近 5 分钟内执行完成的慢查询,query_time 的单位通常是毫秒
SELECT
query_id,
`timestamp` AS finish_time,
user AS login_user,
db AS db_name,
query_time AS slow_ms,
state,
query AS sql_text
FROM starrocks_audit_db__.starrocks_audit_tbl__
WHERE `timestamp` >= NOW() - INTERVAL 5 MINUTE
AND state = 'FINISHED'
ORDER BY query_time DESC
LIMIT 20;
这句 SQL 一看就懂:先筛最近 5 分钟,把状态是执行成功的查询按耗时倒序排,这样最慢的那几条就会跑到最前面。实际生产里,你可以把 INTERVAL 5 MINUTE 改成 1 HOUR 或 1 DAY,用于不同粒度的排查。
3.3 第三板斧:建家底,把监控数据沉淀到自己的表里
如果每次都是临时去翻审计表,效率太低。更好的办法是单独建一张精简的监控表,把审计日志里最关心的字段搬进来。这样后续的统计报表、告警规则、趋势分析,都在这张表上做。
-- 技术栈:StarRocks SQL
-- 创建一个专门放监控数据的库
CREATE DATABASE IF NOT EXISTS monitor;
-- 创建一张精简的查询监控表
CREATE TABLE IF NOT EXISTS monitor.query_monitor (
query_id VARCHAR(64) COMMENT '查询唯一ID',
sample_time DATETIME COMMENT '采样时间',
user_name VARCHAR(64) COMMENT '发起查询的用户',
db_name VARCHAR(64) COMMENT '查询所在的数据库',
sql_text VARCHAR(4096) COMMENT 'SQL 前 4096 个字符',
latency_ms BIGINT COMMENT '查询耗时,单位毫秒',
scan_rows BIGINT COMMENT '扫描行数',
scan_bytes BIGINT COMMENT '扫描字节数',
state VARCHAR(16) COMMENT '查询状态'
)
ENGINE=OLAP
PRIMARY KEY (query_id)
DISTRIBUTED BY HASH(query_id) BUCKETS 3
PROPERTIES ("replication_num" = "1");
有了这张表,我们可以定期把审计表里的新数据往这儿搬。比如每隔一分钟同步一次。
-- 技术栈:StarRocks SQL
-- 把过去一分钟内审计表里新增的查询,搬进监控表
INSERT INTO monitor.query_monitor
SELECT
query_id,
NOW() AS sample_time,
user AS user_name,
db AS db_name,
LEFT(query, 4096) AS sql_text,
query_time AS latency_ms,
scan_rows,
scan_bytes,
state
FROM starrocks_audit_db__.starrocks_audit_tbl__
WHERE `timestamp` >= NOW() - INTERVAL 1 MINUTE;
因为监控表的主键是 query_id,所以同一条查询就算被重复搬两次,也只会保留一条,不会越堆越乱。
数据沉淀下来之后,统计就方便多了。比如看看今天哪些类型的 SQL 失败次数最多:
-- 技术栈:StarRocks SQL
-- 看看今天哪些 SQL 反复出问题,按失败次数排序
SELECT
db_name,
LEFT(sql_text, 80) AS sql_prefix,
COUNT(*) AS fail_count
FROM monitor.query_monitor
WHERE state != 'FINISHED'
AND sample_time >= CURRENT_DATE
GROUP BY db_name, LEFT(sql_text, 80)
ORDER BY fail_count DESC
LIMIT 20;
这一步的重点在于,把临时排查变成长期积累。今天处理完一个慢查询,明天还会不会有新的?哪些 SQL 总是挑事?这些只有靠长期数据才能看出来。
四、拿到可疑查询后,怎样一步步定位根因
4.1 用 query_id 反查完整 SQL
在 SHOW PROCESSLIST 里面看到的 SQL 可能被截断,或者在审计表里我们没有看到完整全文。这时用 query_id 去审计表里捞,是最可靠的方式。
-- 技术栈:StarRocks SQL
-- 拿着 processlist 里看到的 query_id,去审计表里捞完整 SQL
SELECT query
FROM starrocks_audit_db__.starrocks_audit_tbl__
WHERE query_id = '具体的query_id字符串';
拿到完整 SQL 之后,先别急着改。看清楚它查了几张表,过滤条件是什么,Join 的字段有没有走主键。很多时候,问题并不是 SQL 写错了,而是执行计划选错路径。
4.2 用 EXPLAIN 看执行计划
EXPLAIN 是 StarRocks 里用来查看执行计划的命令。它不会真去跑查询,而是把执行计划打印出来,让你提前看到每条 SQL 会怎么执行。
假设你有一张订单表,字段是 order_id、user_id、total_amount、create_time,现在想查最近 30 天的订单。可以先这样看执行计划:
-- 技术栈:StarRocks SQL
-- 先看执行计划,EXPLAIN 不会真的执行 SQL
EXPLAIN
SELECT order_id, user_id, total_amount
FROM orders
WHERE create_time >= DATE_SUB(CURRENT_DATE, INTERVAL 30 DAY);
看执行计划的时候,重点关注几件事:扫描的数据量是不是特别大?过滤条件有没有落到分区或主键上?Join 的时候小表有没有被当成驱动表?如果扫描行数有几十亿,而过滤条件也没用上索引,那基本就能猜到为什么慢了。
4.3 再看 Query Profile,把耗时拆到算子级别
EXPLAIN 看到的是计划,实际执行时到底卡在哪一步,还是看不出来。这时候 Query Profile 就派上用场了。
你可以把 Query Profile 理解成一份给查询做的“全身体检报告单”。它会把查询拆成多个执行算子,然后逐个记录每个算子的耗时、处理行数、内存占用。哪个算子是瓶颈,一眼就能看出来。
复现慢查询的时候,把开关打开,再跑一遍 SQL:
-- 技术栈:StarRocks SQL
-- 在测试环境重新复现一次慢查询,先打开开关
SET enable_profile = true;
-- 重跑刚才的慢 SQL,让系统生成一份新的执行报告
SELECT * FROM orders WHERE create_time >= CURRENT_DATE;
跑完后,去审计数据库里找一下存放 profile 的对应表。不同版本存放位置略有差异,最稳妥的办法是看审计库下有哪些表:
-- 技术栈:StarRocks SQL
-- 查看审计数据库里有哪些表,找找带 profile 字样的表
SHOW TABLES FROM starrocks_audit_db__;
看 Profile 的时候不要从头看到尾,太浪费时间。先找耗时最高的那个算子,然后看它处理了多少行数据,内存消耗是不是异常,再顺着这个算子往上游看它的输入数据量。绝大多数慢查询,要么是扫描了不该扫描的数据,要么是 Join 时数据严重倾斜,要么是某个聚合算子在单节点上扛了太多数据。
五、这个方案的优点和缺点
5.1 优点
第一,足够轻。不需要额外安装监控 agent,也不需要对业务代码做任何改动,用 StarRocks 自带的能力就能搭起来。
第二,数据是自己的。你把监控数据存到自己的表里之后,想怎么统计都行,不受外部监控 SaaS 的限制。
第三,链路完整。从当前会话,到历史审计,再到执行计划,最后到 Query Profile,每一层都能通过 query_id 串联起来,排查起来很有方向感。
5.2 缺点
第一,只能看到 StarRocks 这一层。如果问题出在业务代码、网关、网络链路,或者上游数据源的读取上,这个方案是看不到的。
第二,高频查询审计表本身也会带来压力。如果你每隔几秒就去全表扫描审计日志,那等于是给生产环境又加了一道负担。
第三,不同 StarRocks 版本的审计字段和 profile 入口有差异,文档要常翻,脚本也要跟着版本调整。
六、生产环境落地的注意事项
第一,不要用 SELECT * 去拉整个审计表。审计表的数据量会非常大,尤其是大集群高并发的时候。一定要带上时间范围。
第二,给监控表设计保留策略。监控表如果无限增长,最后吃亏的还是我们自己。可以按天分区,定期清理超过 7 天或者 30 天的数据。
第三,生产环境不要把 enable_profile 全局打开。建议只对需要排查的查询开启,等定位完就赶紧关掉。
第四,权限要管好。监控表里存了整个集群的 SQL 文本,里面有业务字段信息,不能给所有人随便查。建议单独开一个只读账号给值班同学用。
第五,定时同步的 SQL 尽量错开高峰。比如在整点后 10 秒启动,不要在秒级频繁触发,避免和业务查询抢资源。
第六,监控表本身也要做数据保护。上面示例里 replication_num 写的是 1,只是方便演示。生产环境至少要设成 2,甚至 3,别让监控数据成为单点故障。
七、文章总结
查询性能异常这种事,光靠灵感和现场运气,真的太累了。与其等问题发生之后手忙脚乱,不如平时用 StarRocks 自带的审计日志和 Query Profile,给自己搭一套轻量级的“案发记录仪”。
先用 SHOW PROCESSLIST 看当下,再用审计表翻旧账,接着把关键数据攒到自己的监控表里,最后用 EXPLAIN 和 Query Profile 去挖根因。这条路走顺了,再遇到线上慢查询,你打开监控表,按时间排序,按失败次数统计,按 query_id 反查全文,整个排查过程就会变得非常丝滑。
希望这套用 SQL 就能搭建的监控思路,能帮你早一点抓住那个偷走数据库 CPU 的坏家伙。
评论
围绕“StarRocks实时查询监控方案:解决生产环境查询性能异常的排查难题”参与讨论