系统切到 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 root@todoitbo这套连接方式只解决“如何安全到达 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_status='PAID'ANDto_char(order_time,'YYYY-MM-DD')='2026-07-15'ORDERBYorder_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,参数设置为analyze=false。这里让 MCP 读取静态计划,不由它实际跑完查询:
调用 kingbase-mcp 的 explain_query,设置 analyze=false, 返回扫描节点、过滤条件、估算行数、排序节点和总估算成本。MCP 返回了Seq Scan、Sort和Gather Merge,总估算成本上限为4903.91。过滤条件中能够直接看到to_char(order_time, 'YYYY-MM-DD'),估算行数为 471。Codex 随后把各节点整理成表格,函数条件落在Filter、现有状态索引没有被采用,这两个关键点可以直接对照计划节点确认。
静态计划和实际计划承担的任务不同。analyze=false只调用优化器生成计划,不包含真实执行时间和实际行数;EXPLAIN ANALYZE会真正执行查询。本次把前者交给 restricted 模式下的 MCP,用于快速读取结构、过滤条件和成本,把后者留在 DBA 控制的ksql会话中。这样既能利用 Codex 对计划的归纳能力,也不会让一次自然语言分析请求不受控制地执行高成本 SQL。
此时 MCP 没有替数据库“做决定”。它把 SQL、表结构和优化器计划放到同一个上下文中,能快速回答几个具体问题:扫描发生在哪个节点,函数条件落在Filter还是Index Cond,预估行数是否异常,排序有没有被消除。DBA 仍然需要结合数据分布、业务峰值和变更风险判断下一步。
修改条件,同时补上匹配访问路径的索引
日期筛选改成左闭右开的时间范围:
order_time>=TIMESTAMP'2026-07-15 00:00:00'ANDorder_time<TIMESTAMP'2026-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(analyze=false)读取改写后 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 排查经常卡在信息传递上:开发人员提供 SQL,DBA 查询表结构和索引,双方再围绕计划节点来回确认。接入 MCP 后,Codex 可以在授权范围内直接读取 KES 元数据和静态计划,把原始输出整理成可核对的结论。对于条件改写、估算偏差和索引列顺序这类问题,定位速度确实会更快。
但连接更方便,也意味着权限设计要更保守。本次环境保留了几条明确限制:
- MCP 使用
restricted模式,HTTP 服务只监听服务器回环地址; - 数据库使用独立的
mcp_readonly,只授予目标 schema 的USAGE和指定对象的SELECT; - MCP 只读取静态计划,实际运行计划由 DBA 在受控终端执行;
CREATE INDEX、ANALYZE、上线和回滚均由人工完成;- 生产 SQL 在执行前仍需检查锁、资源消耗和业务窗口。
即使当前工具列表里没有UPDATE、DELETE或 DDL,也不能给 MCP 配置高权限业务账号。客户端能力会变化,工具会升级,SQL 校验也可能增加新的执行路径。KES 账号只保留必要的SELECT后,即使上层出现误调用,写操作仍会在数据库权限检查处被拒绝。图中未授权对象无法被发现,就是这道边界的直接结果。
最终落地的改动并不复杂:日期函数改成时间范围,增加一条与过滤条件匹配的联合索引。MCP 读取对象和计划,Codex 归纳扫描路径与过滤条件,DBA 选择并实施变更,实际计划和结果校验负责收口。
数据库运维会越来越多地使用 AI 辅助,但速度不应来自跳过验证。只读连接、静态分析、人工变更和结果复核同时保留,MCP 才适合进入长期运维流程,而不是停留在一次看起来很聪明的演示里。