公司动态

从 MySQL 迁到 KingbaseES 之后:用 MCP 排查一条慢 SQL

📅 2026/8/6 13:16:55
从 MySQL 迁到 KingbaseES 之后:用 MCP 排查一条慢 SQL
系统切到 KingbaseES 已经有一段时间订单报表平时也一直正常。后来线上按日查询已支付订单的接口开始变慢排查最后落到了对应的 SQL 上。这条查询不长返回订单号、客户编码、下单时间和金额再按时间排序。没有表关联也没有子查询。顺着WHERE条件往下看order_time被to_char包了一层ANDto_char(order_time,YYYY-MM-DD)2026-07-15这段条件是从 MySQL 迁移后保留下来的。它不报错查询结果也正确因此一直没有引起注意。但对 KES 来说这不是一个可直接用于索引访问的时间范围扫描到的每行数据都要先执行一次to_char然后再和日期字符串比较。接下来要确认的是函数条件在执行计划中落在哪个节点现有索引为什么没有被使用以及改成时间范围后访问路径会怎么变。这次把 Kingbase-MCP 接入 Codex由它在只读权限下读取表结构和静态执行计划实际运行 SQL、创建索引、收集统计信息和核对结果仍在 DBA 的ksql会话中完成。先把线上问题压缩成可复现样本线上慢 SQL 不适合直接拿来反复试验。这里按原查询的字段、状态分布和日期条件建立了脱敏订单表app_schema.t_mcp_order_query数据库版本为 KingbaseESV009R001C010 / V9R1C10。验证表共有 20 万行时间范围从2026-06-01 00:00:00到2026-08-04 19:32:52。目标日期2026-07-15有 2469 条PAID订单。执行计划前后如果命中的不是同一批数据耗时差异就没有比较价值。MCP 服务与 KES 位于同一台服务器只监听http://127.0.0.1:8000/mcp访问模式为restricted。Mac 上的 Codex 通过 SSH 本地转发访问http://127.0.0.1:18000/mcp数据库端口不需要向客户端开放。ssh-N-L18000:127.0.0.1:8000 roottodoitbo这套连接方式只解决“如何安全到达 MCP”并不放宽数据库权限。MCP 使用独立账号mcp_readonly只读取明确授权的对象。第一次调用没有读到表新建验证表后Codex 第一次通过 MCP 查询information_schema.columns和information_schema.tables返回的都是空数组。站在管理员账号的视角表明明已经存在站在mcp_readonly的视角它却是不可见的。这不是 MCP 连接失败而是对象授权生效了。元数据查询同样受当前数据库身份约束账号没有访问权限时工具不能越过 KES 去读取对象结构。随后由管理员只补充验证表所需的最小权限GRANTUSAGEONSCHEMAapp_schemaTOmcp_readonly;GRANTSELECTONapp_schema.t_mcp_order_queryTOmcp_readonly;没有授予建表、修改数据或创建索引权限。授权完成后不需要重启 MCP后续连接按 KES 权限重新检查新表的结构和静态执行计划已经可以正常读取。这个小插曲反而把安全边界验证得很清楚MCP 能看到什么不由自然语言请求决定而由数据库账号的实际权限决定。原查询为什么走了顺序扫描先在ksql中执行实际计划保留运行时间、缓冲区命中和实际行数EXPLAIN(ANALYZE,BUFFERS)SELECTorder_id,customer_code,order_time,order_amountFROMapp_schema.t_mcp_order_queryWHEREorder_statusPAIDANDto_char(order_time,YYYY-MM-DD)2026-07-15ORDERBYorder_time,order_id;计划中的核心访问路径是Parallel Seq Scan。两个并行执行单元分别扫描并过滤数据随后执行Sort最后由Gather Merge合并有序结果。实际返回 2469 行共命中 1816 个共享缓冲块执行时间为190.812 ms。20 万行并不是一个很大的数据量但这个计划已经暴露了两个问题。首先to_char(order_time, ...)把时间列包在函数中条件无法直接形成order_time的起止范围其次当时现有的idx_mcp_order_status只包含order_status而PAID约占数据的八成只靠状态字段筛选的选择性很低。优化器最终认为并行顺序扫描比读取大量索引项再回表更合适。实际行数和估算行数也有明显偏差。计划估算每个并行分支返回 471 行最终汇总得到 2469 行。日期被包装在函数表达式中后优化器很难直接利用时间列统计信息估算该自然日的分布。估算偏差不一定单独造成慢 SQL但会影响扫描方式、并行和排序等后续选择。MCP把执行计划翻译成可讨论的证据同一条 SQL 原样交给 Kingbase-MCP 的explain_query参数设置为analyzefalse。这里让 MCP 读取静态计划不由它实际跑完查询调用 kingbase-mcp 的 explain_query设置 analyzefalse 返回扫描节点、过滤条件、估算行数、排序节点和总估算成本。MCP 返回了Seq Scan、Sort和Gather Merge总估算成本上限为4903.91。过滤条件中能够直接看到to_char(order_time, YYYY-MM-DD)估算行数为 471。Codex 随后把各节点整理成表格函数条件落在Filter、现有状态索引没有被采用这两个关键点可以直接对照计划节点确认。静态计划和实际计划承担的任务不同。analyzefalse只调用优化器生成计划不包含真实执行时间和实际行数EXPLAIN ANALYZE会真正执行查询。本次把前者交给 restricted 模式下的 MCP用于快速读取结构、过滤条件和成本把后者留在 DBA 控制的ksql会话中。这样既能利用 Codex 对计划的归纳能力也不会让一次自然语言分析请求不受控制地执行高成本 SQL。此时 MCP 没有替数据库“做决定”。它把 SQL、表结构和优化器计划放到同一个上下文中能快速回答几个具体问题扫描发生在哪个节点函数条件落在Filter还是Index Cond预估行数是否异常排序有没有被消除。DBA 仍然需要结合数据分布、业务峰值和变更风险判断下一步。修改条件同时补上匹配访问路径的索引日期筛选改成左闭右开的时间范围order_timeTIMESTAMP2026-07-15 00:00:00ANDorder_timeTIMESTAMP2026-07-16 00:00:00不使用BETWEEN 2026-07-15 00:00:00 AND 2026-07-15 23:59:59是因为时间精度可能包含小数秒。左闭右开范围既覆盖当天全部记录也不会误带第二天零点的数据。查询同时包含order_status等值条件和order_time范围条件因此在验证环境中建立(order_status, order_time)联合索引并重新收集表统计信息CREATEINDEXidx_mcp_order_status_timeONapp_schema.t_mcp_order_query(order_status,order_time);ANALYZEapp_schema.t_mcp_order_query;索引创建和ANALYZE都由管理员在 MCP 之外执行。重新运行实际计划后访问路径变为Bitmap Index Scan加Bitmap Heap Scan状态条件和两个时间边界全部进入Index Cond。索引先定位 2469 条候选记录堆扫描只访问 28 个精确数据块最终执行时间为3.920 ms。排序节点仍然存在。位图扫描不会保留 B-tree 的索引顺序而结果还要求按order_time, order_id排序所以优化器使用了内存中的quicksort占用 289kB。这个排序只处理 2469 行实际耗时很短没有必要为了消掉它立刻继续扩大索引。若盲目把返回列和排序列都塞进索引会增加存储、写放大和后续维护成本。(order_status, order_time)也不是可以套用到所有订单查询的固定答案。它适合当前“状态等值、时间范围”的访问方式。生产实施前还要检查同表其他高频 SQL、索引重复度、磁盘空间、创建索引时的锁影响和回滚方案。一次样本计划只能证明这条查询在当前数据分布下选中了该索引。性能变快以后先核对业务结果SQL 优化最危险的情况不是“没有变快”而是“很快地返回了错误结果”。因此没有直接拿两次耗时宣布结束而是分别对原始条件和时间范围条件计算行数、金额合计、首条时间和末条时间。两条 SQL 都返回 2469 行金额合计均为3637216.59首条订单时间为2026-07-15 00:00:16末条为2026-07-15 23:59:28。四项结果完全一致说明改写没有改变当天已支付订单的业务范围。只比较count(*)仍然有漏洞行数相同并不代表一定是同一批记录。金额合计和首尾时间提供了额外校验。正式上线时还可以用主键集合差集做更严格的验证确认两条 SQL 不存在“数量相同、记录不同”的情况。再让 MCP 复核一次而不是凭耗时下结论索引创建完成后Codex再次通过explain_query(analyzefalse)读取改写后 SQL 的静态计划并调用对象详情核对实际索引。计划从顺序扫描变为Bitmap Index Scan Bitmap Heap Scan三个筛选条件进入索引访问范围Gather Merge消失Sort保留。静态计划的总成本上限从4903.91降至2114.04下降约56.9%。估算返回行数则从 471 变为 2356更接近实际的 2469 行。这不是性能变差而是时间范围条件让优化器能够使用order_time的列统计信息基数估算更接近真实分布。成本值是优化器内部用于比较候选计划的相对量不等于毫秒。实际执行时间应以ksql的EXPLAIN ANALYZE为准这组 20 万行脱敏数据中单次执行从190.812 ms降到3.920 ms。这个数字可以说明本次修改有效但不能直接外推到生产环境缓存状态、并发、硬件、数据倾斜和参数配置都会影响绝对耗时。MCP 的价值在复核阶段表现得更明显。它没有只回答“已经命中索引”而是继续检查索引名称、列顺序、过滤条件所在节点、估算行数和遗留排序。对运维人员而言这比一句笼统的“建议建立索引”更有用因为每个判断都能回到 KES 返回的计划节点。AI 可以加快排查但不能继承 DBA 权限传统慢 SQL 排查经常卡在信息传递上开发人员提供 SQLDBA 查询表结构和索引双方再围绕计划节点来回确认。接入 MCP 后Codex 可以在授权范围内直接读取 KES 元数据和静态计划把原始输出整理成可核对的结论。对于条件改写、估算偏差和索引列顺序这类问题定位速度确实会更快。但连接更方便也意味着权限设计要更保守。本次环境保留了几条明确限制MCP 使用restricted模式HTTP 服务只监听服务器回环地址数据库使用独立的mcp_readonly只授予目标 schema 的USAGE和指定对象的SELECTMCP 只读取静态计划实际运行计划由 DBA 在受控终端执行CREATE INDEX、ANALYZE、上线和回滚均由人工完成生产 SQL 在执行前仍需检查锁、资源消耗和业务窗口。即使当前工具列表里没有UPDATE、DELETE或 DDL也不能给 MCP 配置高权限业务账号。客户端能力会变化工具会升级SQL 校验也可能增加新的执行路径。KES 账号只保留必要的SELECT后即使上层出现误调用写操作仍会在数据库权限检查处被拒绝。图中未授权对象无法被发现就是这道边界的直接结果。最终落地的改动并不复杂日期函数改成时间范围增加一条与过滤条件匹配的联合索引。MCP 读取对象和计划Codex 归纳扫描路径与过滤条件DBA 选择并实施变更实际计划和结果校验负责收口。数据库运维会越来越多地使用 AI 辅助但速度不应来自跳过验证。只读连接、静态分析、人工变更和结果复核同时保留MCP 才适合进入长期运维流程而不是停留在一次看起来很聪明的演示里。