OceanBase 执行计划与慢 SQL 优化

1. 对应简历段落

这篇文章对应简历中“Oracle 到 OceanBase 迁移后负责慢 SQL 治理、执行计划分析、索引优化、分页与报表性能回归、上线后稳定性保障”等经历。

面试里讲慢 SQL 优化,不能停留在“加索引”。资深面试官会问:你怎么发现慢 SQL?怎么看执行计划?如何判断是全表扫描、回表、排序、临时表、分布式执行、锁等待还是租户资源不足?OceanBase 与单机 MySQL、Oracle 的差异是什么?优化后如何验证没有改变业务结果?

推荐把经历讲成一套闭环:采集慢 SQL 和业务入口,定位执行计划和资源消耗,结合 SQL 语义、索引、统计信息、租户资源和数据分布做改造,最后用压测、双跑和生产观测验证。

2. 业务背景

信创迁移完成后,应用从 Oracle 切到 OceanBase。语法兼容只是第一关,真正上线后最容易暴露的是性能问题。老系统中有大量列表页、报表页、批处理和权限范围查询。迁移前 Oracle 上可能依赖历史优化器、索引、统计信息和硬件资源,迁移后执行计划变化,原来可接受的 SQL 可能变慢。

典型问题包括:

1. ROWNUM 改 LIMIT 后,深分页扫描大量数据。
2. TRUNC(create_time) 改写不彻底,日期字段索引失效。
3. CONNECT BY 改递归 CTE 后,组织树结果再 join 大表变慢。
4. 报表 SQL 复杂聚合,临时表和排序消耗高。
5. 迁移后统计信息不准确,优化器选择了错误索引。
6. OceanBase 租户 CPU、内存、并发资源限制导致抖动。

业务侧感知通常是“页面打开慢”“导出超时”“晚上批处理跑不完”“数据库连接池占满”。因此慢 SQL 优化不能只在数据库里看单条 SQL,而要把业务入口、调用频率、用户等待时间和系统资源一起看。

3. 核心原理

执行计划是数据库对 SQL 的执行路径描述。看计划时重点关注几个问题。

第一,访问路径。是全表扫描、索引范围扫描、唯一索引查找,还是先扫索引再回表?如果查询返回少量数据却全表扫描,大概率需要索引或改写条件。

第二,连接顺序。多表 join 时,驱动表选择错误会放大中间结果。比如先扫保单明细千万行,再过滤机构范围,就比先得到机构集合再按索引查保单慢得多。

第三,排序和临时表。ORDER BYGROUP BYDISTINCTUNION 都可能产生排序或临时结果。如果排序字段没有合适索引,分页和报表会很慢。

第四,估算行数与实际行数。优化器依赖统计信息估算数据量。如果统计信息过旧,可能选择错误计划。

第五,分布式执行代价。OceanBase 是分布式数据库,数据可能分布在不同分区和节点上。跨分区查询、数据重分布、远程执行会比本地索引查找更重。

第六,资源与并发。慢不一定全是 SQL 写得差,也可能是租户 CPU 不足、内存不足、并发过高、锁等待、事务太长。

OceanBase MySQL 模式常用 EXPLAIN 查看计划:

EXPLAIN SELECT ...

上线后还要结合慢 SQL 视图、SQL 审计、租户监控和应用链路日志定位。

4. 项目落地

项目中我会把慢 SQL 分成三类。

第一类是迁移改写引入的问题。比如日期条件从 TRUNC 改成了 DATE(create_time),语义对了但索引没了。这类要回到 SQL 改写规范,改成范围查询。

第二类是原本就慢,只是在迁移后暴露。比如报表 SQL 大范围扫描、动态排序、深分页、复杂 OR 条件。这类需要结合业务降级、异步化、预聚合或索引设计。

第三类是环境和计划问题。比如统计信息缺失、租户资源不足、并发批任务叠加。这类需要数据库侧运维和资源治理配合。

优化流程一般是:

1. 从应用 APM、网关日志、慢 SQL 日志中确认慢 SQL 和业务入口。
2. 固定 SQL、参数和数据范围,复现执行耗时。
3. 查看执行计划,判断访问路径、join 顺序、排序、临时表和估算行数。
4. 检查表结构、索引、数据量、数据倾斜和统计信息。
5. 给出改写方案:SQL 改写、索引调整、分页方式调整、预聚合、缓存或任务拆分。
6. 用相同参数对比优化前后耗时和结果。
7. 灰度上线,观察 P95/P99、数据库 CPU、连接池、慢 SQL 数量。

在 Java 侧也要配合。比如 MyBatis 动态 SQL 里可选条件很多,不同参数组合走不同索引。要统计真实生产参数分布,而不是只拿一组测试参数看计划。对于批处理,要控制并发和分页大小,避免多个大 SQL 同时压数据库。

5. 示例 SQL 或流程

问题 SQL:

SELECT o.policy_no,
       o.customer_id,
       o.premium,
       o.create_time
FROM policy_order o
WHERE DATE(o.create_time) = :bizDate
  AND o.status = 'PAID'
ORDER BY o.create_time DESC
LIMIT 0, 20;

问题在于 DATE(o.create_time) 让索引难以使用。改造后:

SELECT o.policy_no,
       o.customer_id,
       o.premium,
       o.create_time
FROM policy_order o
WHERE o.create_time >= :startTime
  AND o.create_time < :endTime
  AND o.status = 'PAID'
ORDER BY o.create_time DESC, o.policy_no DESC
LIMIT 0, 20;

索引设计:

CREATE INDEX idx_policy_status_time_no
ON policy_order(status, create_time, policy_no);

如果查询还经常按机构过滤:

CREATE INDEX idx_policy_org_status_time_no
ON policy_order(org_id, status, create_time, policy_no);

另一个例子是组织树统计:

WITH RECURSIVE org_tree AS (
    SELECT org_id FROM sales_org WHERE org_id = :rootOrgId
    UNION ALL
    SELECT c.org_id
    FROM sales_org c
    JOIN org_tree p ON c.parent_org_id = p.org_id
)
SELECT SUM(o.premium)
FROM policy_order o
JOIN org_tree t ON o.org_id = t.org_id
WHERE o.create_time >= :startTime
  AND o.create_time < :endTime;

优化思路不是只看 CTE,而是确保:

CREATE INDEX idx_org_parent ON sales_org(parent_org_id, org_id);
CREATE INDEX idx_order_org_time ON policy_order(org_id, create_time);

如果机构范围很大、报表频率高,可以把 org_tree 结果沉淀到权限范围表或闭包表,再做 join。

慢 SQL 排查记录可以按这个模板:

SQL ID:
业务入口:
调用频率:
平均/P95/P99 耗时:
参数样本:
执行计划问题:
涉及表数据量:
已有索引:
优化方案:
结果一致性验证:
上线观察指标:

6. 常见坑

第一个坑是只看单次耗时,不看调用频率。一个 2 秒 SQL 每天执行 5 次,和一个 200 毫秒 SQL 每分钟执行 1 万次,治理优先级不同。

第二个坑是只加索引,不改 SQL。字段套函数、前置通配 LIKE '%xx'、大 offset、复杂 OR 条件,单纯加索引效果有限。

第三个坑是索引建太多。每个查询都建一个索引会拖慢写入,增加存储和维护成本。要按高频业务组合设计复合索引。

第四个坑是忽略统计信息。迁移后数据导入、批量变更、分区变化都可能让统计信息不准确,优化器选择错误计划。

第五个坑是用测试库小数据判断性能。小数据下全表扫描也很快,生产千万级数据才暴露问题。要尽量用接近生产的数据量和参数分布压测。

第六个坑是优化后不做结果验证。SQL 改写、join 顺序调整、条件下推都可能改变结果,尤其是外连接、空值和聚合场景。

第七个坑是忽视 OceanBase 租户资源。慢 SQL 优化不仅是 SQL 层,租户 CPU、内存、并发、分区分布和网络也会影响性能。

7. 面试追问

面试官可能问:你看执行计划主要看什么?

回答:看访问路径、索引是否命中、join 顺序、估算行数、排序和临时表、是否跨分区或远程执行。再结合实际耗时、返回行数和业务参数判断问题。

面试官可能问:慢 SQL 一定要加索引吗?

回答:不一定。加索引只是手段之一。字段套函数要改写条件,深分页要改游标分页,大报表要预聚合或异步,复杂动态查询要收敛条件。索引要服务高频场景,不能无限增加。

面试官可能问:复合索引字段顺序怎么定?

回答:结合等值条件、范围条件、排序和区分度。一般等值过滤字段在前,范围和排序字段在后,同时考虑是否覆盖查询。也要看真实业务参数分布,而不是机械套最左前缀。

面试官可能问:迁移后为什么计划会变?

回答:数据库优化器不同,统计信息不同,函数和分页改写改变了 SQL 形态,数据导入后分布可能变化,OceanBase 的分布式执行和租户资源也会影响计划选择。

8. 推荐回答

可以这样回答:

“迁移后我把慢 SQL 治理当成上线稳定性的重点。先从应用日志和数据库慢 SQL 中定位业务入口、调用频率和参数,再用 EXPLAIN 看执行计划,重点关注是否全表扫描、索引是否命中、join 顺序、排序临时表、估算行数和分布式执行代价。优化时不会一上来就加索引,而是先判断 SQL 语义,比如日期字段套函数就改成范围查询,深分页改成游标分页,复杂报表考虑预聚合或异步。索引设计按高频查询组合来做,优化后用相同参数验证结果一致和耗时下降,最后灰度观察 P95、P99、连接池和租户资源。”

9. 延伸学习路线

第一,学习 OceanBase MySQL 模式的执行计划展示,理解表扫描、索引扫描、join、排序、聚合和分布式算子。

第二,系统学习索引设计,包括最左前缀、覆盖索引、回表、索引选择性、复合索引字段顺序。

第三,学习 SQL 改写技巧,包括范围查询、反连接改写、分页优化、聚合下推和减少临时表。

第四,学习 OceanBase 租户、分区、资源隔离和统计信息维护。

第五,建立慢 SQL 治理台账,把业务入口、参数样本、执行计划、优化前后指标和验证结论沉淀下来。