备份恢复 & 迁移30 分钟阅读
OceanBase SQL 性能与执行计划排查 100 条命令
OceanBase 的一条 SQL 可能经 ODP 转发,落在不同 OBServer;同一 SQL_ID 也可能在不同节点缓存不同计划。排慢 SQL 时,先保留租户、请求时间、执行节点和 trace_id,再分清是排队、执行、网络还是计划本身慢。只拿一次 `EXPLAIN` 去解释线上所有慢请求,容易跑偏。
2026年9月16日阅读—点赞—收藏—
dba100oceanbasescenario
100 条命令系列文章专栏
OceanBase 的一条 SQL 可能经 ODP 转发,落在不同 OBServer;同一 SQL_ID 也可能在不同节点缓存不同计划。排慢 SQL 时,先保留租户、请求时间、执行节点和 trace_id,再分清是排队、执行、网络还是计划本身慢。只拿一次 `EXPLAIN` 去解释线上所有慢请求,容易跑偏。
OceanBase 的一条 SQL 可能经 ODP 转发,落在不同 OBServer;同一 SQL_ID 也可能在不同节点缓存不同计划。排慢 SQL 时,先保留租户、请求时间、执行节点和 trace_id,再分清是排队、执行、网络还是计划本身慢。只拿一次 EXPLAIN 去解释线上所有慢请求,容易跑偏。
这篇按“慢请求 → SQL_ID → 节点上的缓存计划 → 计划算子”往下查。前面先给出十条取证命令,后续补统计信息、索引、资源与改动后的回归验证。适合先收藏,问题发生时从真实请求开始查。
示例基于 OceanBase 数据库分布式版 V4.3.0、MySQL 模式。SQL Audit 由 root@sys 查询并限定目标租户;计划缓存与执行计划在对应租户或有权限的系统租户查看。审计视图存在保留窗口,出现问题应及时记录 trace_id 和原始请求。
OceanBase SQL 请求经 ODP 落在不同 OBServer、各节点缓存执行计划的示意
图中 ODP 只负责入口路由;真正的 SQL 执行、等待和 Plan Cache 在 OBServer 上。SQL Audit 记录请求实际落点,排障时据此回查该节点当时的 PLAN_ID。
1-- root@sys;租户 ID 按现场替换,耗时单位为微秒2SELECT SVR_IP, SVR_PORT, SQL_ID, TRACE_ID,3 ELAPSED_TIME, QUERY_SQL4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND IS_EXECUTOR_RPC = 07 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 10000008ORDER BY ELAPSED_TIME DESC9LIMIT 20;先保存 TRACE_ID、发生时间和执行节点。ELAPSED_TIME 高不等于执行计划必然有问题;之后要拆出排队、执行和资源消耗。同一条业务请求可能包含多个内部 RPC,本条先过滤 executor RPC,便于抓客户端 SQL。
1-- root@sys;TRACE_ID 使用第 1 条实际结果2SELECT TRACE_ID, SID, CLIENT_IP, USER_CLIENT_IP,3 USER_NAME, DB_NAME, SVR_IP, SVR_PORT4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';经 ODP 转发时 CLIENT_IP 可能是 ODP 地址,不能直接写成最终业务客户端;USER_CLIENT_IP 和应用连接记录要一起看。把用户、数据库、SID 和节点对上,后面才好找会话及同一 SQL 的其他执行。
1-- root@sys2SELECT SQL_ID, TRACE_ID, ELAPSED_TIME,3 QUEUE_TIME, EXECUTE_TIME, SVR_IP4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';三列均按微秒读。排队占大头时先看租户资源和节点压力;执行占大头时才进一步查计划、算子与数据分布。不能仅用 ELAPSED_TIME - EXECUTE_TIME 断言全是网络耗时。
1-- root@sys;SQL_ID 使用实际请求值2SELECT SQL_ID, COUNT(*) AS executions,3 MIN(ELAPSED_TIME) AS min_usec,4 AVG(ELAPSED_TIME) AS avg_usec,5 MAX(ELAPSED_TIME) AS max_usec6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'9 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 100000010GROUP BY SQL_ID;平均值和最大值差很大时,要按节点、参数或执行计划继续分组。Audit 只保留当前可见的请求记录,结果不能当作全天调用次数;同时确认应用是否在同一时间改变了并发和返回行数。
1-- root@sys2SELECT SVR_IP, SVR_PORT, COUNT(*) AS executions,3 AVG(ELAPSED_TIME) AS avg_usec,4 MAX(ELAPSED_TIME) AS max_usec5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'8 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 10000009GROUP BY SVR_IP, SVR_PORT10ORDER BY avg_usec DESC;仅某个节点慢,优先查该节点的 CPU、I/O、会话排队以及它缓存的计划;所有节点都慢,再看 SQL 逻辑、数据变化和租户整体资源。低调用量节点的平均数容易受一次异常请求影响,别只凭排序决定迁移业务。
1-- root@sys2SELECT SQL_ID, PLAN_ID, PLAN_TYPE,3 SVR_IP, SVR_PORT, TRACE_ID4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';后续缓存计划查询必须带上节点地址、租户 ID 和 PLAN_ID。同一个 SQL_ID 的不同执行可能命中不同计划;不能拿今天重新 EXPLAIN 的结果代替当时 Audit 记录的计划。
1-- 目标租户管理员或有权限的 root@sys2SELECT TENANT_ID, SVR_IP, SVR_PORT,3 SQL_ID, PLAN_ID, EXECUTIONS,4 AVG_EXE_TIME, CPU_TIME5FROM oceanbase.GV$OB_PLAN_CACHE_PLAN_STAT6WHERE TENANT_ID = 10027 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'8ORDER BY SVR_IP, PLAN_ID;先看同一 SQL_ID 是否在不同节点有不同 PLAN_ID。缓存统计属于仍在计划缓存中的对象,计划被淘汰后不再完整;它也可能包含 PL 对象,所以要以 SQL_ID 和对象类型核对,不要把空 SQL_ID 当成业务 SQL。
1-- 节点与计划 ID 均来自第 6/7 条2SELECT TENANT_ID, SVR_IP, SVR_PORT,3 PLAN_ID, QUERY_SQL, EXECUTIONS, AVG_EXE_TIME4FROM oceanbase.GV$OB_PLAN_CACHE_PLAN_STAT5WHERE TENANT_ID = 10026 AND SVR_IP = '10.0.0.11'7 AND SVR_PORT = 28828 AND PLAN_ID = 248;同一个 PLAN_ID 不能脱离节点和租户使用。结果为空时,计划可能已被淘汰或节点变化;先留住 Audit 的 SQL 文本和 trace_id,再取执行日志,别把空结果写成“没有计划”。
1-- 该视图的 GET 查询需限定节点、租户和计划四元组2SELECT PLAN_LINE_ID, PLAN_DEPTH, OPERATOR,3 NAME, ROWS, COST, PROPERTY4FROM oceanbase.GV$OB_PLAN_CACHE_PLAN_EXPLAIN5WHERE TENANT_ID = 10026 AND SVR_IP = '10.0.0.11'7 AND SVR_PORT = 28828 AND PLAN_ID = 2489ORDER BY PLAN_LINE_ID;先找 TABLE SCAN、JOIN、SORT 与数据交换算子,再对照返回行数和当时的慢请求。这里展示的是缓存计划估算与属性,ROWS 不是线上实际每个算子的输出行数;真实执行仍要结合 SQL Plan Monitor 或 trace 分析。
1-- 目标租户的测试库;示例表名与谓词按实际 SQL 替换2EXPLAIN SELECT order_id, order_status, update_time3FROM orders4WHERE customer_id = 1001;EXPLAIN 适合查看当前优化器如何选择路径,不能单独证明线上那次慢请求当时使用相同计划。把它和第 6—9 条的节点缓存计划比较;若不一致,再查参数、统计信息、计划缓存和索引变化。
1-- root@sys;TRACE_ID 按现场替换,单位均为微秒2SELECT TRACE_ID, NET_TIME, NET_WAIT_TIME,3 QUEUE_TIME, DECODE_TIME, GET_PLAN_TIME,4 EXECUTE_TIME, ELAPSED_TIME5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';先看哪个阶段占大头。取计划慢、排队慢和执行慢是三类问题,不能因为总耗时高就直接加索引。
1-- root@sys2SELECT TRACE_ID,3 APPLICATION_WAIT_TIME,4 CONCURRENCY_WAIT_TIME,5 USER_IO_WAIT_TIME,6 SCHEDULE_TIME7FROM oceanbase.GV$OB_SQL_AUDIT8WHERE TENANT_ID = 10029 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';这些是请求级累计等待。I/O 高先看扫描量和磁盘,Concurrency 高再查锁与并发,Schedule 高则要结合租户资源和节点调度。
1-- root@sys2SELECT SQL_ID, TRACE_ID,3 ROW_CACHE_HIT, BLOCK_CACHE_HIT,4 DISK_READS, RETURN_ROWS, AFFECTED_ROWS5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';返回很少但物理读很高,通常值得继续检查访问路径和过滤条件。缓存命中数不是命中率,不能脱离总扫描量单独评价。
1-- root@sys2SELECT SQL_ID, TRACE_ID, TABLE_SCAN,3 PARTITION_CNT, RETURN_ROWS, DISK_READS,4 QUERY_SQL5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';TABLE_SCAN=1 只说明计划中出现全表扫描,不代表扫描一定错误。小表、分析查询和分区裁剪后的扫描都可能合理,要结合行数和计划算子判断。
1-- root@sys2SELECT SQL_ID, TRACE_ID, IS_HIT_PLAN,3 PLAN_ID, GET_PLAN_TIME, ELAPSED_TIME4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';未命中缓存时,取计划时间可能抬升。偶发一次 miss 不必直接处理;持续 miss 要看 SQL 是否未参数化、计划是否频繁淘汰或 Schema 是否不断变化。
1-- root@sys2SELECT IS_HIT_PLAN, COUNT(*) AS EXECUTIONS,3 AVG(GET_PLAN_TIME) AS AVG_GET_PLAN_USEC,4 AVG(ELAPSED_TIME) AS AVG_ELAPSED_USEC5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'8 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 10000009GROUP BY IS_HIT_PLAN;命中与未命中分开比较,才能判断解析和优化成本是否重要。样本很少时先扩大观察窗口,不要用一两次执行下结论。
1-- root@sys2SELECT SQL_ID, TRACE_ID, RETRY_CNT,3 RET_CODE, ELAPSED_TIME, QUERY_SQL4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';重试会拉长总耗时,也可能掩盖一次短暂的路由或并发问题。结合错误码、发生节点和同一时间的系统事件查原因。
1-- root@sys2SELECT RET_CODE, COUNT(*) AS OCCURRENCES,3 MAX(ELAPSED_TIME) AS MAX_ELAPSED_USEC4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND IS_EXECUTOR_RPC = 07 AND RET_CODE <> 08 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 10000009GROUP BY RET_CODE10ORDER BY OCCURRENCES DESC;先找高频错误,再回到具体 trace_id。超时、锁冲突和语法错误的处理方向完全不同,不能把所有失败都归到“数据库慢”。
1-- root@sys2SELECT USEC_TO_TIME(REQUEST_TIME) AS REQUEST_AT,3 SQL_ID, TRACE_ID, RETURN_ROWS,4 ELAPSED_TIME, SVR_IP, QUERY_SQL5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND IS_EXECUTOR_RPC = 08 AND RETURN_ROWS > 1000009 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 100000010ORDER BY RETURN_ROWS DESC11LIMIT 20;大结果集可能让执行、序列化和网络一起变慢。先确认业务是否真的需要全部行,再谈索引和并行度。
1-- root@sys2SELECT USEC_TO_TIME(REQUEST_TIME) AS REQUEST_AT,3 SQL_ID, TRACE_ID, DISK_READS,4 RETURN_ROWS, ELAPSED_TIME, QUERY_SQL5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND IS_EXECUTOR_RPC = 08 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 10000009ORDER BY DISK_READS DESC10LIMIT 20;物理读高的 SQL 是 I/O 排查入口,不等于一定缺索引。还要看扫描范围、缓存冷暖、分区裁剪和同一 SQL 的历史基线。
1-- root@sys2SELECT USEC_TO_TIME(REQUEST_TIME) AS REQUEST_AT,3 TRACE_ID, PLAN_ID, SVR_IP,4 ELAPSED_TIME, EXECUTE_TIME,5 RETURN_ROWS, DISK_READS, RET_CODE6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'9ORDER BY REQUEST_TIME DESC10LIMIT 50;逐次明细能看出计划切换、节点差异和返回行数突增。平均值正常时,尾部慢请求仍可能严重影响用户体验。
1-- root@sys2SELECT PLAN_ID, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 MAX(ELAPSED_TIME) AS MAX_USEC,5 AVG(RETURN_ROWS) AS AVG_RETURN_ROWS6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'9 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 60 * 60 * 100000010GROUP BY PLAN_ID11ORDER BY AVG_USEC DESC;不同计划的输入参数和返回行数也可能不同。计划耗时差异明显时,先排除数据量差异,再判断是否属于计划退化。
1-- root@sys2SELECT DATE_FORMAT(USEC_TO_TIME(REQUEST_TIME), '%Y-%m-%d %H:00:00')3 AS HOUR_SLOT,4 COUNT(*) AS EXECUTIONS,5 AVG(ELAPSED_TIME) AS AVG_USEC,6 MAX(ELAPSED_TIME) AS MAX_USEC7FROM oceanbase.GV$OB_SQL_AUDIT8WHERE TENANT_ID = 10029 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'10GROUP BY HOUR_SLOT11ORDER BY HOUR_SLOT;Audit 受内存容量和保留窗口限制,缺少早期小时不一定代表没有执行。趋势要同时记录采集窗口和样本数。
1-- root@sys2SELECT CASE WHEN RET_CODE = 0 THEN 'SUCCESS' ELSE 'FAILED' END3 AS EXEC_RESULT,4 COUNT(*) AS EXECUTIONS,5 AVG(ELAPSED_TIME) AS AVG_USEC,6 MAX(ELAPSED_TIME) AS MAX_USEC7FROM oceanbase.GV$OB_SQL_AUDIT8WHERE TENANT_ID = 10029 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'10GROUP BY EXEC_RESULT;失败请求可能在超时后才返回,平均耗时会明显高于成功样本。先把错误路径和正常执行分开,避免污染计划性能判断。
1-- root@sys;FORMAT_SQL_ID 由实际慢请求取得2SELECT FORMAT_SQL_ID, SQL_ID,3 COUNT(*) AS EXECUTIONS,4 AVG(ELAPSED_TIME) AS AVG_USEC5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND FORMAT_SQL_ID = 'ABCDEF0123456789ABCDEF0123456789'8GROUP BY FORMAT_SQL_ID, SQL_ID9ORDER BY EXECUTIONS DESC;大量字面量 SQL 可能形成多个 SQL_ID,增加解析和缓存压力。确认业务语义一致后,再推动应用使用绑定变量。
1-- root@sys2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 SUM(ELAPSED_TIME) AS TOTAL_USEC,5 MIN(QUERY_SQL) AS SQL_SAMPLE6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND IS_EXECUTOR_RPC = 09 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000010GROUP BY SQL_ID11ORDER BY EXECUTIONS DESC12LIMIT 20;单次很快但调用极多的 SQL,也可能消耗大量总资源。优化优先级要同时看单次时延、调用量和总耗时。
1-- root@sys2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 SUM(ELAPSED_TIME) AS TOTAL_USEC,4 AVG(ELAPSED_TIME) AS AVG_USEC,5 MAX(ELAPSED_TIME) AS MAX_USEC,6 MIN(QUERY_SQL) AS SQL_SAMPLE7FROM oceanbase.GV$OB_SQL_AUDIT8WHERE TENANT_ID = 10029 AND IS_EXECUTOR_RPC = 010 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000011GROUP BY SQL_ID12ORDER BY TOTAL_USEC DESC13LIMIT 20;累计耗时更适合做容量优化清单。它仍只代表当前 Audit 窗口,报告里要注明采样时间和过滤条件。
1-- root@sys2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 AVG(QUEUE_TIME) AS AVG_QUEUE_USEC,4 AVG(ELAPSED_TIME) AS AVG_ELAPSED_USEC,5 ROUND(100 * SUM(QUEUE_TIME)6 / NULLIF(SUM(ELAPSED_TIME), 0), 2) AS QUEUE_PCT7FROM oceanbase.GV$OB_SQL_AUDIT8WHERE TENANT_ID = 10029 AND IS_EXECUTOR_RPC = 010 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000011GROUP BY SQL_ID12HAVING COUNT(*) >= 513ORDER BY QUEUE_PCT DESC14LIMIT 20;排队占比高时,改 SQL 计划未必有效。先检查租户 CPU 配额、并发和节点负载,再决定是否需要 SQL 级优化。
1-- root@sys2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 AVG(GET_PLAN_TIME) AS AVG_GET_PLAN_USEC,4 MAX(GET_PLAN_TIME) AS MAX_GET_PLAN_USEC,5 AVG(IS_HIT_PLAN) AS PLAN_HIT_RATIO6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND IS_EXECUTOR_RPC = 09 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000010GROUP BY SQL_ID11ORDER BY AVG_GET_PLAN_USEC DESC12LIMIT 20;命中率低且取计划慢,优先看 SQL 规范化和计划淘汰。命中率高但取计划仍慢,再查 Schema 变化、锁和节点异常。
1-- root@sys2SELECT SQL_ID, TRACE_ID, PARTITION_CNT,3 RPC_COUNT, ELAPSED_TIME, QUERY_SQL4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND IS_EXECUTOR_RPC = 07 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 10000008ORDER BY PARTITION_CNT DESC, ELAPSED_TIME DESC9LIMIT 20;分区数多可能意味着分区裁剪未生效,也可能是本就需要全局扫描的分析 SQL。回到算子树检查范围和数据交换方式。
1-- 目标租户;TRACE_ID 使用慢请求实际值2SELECT PLAN_LINE_ID, PLAN_DEPTH, PLAN_OPERATION,3 OUTPUT_ROWS, STARTS, SVR_IP4FROM oceanbase.GV$SQL_PLAN_MONITOR5WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'6ORDER BY PLAN_LINE_ID, SVR_IP;Plan Monitor 记录的是算子实例的运行数据,比 EXPLAIN 更接近真实执行。同一算子可能在多个线程和节点出现多行,需要先按算子汇总。
1-- 目标租户2SELECT PLAN_LINE_ID,3 CONCAT(LPAD(' ', PLAN_DEPTH, ' '), PLAN_OPERATION) AS OPERATOR,4 SUM(OUTPUT_ROWS) AS OUTPUT_ROWS,5 SUM(STARTS) AS STARTS,6 COUNT(*) AS THREADS7FROM oceanbase.GV$SQL_PLAN_MONITOR8WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'9GROUP BY PLAN_LINE_ID, PLAN_OPERATION, PLAN_DEPTH10ORDER BY PLAN_LINE_ID;输出行数远高于优化器估算时,继续检查统计信息、谓词选择性和数据倾斜。STARTS 很高则要留意嵌套循环反复驱动内表。
1-- 目标租户2SELECT PLAN_LINE_ID, PLAN_OPERATION,3 MIN(FIRST_REFRESH_TIME) AS OPEN_TIME,4 MIN(FIRST_CHANGE_TIME) AS FIRST_ROW_TIME,5 MAX(LAST_CHANGE_TIME) AS LAST_ROW_TIME,6 MAX(LAST_REFRESH_TIME) AS CLOSE_TIME7FROM oceanbase.GV$SQL_PLAN_MONITOR8WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'9GROUP BY PLAN_LINE_ID, PLAN_OPERATION10ORDER BY PLAN_LINE_ID;首行迟迟不返回和总执行慢不是同一回事。阻塞型 SORT、HASH JOIN 构建端或远程数据交换,都可能让首行时间明显后移。
1-- 目标租户2SELECT PLAN_LINE_ID, PLAN_OPERATION, SVR_IP,3 SUM(OUTPUT_ROWS) AS OUTPUT_ROWS,4 COUNT(*) AS THREADS5FROM oceanbase.GV$SQL_PLAN_MONITOR6WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'7GROUP BY PLAN_LINE_ID, PLAN_OPERATION, SVR_IP8ORDER BY PLAN_LINE_ID, OUTPUT_ROWS DESC;同一并行算子在某台节点输出远高于其他节点,可能存在数据倾斜。先确认分区和参数分布,再考虑并行度或数据模型调整。
1-- 目标租户2SELECT PLAN_LINE_ID, PLAN_OPERATION,3 SUM(OUTPUT_ROWS) AS OUTPUT_ROWS,4 SUM(STARTS) AS STARTS5FROM oceanbase.GV$SQL_PLAN_MONITOR6WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'7GROUP BY PLAN_LINE_ID, PLAN_OPERATION8ORDER BY OUTPUT_ROWS DESC9LIMIT 20;中间结果集很大而最终返回很少,说明过滤或聚合发生得太晚。回到 SQL 和算子属性确认谓词是否下推、Join 顺序是否合理。
1-- 目标租户2SELECT PLAN_LINE_ID, PLAN_OPERATION,3 SUM(STARTS) AS TOTAL_STARTS,4 SUM(OUTPUT_ROWS) AS OUTPUT_ROWS5FROM oceanbase.GV$SQL_PLAN_MONITOR6WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'7GROUP BY PLAN_LINE_ID, PLAN_OPERATION8ORDER BY TOTAL_STARTS DESC9LIMIT 20;Nested Loop 内表扫描常出现高 STARTS。如果外表实际行数远超估算,索引再好也可能被反复访问拖慢。
1-- 目标租户2SELECT PLAN_LINE_ID, PLAN_OPERATION,3 COUNT(*) AS OPERATOR_INSTANCES,4 COUNT(DISTINCT SVR_IP) AS OBSERVER_COUNT5FROM oceanbase.GV$SQL_PLAN_MONITOR6WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'7GROUP BY PLAN_LINE_ID, PLAN_OPERATION8ORDER BY PLAN_LINE_ID;线程数多不代表一定更快。数据量小或节点资源紧张时,并行调度和数据交换本身也会成为成本。
1-- 目标租户;算子行号按第 32 条结果替换2SELECT SVR_IP, OUTPUT_ROWS, STARTS,3 FIRST_CHANGE_TIME, LAST_CHANGE_TIME4FROM oceanbase.GV$SQL_PLAN_MONITOR5WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'6 AND PLAN_LINE_ID = 57ORDER BY OUTPUT_ROWS DESC;某一线程处理绝大多数数据时,总耗时往往由它决定。先确认分区键和 Join Key 的数据分布,再判断是否需要改并行策略。
1-- 先用缓存计划得到估算行数2SELECT PLAN_LINE_ID, OPERATOR, ROWS AS ESTIMATED_ROWS3FROM oceanbase.GV$OB_PLAN_CACHE_PLAN_EXPLAIN4WHERE TENANT_ID = 10025 AND SVR_IP = '10.0.0.11'6 AND SVR_PORT = 28827 AND PLAN_ID = 2488ORDER BY PLAN_LINE_ID;将结果与第 32 条逐行对照。估算与实际相差几个数量级时,统计信息、直方图和列相关性应优先检查。
1-- root@sys2SELECT SVR_IP, SVR_PORT, IS_EXECUTOR_RPC,3 SQL_ID, PLAN_ID, ELAPSED_TIME,4 EXECUTE_TIME, RETURN_ROWS, QUERY_SQL5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'8ORDER BY SVR_IP, IS_EXECUTOR_RPC;分布式计划可能有入口请求和多个 executor RPC。排障报告要把它们区分开,不能把各 RPC 耗时简单相加当成用户等待时间。
1-- 目标租户2SELECT OWNER, TABLE_NAME, NUM_ROWS,3 BLOCKS, AVG_ROW_LEN, LAST_ANALYZED4FROM oceanbase.DBA_TAB_STATISTICS5WHERE OWNER = 'APPDB'6 AND TABLE_NAME = 'ORDERS';行数和收集时间明显落后于业务现状时,优化器估算容易失真。先确认自动统计是否运行,再安排受控收集。
1-- 目标租户2SELECT DATABASE_NAME, TABLE_NAME,3 LAST_ANALYZED_ROWS, LAST_ANALYZED_TIME,4 INSERTS, UPDATES, DELETES,5 STALE_PERCENT, IS_STALE6FROM oceanbase.DBA_OB_TABLE_STAT_STALE_INFO7WHERE DATABASE_NAME = 'appdb'8 AND IS_STALE = 'YES'9ORDER BY INSERTS + UPDATES + DELETES DESC;过期只表示 DML 变化超过阈值,不等于必须立刻全量收集。先按问题 SQL 涉及的表和业务窗口排序处理。
1-- 目标租户2SELECT DATABASE_NAME, TABLE_NAME,3 LAST_ANALYZED_ROWS, INSERTS, UPDATES, DELETES,4 INSERTS + UPDATES + DELETES AS CHANGED_ROWS,5 IS_STALE6FROM oceanbase.DBA_OB_TABLE_STAT_STALE_INFO7WHERE DATABASE_NAME = 'appdb'8 AND TABLE_NAME = 'orders';大批量导入或状态更新后,行数变化可能不大,列值分布却已明显改变。统计信息判断还要结合问题谓词所用列。
1-- 目标租户2SELECT OWNER, TABLE_NAME, COLUMN_NAME,3 NUM_DISTINCT, NUM_NULLS, DENSITY,4 HISTOGRAM, NUM_BUCKETS, LAST_ANALYZED5FROM oceanbase.DBA_TAB_COL_STATISTICS6WHERE OWNER = 'APPDB'7 AND TABLE_NAME = 'ORDERS'8ORDER BY COLUMN_NAME;等值谓词列的 NUM_DISTINCT、空值数和直方图会直接影响选择率估算。不要只看表总行数是否准确。
1-- 目标租户2SELECT OWNER, TABLE_NAME, COLUMN_NAME,3 HISTOGRAM, NUM_BUCKETS, NUM_DISTINCT4FROM oceanbase.DBA_TAB_COL_STATISTICS5WHERE OWNER = 'APPDB'6 AND TABLE_NAME = 'ORDERS'7 AND COLUMN_NAME IN ('CUSTOMER_ID', 'ORDER_STATUS');数据倾斜明显的列可能需要直方图;分布均匀或高频变化的列盲目加直方图,反而增加收集和计划波动成本。
1-- 目标租户2SELECT OWNER, TABLE_NAME, COLUMN_NAME,3 ENDPOINT_NUMBER, ENDPOINT_VALUE,4 ENDPOINT_ACTUAL_VALUE5FROM oceanbase.DBA_TAB_HISTOGRAMS6WHERE OWNER = 'APPDB'7 AND TABLE_NAME = 'ORDERS'8 AND COLUMN_NAME = 'ORDER_STATUS'9ORDER BY ENDPOINT_NUMBER;桶信息用来解释优化器为什么认为某个值稀少或常见。先核对统计收集时间,过期直方图比没有直方图更容易误导。
1-- 目标租户2SELECT OWNER, INDEX_NAME, TABLE_NAME,3 BLEVEL, LEAF_BLOCKS, DISTINCT_KEYS,4 CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED5FROM oceanbase.DBA_IND_STATISTICS6WHERE OWNER = 'APPDB'7 AND TABLE_NAME = 'ORDERS'8ORDER BY INDEX_NAME;索引存在不等于优化器一定选择。区分度、聚簇因子和统计时间都可能改变索引成本估算。
1-- 目标租户管理员;写操作,安排在受控窗口2CALL DBMS_STATS.GATHER_TABLE_STATS(3 'APPDB', 'ORDERS',4 estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,5 method_opt=>'FOR ALL COLUMNS SIZE AUTO',6 degree=>8,7 cascade=>TRUE,8 no_invalidate=>FALSE9);cascade=>TRUE 同时收集索引统计,no_invalidate=>FALSE 允许相关缓存计划失效。执行前评估大表扫描和计划变化影响。
1-- 目标租户管理员;分区名按现场替换2CALL DBMS_STATS.GATHER_TABLE_STATS(3 'APPDB', 'ORDERS',4 partname=>'P202609',5 granularity=>'PARTITION',6 degree=>8,7 cascade=>TRUE,8 no_invalidate=>FALSE9);新分区刚装载数据时,分区级收集比整表收集更可控。全局统计是否同步满足查询需要,要结合分区表策略继续核对。
1-- 目标租户管理员;写操作2CALL DBMS_STATS.GATHER_TABLE_STATS(3 'APPDB', 'ORDERS',4 method_opt=>'FOR COLUMNS ORDER_STATUS SIZE 16',5 degree=>8,6 no_invalidate=>FALSE7);桶数按真实分布确定,不要照抄 16。收集后对比典型值和极端值的执行计划,确认没有改善一个参数却拖慢另一个参数。
1-- 目标租户管理员;首次使用时执行2CALL DBMS_STATS.CREATE_STAT_TABLE('APPDB', 'ORDERS_STATS_BAK');改统计信息前先准备回退点。统计表是普通用户对象,要纳入权限和保留管理,名称中写明用途比临时乱建更容易维护。
1-- 目标租户管理员2CALL DBMS_STATS.EXPORT_TABLE_STATS(3 'APPDB', 'ORDERS',4 stattab=>'ORDERS_STATS_BAK',5 statown=>'APPDB'6);导出成功后再收集新统计。这样计划明显退化时,可以恢复原统计并重新验证。
1-- 目标租户管理员;仅在确认需要回退时执行2CALL DBMS_STATS.IMPORT_TABLE_STATS(3 'APPDB', 'ORDERS',4 stattab=>'ORDERS_STATS_BAK',5 statown=>'APPDB'6);导入后还要重新获取执行计划并做压力回归。恢复统计信息不等于计划一定立即回到原状态。
1-- 目标租户2SELECT OWNER, TABLE_NAME, NUM_ROWS,3 BLOCKS, LAST_ANALYZED4FROM oceanbase.DBA_TAB_STATISTICS5WHERE OWNER = 'APPDB'6 AND TABLE_NAME = 'ORDERS';行数应与业务量级接近,时间应更新到本次窗口。若视图没有变化,先查统计收集任务记录和权限,而不是重复提交。
1-- 目标租户2SELECT DATABASE_NAME, TABLE_NAME,3 LAST_ANALYZED_ROWS, LAST_ANALYZED_TIME,4 INSERTS, UPDATES, DELETES, IS_STALE5FROM oceanbase.DBA_OB_TABLE_STAT_STALE_INFO6WHERE DATABASE_NAME = 'appdb'7 AND TABLE_NAME = 'orders';过期标记恢复正常只说明统计任务完成。最终是否改善,仍以同一参数、同一负载下的实际执行数据为准。
1-- 目标租户2SHOW INDEX FROM appdb.orders;先确认索引名称、列顺序、唯一性和可见性。看到谓词列“在索引里”还不够,联合索引的前导列顺序决定可用范围。
1-- 目标租户2SELECT INDEX_NAME, NON_UNIQUE, SEQ_IN_INDEX,3 COLUMN_NAME, COLLATION, CARDINALITY4FROM information_schema.STATISTICS5WHERE TABLE_SCHEMA = 'appdb'6 AND TABLE_NAME = 'orders'7ORDER BY INDEX_NAME, SEQ_IN_INDEX;把实际谓词、排序列和索引列顺序放在一起看。不要因为索引包含某列就断定能高效过滤。
1-- 目标租户2SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE3FROM information_schema.TABLE_CONSTRAINTS4WHERE TABLE_SCHEMA = 'appdb'5 AND TABLE_NAME = 'orders'6ORDER BY CONSTRAINT_TYPE, CONSTRAINT_NAME;唯一约束不仅影响数据正确性,也影响优化器基数判断。新增普通索引前先避免与现有唯一索引重复。
1-- 目标租户2SHOW CREATE TABLE appdb.orders;索引、分区键、表组和表属性都在这里。性能问题涉及分布式扫描时,不能只看 SHOW INDEX。
1-- 测试库;参数使用真实高频值2EXPLAIN SELECT order_id, order_status3FROM appdb.orders4WHERE customer_id = 10015 AND order_status = 'PAID';关注访问路径、扫描范围和估算行数。即使使用索引,回表次数过多或选择性太差也可能比扫描更慢。
1-- 测试库;仅用于验证,不直接写进生产 SQL2EXPLAIN SELECT order_id, order_status3FROM appdb.orders FORCE INDEX (idx_customer_status)4WHERE customer_id = 10015 AND order_status = 'PAID';强制索引用来判断“如果走这条路径会怎样”,不是永久修复。成本和实际耗时都更好后,再决定改索引、统计或使用计划管理。
1-- 测试库2EXPLAIN SELECT order_id, update_time3FROM appdb.orders4WHERE customer_id = 10015ORDER BY update_time DESC6LIMIT 20;检查计划是否额外 SORT。联合索引既要满足过滤,也要考虑排序方向和返回列,不能只为消除排序堆叠宽索引。
1-- 测试库;分区键按现场替换2EXPLAIN SELECT COUNT(*)3FROM appdb.orders4WHERE business_date = '2026-09-16';计划中的分区范围应与谓词一致。字段类型转换、函数包裹或隐式转换都可能让裁剪失效。
1-- 测试库;与直接范围谓词作对照2EXPLAIN SELECT COUNT(*)3FROM appdb.orders4WHERE DATE(update_time) = '2026-09-16';对索引列套函数常使普通索引难以使用。优先改写成时间范围,再比较计划和实际结果是否等价。
1-- 测试库2EXPLAIN SELECT COUNT(*)3FROM appdb.orders4WHERE update_time >= '2026-09-16 00:00:00'5 AND update_time < '2026-09-17 00:00:00';范围写法更利于索引和分区裁剪。上线前要确认时区、字段精度和边界语义没有变化。
1-- 测试库;customer_code 为字符列时不要传数字2EXPLAIN SELECT order_id3FROM appdb.orders4WHERE customer_code = '10001';参数类型应与列类型一致。应用把字符列当数字绑定时,可能让索引选择和基数估算发生变化。
1-- 目标租户2SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_ROWS3FROM information_schema.TABLES4WHERE TABLE_SCHEMA = 'appdb'5 AND TABLE_NAME IN ('orders', 'customers');先确认大表和小表的量级,再看 Join Key 是否有索引和统计。旧行数会直接影响驱动表选择。
1-- 测试库2EXPLAIN SELECT o.order_id, c.customer_name3FROM appdb.orders AS o4JOIN appdb.customers AS c5 ON c.customer_id = o.customer_id6WHERE o.business_date = '2026-09-16';关注驱动表、Join 方法、数据交换和估算行数。不要只盯是否出现 HASH JOIN 或 NESTED LOOP,关键是输入规模是否符合估算。
1-- 目标租户2SELECT INDEX_NAME,3 GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS INDEX_COLUMNS4FROM information_schema.STATISTICS5WHERE TABLE_SCHEMA = 'appdb'6 AND TABLE_NAME = 'orders'7GROUP BY INDEX_NAME8ORDER BY INDEX_NAME;先找完全重复或前缀重叠的索引。多建一条索引会增加写入、合并和存储成本,不能只看查询收益。
1-- 目标租户;DDL,先在测试环境验证2CREATE INDEX idx_orders_customer_status_time3ON appdb.orders(customer_id, order_status, update_time);列顺序应由等值条件、范围条件、排序和选择性共同决定。上线后要检查写入影响、计划变化和是否出现重复索引。
1-- 目标租户2SHOW INDEX FROM appdb.orders3WHERE Key_name = 'idx_orders_customer_status_time';名称存在后继续看列顺序和基数,不要只以 DDL 返回成功作为验收。
1-- 目标租户管理员;写操作2CALL DBMS_STATS.GATHER_TABLE_STATS(3 'APPDB', 'ORDERS',4 degree=>8,5 cascade=>TRUE,6 no_invalidate=>FALSE7);新索引没有合适统计时,优化器可能仍不选择它。收集后重新比较计划和实际运行数据。
1-- 测试库2EXPLAIN SELECT order_id, order_status3FROM appdb.orders4WHERE customer_id = 10015 AND order_status = 'PAID'6ORDER BY update_time DESC7LIMIT 20;计划使用新索引只是第一步。必须用真实参数和并发回归,确认执行时间、扫描行数和写入成本都在预期内。
1-- 目标租户2SELECT INDEX_NAME, NUM_ROWS, DISTINCT_KEYS,3 LEAF_BLOCKS, CLUSTERING_FACTOR, LAST_ANALYZED4FROM oceanbase.DBA_IND_STATISTICS5WHERE OWNER = 'APPDB'6 AND INDEX_NAME = 'IDX_ORDERS_CUSTOMER_STATUS_TIME';统计时间应是本次变更之后。区分度和聚簇因子异常时,先检查数据分布与收集过程。
1-- 目标租户;DDL,仅在回退方案触发时执行2DROP INDEX idx_orders_customer_status_time3ON appdb.orders;索引没有收益或明显拖慢写入时按回退方案删除。删除后再次确认计划恢复和应用错误率,不能只看 DDL 完成。
1-- 目标租户2SHOW FULL PROCESSLIST;先找运行时间长、状态异常和同类 SQL 堆积的会话。Processlist 是当前快照,重要会话仍要保存 trace_id 和 Audit 记录。
1-- 目标租户2SELECT ID, USER, HOST, DB, COMMAND,3 TIME, STATE, INFO4FROM information_schema.PROCESSLIST5WHERE COMMAND <> 'Sleep'6ORDER BY TIME DESC7LIMIT 30;当前运行时间长不一定是性能故障,批处理和 DDL 本来可能耗时。与业务窗口、锁等待和 Audit 执行阶段一起判断。
1-- 目标租户当前会话2SHOW VARIABLES LIKE 'ob_query_timeout';超时过短会制造大量失败和重试,过长则可能让异常查询长期占资源。先确认应用实际设置,不要把超时错误直接归因于执行计划。
1-- 仅测试会话,单位为微秒2SET SESSION ob_query_timeout = 600000000;该设置只用于完整采集测试 SQL 的执行数据,不能当作生产优化。问题查完后关闭测试会话或恢复原值。
1-- 目标租户当前会话2SHOW VARIABLES LIKE 'ob_trx_timeout';事务超时与单条查询超时不同。长事务、锁等待和应用重试可能共同放大 SQL 时延。
1-- root@sys2SELECT USER_NAME, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 SUM(ELAPSED_TIME) AS TOTAL_USEC5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND IS_EXECUTOR_RPC = 08 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 10000009GROUP BY USER_NAME10ORDER BY TOTAL_USEC DESC;某个账号流量突增时,租户整体变慢可能来自应用批次或重试风暴。先找来源再扩大租户资源。
1-- root@sys2SELECT DB_NAME, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 SUM(ELAPSED_TIME) AS TOTAL_USEC5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND IS_EXECUTOR_RPC = 08 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 10000009GROUP BY DB_NAME10ORDER BY TOTAL_USEC DESC;同一租户包含多个业务库时,这条能快速缩小范围。空数据库名和内部 SQL 需要单独过滤解释。
1-- root@sys2SELECT USER_CLIENT_IP, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 SUM(ELAPSED_TIME) AS TOTAL_USEC5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND IS_EXECUTOR_RPC = 08 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 10000009GROUP BY USER_CLIENT_IP10ORDER BY EXECUTIONS DESC;经 ODP 时要确认 USER_CLIENT_IP 是否完整保留真实来源。无法区分时,结合应用连接池和全链路 trace。
1-- root@sys2SELECT DATE_FORMAT(USEC_TO_TIME(REQUEST_TIME), '%Y-%m-%d %H:%i:%s')3 AS SECOND_SLOT,4 COUNT(*) AS SLOW_REQUESTS5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND IS_EXECUTOR_RPC = 08 AND ELAPSED_TIME >= 10000009 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000010GROUP BY SECOND_SLOT11ORDER BY SLOW_REQUESTS DESC12LIMIT 20;同一秒大量 SQL 同时变慢,更像资源或依赖抖动;只有一条 SQL 持续慢,则更偏向计划和数据问题。
1-- root@sys2SELECT TENANT_ID, UNIT_ID, ZONE, SVR_IP,3 MIN_CPU, MAX_CPU, MEMORY_SIZE4FROM oceanbase.GV$OB_UNITS5WHERE TENANT_ID = 10026ORDER BY ZONE, SVR_IP;Audit 排队高时先确认租户资源配额。这里是额度,不是实时使用率;是否扩容还要结合节点余量和长期负载。
1-- root@sys2SELECT s.ZONE, s.SVR_IP,3 s.CPU_CAPACITY - s.CPU_ASSIGNED AS CPU_HEADROOM,4 s.MEM_CAPACITY - s.MEM_ASSIGNED AS MEM_HEADROOM_BYTES5FROM oceanbase.GV$OB_SERVERS AS s6WHERE EXISTS (7 SELECT 1 FROM oceanbase.GV$OB_UNITS AS u8 WHERE u.TENANT_ID = 10029 AND u.SVR_IP = s.SVR_IP10)11ORDER BY s.ZONE, s.SVR_IP;租户需要扩容时,Unit 所在节点必须有资源可分配。不能因为集群总余量够就直接调大规格。
1-- root@sys2SELECT SVR_IP, COUNT(*) AS EXECUTIONS,3 AVG(QUEUE_TIME) AS AVG_QUEUE_USEC,4 AVG(EXECUTE_TIME) AS AVG_EXECUTE_USEC,5 AVG(ELAPSED_TIME) AS AVG_ELAPSED_USEC6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND IS_EXECUTOR_RPC = 09 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000010GROUP BY SVR_IP11ORDER BY AVG_QUEUE_USEC DESC;仅一台节点排队高时,优先查该节点负载和 Unit 分布;所有节点都高,再看租户整体并发与配额。
1-- root@sys2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 SUM(CONCURRENCY_WAIT_TIME) AS CONCURRENCY_WAIT_USEC,4 AVG(ELAPSED_TIME) AS AVG_ELAPSED_USEC,5 MIN(QUERY_SQL) AS SQL_SAMPLE6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND IS_EXECUTOR_RPC = 09 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 100000010GROUP BY SQL_ID11ORDER BY CONCURRENCY_WAIT_USEC DESC12LIMIT 20;并发等待高的 SQL 需要结合锁专题继续下钻。先确认是不是同一批更新互相竞争,再决定索引或事务改造。
1-- root@sys;在变更前固定时间窗执行2SELECT COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 MAX(ELAPSED_TIME) AS MAX_USEC,5 AVG(EXECUTE_TIME) AS AVG_EXECUTE_USEC,6 AVG(DISK_READS) AS AVG_DISK_READS,7 AVG(RETURN_ROWS) AS AVG_RETURN_ROWS8FROM oceanbase.GV$OB_SQL_AUDIT9WHERE TENANT_ID = 100210 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'11 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 1000000;没有变更前基线,就很难证明优化是否有效。保存参数分布、调用量和业务时段,避免拿低峰新结果对比高峰旧结果。
1-- root@sys2SELECT SVR_IP, PLAN_ID, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'7 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 10000008GROUP BY SVR_IP, PLAN_ID9ORDER BY SVR_IP, PLAN_ID;变更后计划 ID 可能在各节点陆续变化。只看一台节点无法证明全租户已经使用新计划。
1-- 测试环境或受控会话2SELECT order_id, order_status, update_time3FROM appdb.orders4WHERE customer_id = 10015 AND order_status = 'PAID'6ORDER BY update_time DESC7LIMIT 20;至少准备高频值、低频值和边界值三组参数。单一参数变快,可能以其他参数退化为代价。
1-- root@sys;使用回归执行产生的新 TRACE_ID2SELECT TRACE_ID, PLAN_ID, SVR_IP,3 ELAPSED_TIME, QUEUE_TIME, GET_PLAN_TIME,4 EXECUTE_TIME, DISK_READS, RETURN_ROWS, RET_CODE5FROM oceanbase.GV$OB_SQL_AUDIT6WHERE TENANT_ID = 10027 AND TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0';回归结果必须来自真实执行,不要只比较 EXPLAIN 的 COST。确认错误码、返回行数和结果集也与变更前一致。
1-- 目标租户;使用回归 TRACE_ID2SELECT PLAN_LINE_ID, PLAN_OPERATION,3 SUM(OUTPUT_ROWS) AS OUTPUT_ROWS,4 SUM(STARTS) AS STARTS,5 COUNT(*) AS THREADS6FROM oceanbase.GV$SQL_PLAN_MONITOR7WHERE TRACE_ID = 'YB420BA2D99B-0005EBBFC45D5A00-0-0'8GROUP BY PLAN_LINE_ID, PLAN_OPERATION9ORDER BY PLAN_LINE_ID;预期优化应体现在扫描行数、重复启动或中间结果下降,而不只是计划名称变化。
1-- root@sys2SELECT SVR_IP, SVR_PORT, PLAN_ID,3 EXECUTIONS, AVG_EXE_TIME, CPU_TIME4FROM oceanbase.GV$OB_PLAN_CACHE_PLAN_STAT5WHERE TENANT_ID = 10026 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'7ORDER BY SVR_IP, PLAN_ID;确认所有承载节点都已出现预期计划。仍有旧计划时,先判断是否还有旧连接或缓存未淘汰,不要直接重启节点。
1-- root@sys;在与基线等长的窗口执行2SELECT COUNT(*) AS EXECUTIONS,3 AVG(DISK_READS) AS AVG_DISK_READS,4 MAX(DISK_READS) AS MAX_DISK_READS,5 AVG(ELAPSED_TIME) AS AVG_USEC6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'9 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 1000000;物理读下降通常是访问路径改善的证据,但冷缓存和热缓存窗口不能直接横比。最好在相近负载和缓存状态下验证。
1-- root@sys2SELECT COUNT(*) AS EXECUTIONS,3 ROUND(100 * SUM(IS_HIT_PLAN) / COUNT(*), 2)4 AS PLAN_HIT_PCT,5 AVG(GET_PLAN_TIME) AS AVG_GET_PLAN_USEC6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'9 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 10 * 60 * 1000000;SQL 改写后若 SQL_ID 大量变化,先确认应用是否保持绑定变量。命中率下降会抵消一部分执行计划收益。
1-- root@sys2SELECT RET_CODE, COUNT(*) AS OCCURRENCES,3 MIN(QUERY_SQL) AS SQL_SAMPLE4FROM oceanbase.GV$OB_SQL_AUDIT5WHERE TENANT_ID = 10026 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'7 AND RET_CODE <> 08 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 10000009GROUP BY RET_CODE;性能变快但错误率上升不能算成功。索引 DDL、SQL 改写和超时调整都要同时看正确性与稳定性。
1-- root@sys;写 SQL 的 SQL_ID 按实际替换2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 AVG(ELAPSED_TIME) AS AVG_USEC,4 MAX(ELAPSED_TIME) AS MAX_USEC,5 AVG(AFFECTED_ROWS) AS AVG_AFFECTED_ROWS6FROM oceanbase.GV$OB_SQL_AUDIT7WHERE TENANT_ID = 10028 AND SQL_ID IN ('INSERT_SQL_ID', 'UPDATE_SQL_ID', 'DELETE_SQL_ID')9 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 100000010GROUP BY SQL_ID;新增索引会增加写入维护成本。查询收益和写入退化要一起评估,尤其是高频更新表。
1-- 目标租户2SELECT DATABASE_NAME, TABLE_NAME,3 LAST_ANALYZED_TIME, INSERTS, UPDATES, DELETES,4 IS_STALE5FROM oceanbase.DBA_OB_TABLE_STAT_STALE_INFO6WHERE DATABASE_NAME = 'appdb'7 AND TABLE_NAME = 'orders';大批量上线数据后,刚收集的统计也可能很快过期。将统计维护纳入发布和批处理流程,比每次故障后手工补救可靠。
1-- root@sys;固定验收窗口2SELECT SQL_ID, COUNT(*) AS EXECUTIONS,3 COUNT(DISTINCT PLAN_ID) AS PLAN_COUNT,4 COUNT(DISTINCT SVR_IP) AS OBSERVER_COUNT,5 AVG(ELAPSED_TIME) AS AVG_USEC,6 MAX(ELAPSED_TIME) AS MAX_USEC,7 AVG(DISK_READS) AS AVG_DISK_READS,8 SUM(CASE WHEN RET_CODE <> 0 THEN 1 ELSE 0 END) AS ERRORS9FROM oceanbase.GV$OB_SQL_AUDIT10WHERE TENANT_ID = 100211 AND SQL_ID = '0123456789ABCDEF0123456789ABCDEF'12 AND REQUEST_TIME >= TIME_TO_USEC(NOW()) - 30 * 60 * 100000013GROUP BY SQL_ID;验收至少同时看样本量、计划数、节点、平均与最大耗时、物理读和错误数。任何一项没有基线,就不能只凭“平均耗时下降”宣布优化完成。
OceanBase SQL 调优先从真实请求取证:trace_id、执行节点、PLAN_ID、各阶段耗时和实际算子行数缺一不可。计划不合理时再回到统计信息、索引、分区裁剪和 SQL 写法,顺序比直接试 Hint 更重要。
改动以后用相同业务时段、参数分布和调用量做回归,同时检查写入与错误率。只看一次 EXPLAIN 或一条最快样本,都不足以证明线上问题已经解决。
如果这份清单对你有用,欢迎点赞、收藏并转发给负责 OceanBase 性能排查的同事。
更多数据库运维专题会继续整理到 ORA100 · DBA100:
微信里也可以搜索小程序 「三笠的百令册」,随时查常用命令。
ORA100 DBA100 数据库命令手册