OpenClaw 排查数据库变慢:慢查询日志、执行计划到索引上线一条线

OpenClaw 排查数据库变慢:慢查询日志、执行计划到索引上线一条线

Agent 接上数据库的第三周,用户开始抱怨「有时候特别慢,要等七八秒」。你去看服务器,CPU 不到 20%,内存也够,磁盘 IO 平稳。查日志,工具调用全部成功,没有报错。这种「不报错但很慢」的问题最难查,因为所有人都在看错的地方。

先把分工说清:OpenClaw 怎么接数据库讲的是「怎么连上、权限怎么给」,OpenClaw 监控服务器讲的是「机器层面的 CPU、内存、磁盘、端口」,这两篇都不管 SQL 本身。本篇只管一件事——连上之后,某一条查询把整条链路拖住了,怎么找到它。

Agent 场景下这件事比人工查库更麻烦,原因有三个:SQL 是模型现生成的,写完就走,没人提前 review;调用频次高,一条慢查询一天可能被执行上万次;还有重试机制——超时重试一次,等于把慢查询跑了两遍。所以人工场景下「偶尔慢一下」的查询,在 Agent 这里会被放大成系统性变慢。

一、先分清三类慢,别一上来就加索引

「数据库慢」是个笼统的说法,实际至少分三类,解法完全不同。加错方向,不但没解决,还可能引入新问题。

查询本身慢。特征是单条 SQL 的执行时间就长,慢查询日志里 Query_time 和 Rows_examined 都很大。通常是没走索引、走了全表扫,或者排序、分组产生了临时表。

连接慢。特征是 SQL 执行时间不长,但从发起请求到拿到结果的总时长很长,而且库里看不到对应的慢查询。常见原因是每次调用都新建连接、连接池太小被打满、或者应用和数据库之间网络绕了远路。Agent 特别容易踩这个:很多 SDK 默认不用连接池。

等锁慢。特征是同一时刻一批请求一起变慢,日志里出现长时间等待。常见原因是长事务没提交、批量更新锁了大范围的行。这种情况加索引完全无效。

判断方法很简单:先看慢查询日志里有没有这条 SQL 的记录。有,就是第一类;没有,就往第二、第三类查。这一步花不了十分钟,能省掉后面几小时的瞎猜。

二、把慢查询日志打开:三个参数就够

MySQL 的慢查询日志默认是关的,很多线上库一直没开,出问题只能干瞪眼。要开的只有三个:

slow_query_log 打开,long_query_time 先设 1 秒。别一上来就设 0.1 秒,噪声会把你淹掉,根本看不出重点。等 Top 慢查询清完了,再降到 0.5 秒做精细优化。

log_queries_not_using_indexes 建议短期打开、看完就关。它会把所有没走索引的查询都记下来,包括那些扫小表的「无害」查询,长期开着日志会爆。

还有一个容易犯的错:为了排查问题打开 general_log(记录所有语句)。这个日志的写盘开销非常大,在高频 Agent 场景下可能直接把磁盘打满,进而让整个库变慢——等于为了查慢而制造更慢。用完立刻关。

另外,如果用的是云数据库,慢日志通常能在控制台直接看,还带聚合统计,比自己扒文件省事。

三、读执行计划:只看五个字段

拿到慢 SQL,下一步是看执行计划。EXPLAIN 的输出列很多,实际只用盯五个。

type:访问类型。看到 ALL 就是全表扫,基本可以确定问题在这。index 是全索引扫,也不好。range、ref、eq_ref、const 依次更好。

key:实际用了哪个索引。如果这里是 NULL,说明没走索引,或者优化器判断走索引更贵。后者要留意——有时候是统计信息过期导致的误判。

rows:优化器预估要扫多少行。这个数字是估算值,不是实际值,但估算离谱通常意味着统计信息不准,跑一次 ANALYZE TABLE 再看。

Extra:这里信息量最大。Using filesort 表示排序没走索引,要把结果拉到内存或磁盘再排;Using temporary 表示用了临时表,分组和去重常见;Using index 是好消息,说明用了覆盖索引,不回表。

filtered:过滤后剩余百分比。这个值很低配合 rows 很大,就是典型的「扫了很多行但只用了很少」。

MySQL 8 还有个更实用的命令:EXPLAIN ANALYZE。它会把 SQL 真跑一遍,给出实际的执行行数和耗时分布。排查「估算和实际差很远」的问题,这个比 EXPLAIN 有用得多。注意它真的会执行语句,别拿它去跑 UPDATE。

四、索引决策:四个判断,别拍脑袋

看到全表扫就加索引,是最常见的错误动作。加之前先过四个判断。

第一,区分度够不够。把「不重复值的数量 ÷ 总行数」算一下,低于 10% 的字段单独建索引意义不大。典型反例是「状态」字段——只有三五种值,扫全表可能比走索引还快,因为走索引要回表,回表是随机 IO。

第二,要不要建联合索引。多条件查询优先建联合索引,规则是最左前缀:把等值条件放前面,范围条件放后面。顺序错了,索引基本白建。比如查询是「按客户 + 按时间范围」,索引就该是 (客户, 时间),反过来 (时间, 客户) 在只给客户条件时用不上。

第三,这张表写多还是读多。索引不是免费的,每次写入都要维护。日志表、流水表这类写多读少的,索引要克制,宁可让偶发的报表查询慢一点,也别让主流程的写入被拖住。

第四,能不能做成覆盖索引。如果查询要的字段全在索引里,就不需要回表,性能会有数量级提升。代价是索引变大、写更慢。对 Agent 场景里那几个高频查询,这个取舍通常是划算的。

五、上线验证:三步一步都不能省

索引改动的风险不在「加不加」,在「怎么加」。

第一步,先在从库或影子库验证。同一条 SQL,改前改后各跑一次 EXPLAIN ANALYZE,对比实际扫描行数和耗时。别只看 EXPLAIN 的预估,预估和实际经常两回事。

第二步,加索引本身要控风险。InnoDB 支持在线加索引(不锁表),但大表上加索引仍会消耗大量 IO 和临时空间,可能把磁盘打满。规避办法:避开业务高峰、先看表大小和剩余磁盘、一次只加一个索引。

第三步,上线后要有对比数据。同一条 SQL 的 Rows_examined 降了多少、P95 耗时降了多少。没有这两个数,你无法判断这次改动到底有效,还是那段时间刚好流量低。

还有一个容易被忽略的收益:慢查询清掉之后,Agent 的调用成本会跟着降。因为很多 Agent 的实现是「查不到就重试」,慢查询超时触发的重试,烧的不只是时间,还有 token。

六、Agent 能做什么,不能碰什么

能做的四件事:汇总慢查询日志的 Top N(按总耗时排,不按单次耗时排——一条跑 0.2 秒但一天跑十万次的查询,比一条跑 5 秒一天跑三次的严重得多);解释执行计划,把 type、key、Extra 翻译成人话;给出索引建议并自动写好验证用的 SQL,人在从库上跑;把慢查询归到具体业务动作上,回答「是哪个工具调用在拖后腿」。

不能碰的三件事:不能自己执行 DDL——加索引、改表结构是变更,必须走审批;不能在生产高峰跑验证,哪怕只是 SELECT 也可能拖垮库;不能替 DBA 决定加不加,Agent 能给建议和依据,但选择性判断和容量评估需要人。

还有一条底线:Agent 拿到的数据库账号应该是只读 + 限表的。排查慢查询根本不需要写权限。权限给大了,模型写错一个语句就是生产事故,这类问题在工具调用失败排查里也提过——很多「失败」的根因不是工具坏了,是权限和边界没划清。

七、三个坑和落地三步

坑一:只看单条 SQL 的最慢,不看总耗时。优化了半天那条 5 秒的报表查询,结果真正拖慢系统的是那条 0.2 秒的高频查询。

坑二:把索引当万能药。等锁、连接不足、网络绕路,加索引一点用没有,还会让人误以为已经优化过了。

坑三:忽略数据质量。有时候查询慢是因为表里塞了大量重复和空值脏数据,扫的行数天然就多。这类问题该在入库前拦,属于数据质量门禁的事,不是索引能救的。

落地三步:第一天先把慢查询日志开起来,设 1 秒,跑一周攒底数;第一周按总耗时排出 Top 10,逐个看执行计划,判断哪些是索引问题、哪些不是;第二周开始改,一次只改一个,改完用压测的方式验证——不是单跑一次看快不快,是按真实并发压一轮看 P95 有没有降。

最后提醒一句:如果 Agent 的查询是模型现写的,那优化的终点不是「把已知的慢 SQL 改完」,而是「让模型少写慢 SQL」。做法有两个——把高频查询固化成参数化的视图或存储过程,让模型只填参数;以及在系统提示里明确告诉它表结构、索引和禁用的写法。前者是工程,后者是提示词,两条一起上才稳。

发表评论

您的电子邮箱地址不会被公开,必填项已标注 *