
1. 为什么需要关注SQL是否走索引在OceanBase数据库的实际运维和开发过程中SQL查询性能往往是影响系统整体表现的关键因素。我经历过太多因为索引使用不当导致的性能问题——一个本该毫秒级返回的查询因为没走索引而变成全表扫描让整个系统陷入瘫痪。索引的本质是数据库中的目录就像图书馆的图书检索系统。没有索引时数据库不得不进行全表扫描就像在图书馆里一本本书翻找而合理利用索引可以让查询效率提升几个数量级。但索引并非总是被使用这取决于多种因素查询条件的写法是否符合索引的最左前缀原则数据分布和统计信息是否准确索引的选择性是否足够高SQL语句中是否存在导致索引失效的操作在OceanBase这样的分布式数据库中索引的使用更加复杂因为数据可能分布在多个节点上。一次看似简单的查询如果没有正确使用索引可能会引发跨节点的数据搬运造成严重的性能问题。2. OceanBase中索引的基础知识2.1 OceanBase索引类型解析OceanBase支持多种索引类型每种都有其适用场景主键索引(Primary Key)每个表必须有且只有一个主键物理上按照主键顺序存储数据聚簇索引主键查询效率最高唯一索引(Unique Index)保证索引列值的唯一性可以包含NULL值但NULL值也需唯一普通索引(Normal Index)最基本的索引类型不保证唯一性全局索引(Global Index)分布式环境下的特殊索引索引数据与表数据可以分布在不同的节点适合点查询场景局部索引(Local Index)索引数据与表数据位于同一节点适合范围查询场景2.2 索引的存储结构OceanBase的索引主要采用B树结构这是经过验证的高效索引结构。理解B树的特点有助于我们更好地使用索引所有数据都存储在叶子节点非叶子节点只存储键值叶子节点通过指针连接适合范围查询树的高度通常很低3-4层意味着很少的磁盘IO就能定位到数据在分布式环境中OceanBase会对索引进行分区处理。全局索引和局部索引的主要区别就在于分区策略全局索引独立于表数据进行分区局部索引与表数据采用相同的分区策略3. 使用EXPLAIN分析执行计划3.1 EXPLAIN基础用法在OceanBase中EXPLAIN命令是我们查看SQL执行计划的主要工具。基本用法非常简单EXPLAIN SELECT * FROM orders WHERE user_id 1001;执行结果会显示查询的执行计划包括访问路径、连接方式等重要信息。一个典型的输出如下 |ID|OPERATOR |NAME |EST. ROWS|COST| ---------------------------------------------- |0 |TABLE SCAN |orders |1 |53 | |1 | TABLE SCAN |users |1 |45 | 3.2 解读执行计划的关键指标理解执行计划的每个部分至关重要OPERATOR执行操作类型TABLE SCAN全表扫描通常性能较差INDEX SCAN索引扫描通常性能较好JOIN表连接操作NAME访问的对象名称显示表名或索引名如果使用了索引这里会显示索引名称EST. ROWS预估返回行数优化器根据统计信息估算的值如果估算严重偏离实际可能需要更新统计信息COST执行成本优化器计算的相对成本值用于比较不同执行计划的优劣3.3 高级EXPLAIN用法OceanBase的EXPLAIN还支持更详细的输出EXPLAIN EXTENDED SELECT * FROM orders WHERE user_id 1001;EXTENDED选项会显示更多细节信息包括实际使用的索引名称过滤条件访问谓词分区信息对于复杂查询还可以使用EXPLAIN OUTLINE查看优化器的决策依据EXPLAIN OUTLINE SELECT * FROM orders WHERE user_id 1001;4. 索引使用情况实战分析4.1 确认索引是否被使用在执行计划中确认索引是否被使用的关键点是查看OPERATOR和NAME列如果OPERATOR是INDEX SCAN且NAME显示索引名称则说明使用了索引如果OPERATOR是TABLE SCAN则说明没有使用索引例如以下执行计划显示使用了索引 |ID|OPERATOR |NAME |EST. ROWS| ---------------------------------------------- |0 |INDEX SCAN |idx_user_id |1 | 4.2 常见索引失效场景即使创建了索引查询也不一定会使用。以下是OceanBase中常见的索引失效场景不符合最左前缀原则对于复合索引(a,b,c)查询条件必须包含a才能使用索引WHERE b1 AND c2无法使用索引使用了函数或运算WHERE YEAR(create_time) 2023无法使用create_time上的索引WHERE amount100 500无法使用amount上的索引使用了不等于(!或)WHERE status ! 1通常不会使用索引LIKE以通配符开头WHERE name LIKE %张无法使用索引WHERE name LIKE 张%可以使用索引OR条件使用不当WHERE a1 OR b2如果a和b上都有索引可能不会使用可以改写为UNION ALL提高效率4.3 强制使用索引在某些情况下优化器可能没有选择最优索引我们可以使用FORCE INDEX提示强制使用特定索引SELECT * FROM orders FORCE INDEX(idx_user_id) WHERE user_id 1001;但要注意强制使用索引应该是最后手段通常我们需要先检查统计信息是否最新分析查询条件是否符合索引使用规则考虑索引是否需要调整5. 高级诊断技巧5.1 使用SQL审计功能OceanBase提供了强大的SQL审计功能可以帮助我们识别没有使用索引的慢查询-- 开启SQL审计 ALTER SYSTEM SET enable_sql_audittrue; -- 查询审计日志 SELECT * FROM __all_virtual_sql_audit WHERE tenant_id 1001 ORDER BY elapsed_time DESC LIMIT 10;审计日志中包含每条SQL的执行时间、扫描行数等关键信息是定位性能问题的宝贵资源。5.2 分析优化器决策OceanBase提供了优化器跟踪功能可以深入了解优化器为什么选择某个执行计划-- 开启优化器跟踪 SET OPTIMIZER_TRACEenabledon; -- 执行查询 SELECT * FROM orders WHERE user_id 1001; -- 查看跟踪结果 SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;跟踪信息会显示优化器考虑过的所有执行路径及其成本估算帮助我们理解为什么某些索引没有被使用。5.3 使用性能视图OceanBase提供了一系列性能视图可以监控索引使用情况-- 查看索引使用统计 SELECT * FROM __all_virtual_table_index_stat WHERE table_id 1101; -- 查看表访问统计 SELECT * FROM __all_virtual_table_stat WHERE table_id 1101;这些统计信息可以帮助我们识别很少使用的索引可以考虑删除和频繁全表扫描的表可能需要优化索引。6. 索引优化实战案例6.1 案例一电商订单查询优化问题描述 电商平台的订单查询接口响应缓慢执行计划显示没有使用user_id上的索引。原始SQLSELECT * FROM orders WHERE user_id 1001 AND status PAID ORDER BY create_time DESC;分析过程检查表结构发现user_id上有单列索引使用EXPLAIN分析发现执行了全表扫描发现status字段使用了ENUM类型但查询条件中是字符串检查发现user_id列有大量NULL值解决方案修改查询条件使用ENUM值而非字符串SELECT * FROM orders WHERE user_id 1001 AND status 2 -- PAID对应的ENUM值 ORDER BY create_time DESC;创建复合索引ALTER TABLE orders ADD INDEX idx_user_status(user_id, status);清理user_id为NULL的无效数据优化结果 查询时间从1200ms降至15ms执行计划显示使用了新创建的复合索引。6.2 案例二报表查询优化问题描述 每月报表生成时系统负载极高一个复杂查询需要几分钟才能完成。原始SQLSELECT c.customer_name, SUM(o.amount) as total_amount FROM orders o JOIN customers c ON o.customer_id c.customer_id WHERE o.create_time BETWEEN 2023-01-01 AND 2023-01-31 GROUP BY c.customer_name ORDER BY total_amount DESC;分析过程使用EXPLAIN分析发现orders表进行了全表扫描检查索引发现create_time上有索引但未使用发现查询使用了BETWEEN条件且时间范围较大检查数据分布发现1月份数据量很大解决方案按月份对orders表进行分区ALTER TABLE orders PARTITION BY RANGE(TO_DAYS(create_time)) ( PARTITION p202301 VALUES LESS THAN (TO_DAYS(2023-02-01)), PARTITION p202302 VALUES LESS THAN (TO_DAYS(2023-03-01)), ... );创建本地分区索引ALTER TABLE orders ADD INDEX idx_create_time(create_time) LOCAL;优化查询只扫描必要分区SELECT c.customer_name, SUM(o.amount) as total_amount FROM orders o PARTITION(p202301) JOIN customers c ON o.customer_id c.customer_id GROUP BY c.customer_name ORDER BY total_amount DESC;优化结果 查询时间从5分钟降至8秒系统负载显著降低。7. 索引设计与维护最佳实践7.1 索引设计原则基于多年OceanBase使用经验我总结了以下索引设计原则选择性原则只为高选择性的列创建索引选择性 不同值数量/总行数通常选择性10%的列才考虑索引最左前缀原则设计复合索引时将最常用的列放在左边查询条件必须包含最左列才能使用索引覆盖索引原则尽量让索引包含查询所需的所有列避免回表操作可以极大提升性能适度原则索引不是越多越好每个索引都有维护成本通常建议单表索引不超过5-6个7.2 索引维护策略定期更新统计信息ANALYZE TABLE orders UPDATE HISTOGRAM ON user_id,status WITH 256 BUCKETS;统计信息过期会导致优化器做出错误决策。监控索引使用情况SELECT index_name, rows_read, rows_inserted, rows_updated, rows_deleted FROM __all_virtual_index_stat WHERE table_id 1101;删除很少使用的索引。定期重建碎片化索引ALTER TABLE orders REBUILD INDEX idx_user_id;特别是对于频繁更新的表。7.3 分布式环境下的特殊考虑在OceanBase这样的分布式数据库中还需要特别注意全局索引 vs 本地索引点查询多用全局索引范围查询多用本地索引索引分区策略考虑索引分区与表数据分区的亲和性避免跨节点访问RPC成本考量分布式环境下索引访问可能涉及网络通信有时全表扫描可能比跨节点索引访问更快8. 常见问题排查指南在实际工作中我遇到过各种索引相关的问题以下是典型问题的排查思路8.1 为什么索引存在但查询没有使用检查查询条件是否符合最左前缀原则检查是否有数据类型不匹配如字符串与数字比较检查是否有函数或运算导致索引失效检查统计信息是否最新检查索引的选择性是否足够高8.2 为什么执行计划突然变化检查数据量是否发生显著变化检查统计信息是否被更新检查OceanBase版本是否升级检查参数配置是否有变更检查是否有新增或删除的索引8.3 如何解决索引扫描反而比全表扫描慢检查是否是分布式环境下的跨节点访问检查索引的选择性是否太低检查是否是覆盖索引避免回表考虑使用HINT强制全表扫描评估是否需要调整索引类型或结构8.4 如何识别冗余索引使用SHOW INDEX命令查看现有索引SHOW INDEX FROM orders;查找前缀相同的索引如idx_a和idx_a_b查找功能重复的索引如idx_a_b和idx_b_a通过性能视图确认索引使用频率在测试环境删除可疑索引并验证性能9. 性能优化工具箱9.1 OceanBase内置工具SQL审计记录SQL执行详情SELECT * FROM __all_virtual_sql_audit;性能视图监控数据库状态SELECT * FROM __all_virtual_sysstat;优化器跟踪分析优化器决策SET OPTIMIZER_TRACEenabledon;执行计划管理固定执行计划CREATE OUTLINE ln_order_query ON SELECT/* INDEX(orders idx_user_status)*/ * FROM orders WHERE user_id?;9.2 外部工具推荐OBClientOceanBase官方命令行工具支持语法高亮、自动补全内置性能诊断命令OCPOceanBase云平台可视化性能监控SQL诊断和优化建议ArcherySQL审核平台SQL审核与优化建议执行计划可视化PrometheusGrafana监控告警自定义指标采集可视化仪表盘9.3 自制诊断脚本分享以下是我常用的一个诊断脚本用于快速识别未使用索引的SQLSELECT sql_id, query_sql, elapsed_time, execute_time, row_cache_hit, block_cache_hit, plan_type FROM __all_virtual_sql_audit WHERE plan_type LOCAL -- 全表扫描 AND elapsed_time 1000000 -- 大于1秒 AND request_time DATE_SUB(NOW(), INTERVAL 1 HOUR) ORDER BY elapsed_time DESC LIMIT 20;这个脚本可以帮助我们快速定位最近1小时内执行的全表扫描慢查询是性能优化的起点。10. 从简单到专业的进阶路径10.1 新手阶段基础确认对于刚开始使用OceanBase的开发者可以按照以下简单步骤确认索引使用情况使用EXPLAIN查看执行计划确认OPERATOR是否为INDEX SCAN确认NAME是否显示索引名称如果没有使用索引检查查询条件是否符合基本规则10.2 中级阶段深入分析随着经验积累可以开展更专业的分析使用EXPLAIN EXTENDED获取详细信息分析优化器成本估算检查统计信息准确性考虑查询重写或索引调整10.3 专家阶段全面优化达到专家水平后可以实施全面的优化策略设计符合业务特点的索引策略使用Outline固定关键查询的执行计划实施SQL审核流程预防性能问题建立性能基准和监控体系参与OceanBase参数调优和架构设计在我自己的成长过程中最大的体会是索引优化不是孤立的数据库技巧而是需要结合业务特点、数据特性和系统架构的综合能力。一个专业的OceanBase使用者应该能够预见性地设计索引策略而不仅仅是在问题出现后进行补救。