Oracle索引深度解析:从B-Tree原理到实战优化策略
1. 从一次深夜告警说起为什么我们需要深入理解Oracle索引凌晨两点手机突然震动一条数据库慢查询告警弹了出来。登录系统一看一条原本运行不到100毫秒的报表查询突然飙到了20秒以上业务页面直接超时。紧急排查发现是某个核心交易表的索引失效了。这已经不是第一次了但每次处理都像在救火治标不治本。我相信很多DBA和开发兄弟都遇到过类似的场景明明建了索引为什么查询还是慢为什么有时加索引反而性能更差为什么索引会莫名其妙地失效这些问题背后都指向我们对“索引”这个核心机制的认知深度。索引绝不是简单的“建个索引就快了”那么简单。在Oracle数据库里索引是一个极其精密的“数据导航系统”它的结构、类型、使用方式直接决定了海量数据查询的生死时速。理解它你就能让数据库“飞起来”误解它它就可能成为系统中最隐蔽的性能杀手。今天我就结合十多年踩过的坑和填过的坑把Oracle索引从里到外、从原理到实战掰开揉碎了讲清楚。无论你是刚接触Oracle的新手还是遇到过索引疑难杂症的老手这篇文章都会让你对索引有一个全新的、体系化的认识。我们会从最基础的B-Tree索引讲起深入到位图索引、函数索引等高级特性最后落到最关键的“如何用好索引”和“如何避开索引的坑”上。目标只有一个让你不仅能解决眼前的慢查询更能建立起一套预防索引问题的知识体系。2. Oracle索引的核心原理与类型选型2.1 索引的本质一本书的目录理解索引最形象的比喻就是一本书的目录。如果没有目录你想找书中关于“事务隔离级别”的内容只能一页一页地翻这就是全表扫描Full Table Scan。而有了目录你可以快速定位到“事务”相关的章节再精确定位到“隔离级别”所在的页数这就是索引扫描Index Scan。索引就是数据库表中一列或多列值的排序副本并包含了指向表中物理存储位置的指针ROWID。但Oracle的索引比书的目录更智能。它不仅仅是一个静态列表而是一个动态的、平衡的、高效的数据结构主要就是为了解决磁盘I/O这个数据库最大的性能瓶颈。一次磁盘I/O的耗时可能是内存访问的成千上万倍索引的核心价值就是通过极小的计算和内存访问换取尽可能少的磁盘I/O次数。2.2 B-Tree索引Oracle的默认王牌当你创建一个索引如果没有指定类型Oracle默认创建的就是B-Tree索引多路平衡查找树。这是使用最广泛、适应性最强的索引结构。B-Tree索引的结构解剖一棵B-Tree索引由根块Root Block、分支块Branch Block和叶块Leaf Block组成呈倒置的树状结构。根块树的顶端存储了指向下一层分支块的范围信息。分支块中间层进一步细分数据范围指向更细的分支块或最终的叶块。叶块树的底层存储了实际的索引条目Index Entry。每个条目由两部分构成索引键值Key Value你建立索引的那一列或多列的值并且这些值在叶块中是有序排列的默认为升序。ROWID指向表中对应数据行的物理地址。一个ROWID包含了数据文件号、数据块号、行号等信息能让你直接“跳转”到数据行所在位置。为什么是B-TreeB-Tree的精髓在于“平衡”。无论你查询的键值在树的哪一端从根节点到叶节点的访问路径长度即I/O次数基本是相同的且通常非常短对于数千万行的表高度也就在3-4层。这意味着无论数据量多大通过索引定位一条记录的时间复杂度都是O(log n)极其高效。B-Tree索引的适用场景几乎覆盖80%的需求高基数High Cardinality列列中唯一值很多重复值很少如主键、手机号、身份证号。等值查询WHERE user_id 10086。范围查询, , BETWEENWHERE create_time BETWEEN SYSDATE-7 AND SYSDATE。因为叶块有序所以范围查询效率很高。前缀匹配查询LIKE ‘ABC%’WHERE name LIKE ‘张%’。因为‘张’开头的名字在索引中是连续存储的。排序和分组ORDER BY, GROUP BY如果排序或分组的字段有索引数据库可能直接读取有序的索引数据避免昂贵的排序操作。注意虽然B-Tree万能但它并非没有代价。索引本身需要占用额外的存储空间并且会对数据的插入INSERT、更新UPDATE如果更新了索引列和删除DELETE操作带来额外的维护开销因为数据库需要同时维护索引树的结构有序性。这是一个典型的“以空间换时间以写性能换读性能”的权衡。2.3 位图索引为低基数数据而生如果说B-Tree索引是为“个性化”查询设计的那么位图索引就是为“群体性”分析查询设计的。这是Oracle中与B-Tree并列的另一个核心索引类型。位图索引的原理想象一下你有一张员工表其中有一列“部门dept”只有‘销售部’‘技术部’‘行政部’等有限的几个值低基数。位图索引会为每一个唯一的部门值创建一个位图Bitmap。每个位图的长度等于表中的总行数。位图中的每一位bit对应表中的一行。如果该行属于‘销售部’则在‘销售部’位图中对应位置标记为1否则为0。例如行号部门‘销售部’位图‘技术部’位图‘行政部’位图1销售部1002技术部0103销售部1004行政部001位图索引的威力在于位运算。当执行一个查询WHERE dept ‘销售部’ AND gender ‘男’如果dept和gender都建有位图索引Oracle会进行如下操作取出‘销售部’的位图1010假设有4行。取出‘男’的位图1100。对两个位图进行按位与AND操作1010 1100 1000。 结果位图1000表明只有第1行同时满足两个条件。这个计算过程完全在内存中进行速度极快非常适合多条件的组合查询。位图索引的适用场景与重大限制适用场景列的唯一值很少低基数例如性别、状态Y/N、地区、产品类别。主要用于数据仓库、报表系统的复杂即席查询Ad-hoc Query涉及多个低基数列的AND/OR条件过滤。表主要是只读或批量加载很少有单行DML操作。重大限制锁粒度问题这是位图索引最致命的缺点。由于一个位图条目对应多行当更新一条记录时例如将某个员工从‘销售部’调到‘技术部’Oracle需要锁定‘销售部’和‘技术部’两个位图中对应的所有bit位实际上是锁住整个位图片段。这在高并发OLTP在线事务处理环境中会导致严重的锁竞争和死锁极大降低系统吞吐量。因此在OLTP系统中应绝对避免使用位图索引。不适合高基数列否则位图会变得非常稀疏浪费空间且效率降低。2.4 其他索引类型速览除了上述两位“主角”Oracle还提供了一些特定场景下的“特种兵”。函数索引Function-Based Index 索引的键值不是列本身而是对一个或多个列应用函数或表达式后的结果。-- 创建函数索引解决大小写不敏感的查询问题 CREATE INDEX idx_upper_name ON employees(UPPER(last_name)); -- 查询时以下语句就能用到索引 SELECT * FROM employees WHERE UPPER(last_name) ‘SMITH’;用途优化使用了函数或表达式的WHERE条件、JOIN条件。注意查询条件中的表达式必须与索引定义中的表达式完全一致优化器才能识别。反向键索引Reverse Key Index 将索引键值的字节顺序反转后存储。例如键值1234存储为4321。用途专门针对序列生成的主键如ID1,2,3…在B-Tree索引中可能引发的“右侧热点”问题所有新数据都插入到最右边的叶块导致该块竞争激烈。反向键打散了连续值将插入负载分布到多个索引块中。缺点范围查询WHERE id 100将完全无法使用该索引因为它破坏了键值的有序性。降序索引Descending Index 明确指定索引的存储顺序为降序。CREATE INDEX idx_time_desc ON transactions(create_time DESC);用途优化需要按降序进行大量范围扫描或排序的查询如ORDER BY create_time DESC。在Oracle 8i之后普通的B-Tree索引其实已经可以支持双向扫描但在某些复杂ORDER BY混合ASC/DESC的场景下显式创建降序索引可能仍有助于优化器选择更优的计划。3. 索引的创建、管理与维护实战知道了原理和类型接下来就是动手环节。如何正确地创建和维护索引是保证其持续发挥效能的基石。3.1 索引创建语法精讲与参数抉择基础的创建语法大家都会CREATE [UNIQUE | BITMAP] INDEX index_name ON table_name (column1 [ASC|DESC], column2, ...) [TABLESPACE tablespace_name] [STORAGE (...)] [PCTFREE integer] [COMPUTE STATISTICS];这里重点讲几个关键参数的选择它们直接影响索引的性能和空间利用率。TABLESPACE为索引指定独立的表空间。这是一个强烈推荐的最佳实践。将索引和数据表分离到不同的物理磁盘上可以分散I/O压力提升并发性能也便于单独管理和备份。PCTFREE这个参数定义了每个数据块中保留的、用于未来更新的空闲空间百分比。对于索引对于OLTP系统频繁插入建议设置PCTFREE 10-20。为索引键值的更新对于非唯一索引更新可能导致条目在块内移动预留空间减少索引块分裂Index Block Split的频率。块分裂是昂贵的操作会导致性能抖动和空间碎片。对于数据仓库批量加载后只读可以设置PCTFREE 0或一个很小的值以最大化存储密度减少查询时需要扫描的索引块数量。COMPUTE STATISTICS在创建索引的同时收集统计信息。统计信息如索引的聚簇因子、叶块数量、索引高度等是Oracle优化器CBO判断是否使用索引、以及如何使用索引的决定性依据。创建重要索引时务必加上此选项。在生产环境更规范的做法是使用DBMS_STATS包来收集。并行创建PARALLEL对于大表创建索引可以使用PARALLEL子句。CREATE INDEX idx_big ON huge_table(column) PARALLEL 4;这会让Oracle使用4个并行进程来构建索引大幅缩短创建时间。但切记创建完成后最好将其改回NOPARALLEL因为并行度会影响后续使用该索引的查询行为。ALTER INDEX idx_big NOPARALLEL;3.2 复合索引设计列顺序的艺术当你在多个列上创建索引复合索引时列的顺序是至关重要的它决定了索引的可用性。黄金法则最常用于等值过滤的列放在最前面用于范围查询或排序的列放在后面。举例有一张订单表查询模式通常是WHERE customer_id ? AND order_date ?。差的顺序(order_date, customer_id)。因为order_date是范围查询当使用order_date ?条件时索引中customer_id的排列是无序的无法有效利用索引进行快速定位。好的顺序(customer_id, order_date)。首先通过customer_id进行精确筛选然后在同一个customer_id的数据范围内order_date是有序的可以高效地进行范围扫描和排序。索引跳跃扫描Index Skip Scan 在Oracle 9i之后即使查询条件没有包含复合索引的前导列优化器在某些情况下也可能选择使用“索引跳跃扫描”。例如索引是(gender, employee_id)查询是WHERE employee_id 100。优化器可能会先“跳”到gender’M’的部分扫描employee_id再“跳”到gender’F’的部分扫描。但这是一种退而求其次的访问路径效率远不如直接访问前导列。因此设计时仍应优先保证前导列出现在查询条件中。3.3 索引的日常维护与监控索引不是建完就一劳永逸的。随着数据的增删改索引会产生碎片影响性能。查看索引信息-- 查看用户所有索引 SELECT index_name, table_name, uniqueness FROM user_indexes; -- 查看索引的列 SELECT index_name, column_name, column_position FROM user_ind_columns ORDER BY index_name, column_position; -- 查看索引大小和空间使用 SELECT segment_name, bytes/1024/1024 MB, blocks FROM user_segments WHERE segment_type ‘INDEX’;评估索引碎片化程度 一个常用的方法是检查索引的“聚簇因子Clustering Factor”。聚簇因子接近表的数据块数说明数据存储顺序与索引顺序基本一致范围扫描效率高聚簇因子接近表的行数说明数据存储非常随机通过索引范围扫描回表成本会很高。SELECT index_name, clustering_factor FROM user_indexes WHERE table_name ‘YOUR_TABLE’;如果聚簇因子很高且该索引经常用于范围扫描可能需要考虑重组表MOVE或使用索引组织表IOT来改善。重建索引REBUILD 当索引碎片化严重删除很多行后或需要迁移表空间时可以重建索引。ALTER INDEX idx_name REBUILD ONLINE TABLESPACE new_tbs;ONLINE关键字允许在重建过程中对基表进行DML操作这对于7x24系统至关重要。注意重建索引是一个资源密集型操作会占用大量临时表空间和产生重做日志应在业务低峰期进行。合并索引COALESCE 合并索引是比重建更轻量的操作它只是合并同一个索引分支块中的空闲空间不会改变索引的树状结构即不会降低树的高度。ALTER INDEX idx_name COALESCE;适用于轻度碎片化的维护速度快资源消耗小。4. 索引使用策略与性能优化深度解析建好了索引不代表查询就一定会用。优化器如何选择以及索引如何被使用是更复杂的一环。4.1 索引的访问路径优化器如何工作当执行一个SQL时优化器会评估所有可能的访问路径Access Path并选择成本Cost最低的一个。与索引相关的主要路径有索引唯一扫描INDEX UNIQUE SCAN 针对唯一索引如主键的等值查询。效率最高直接定位到唯一一条记录。索引范围扫描INDEX RANGE SCAN 最常用的索引访问方式。适用于等值非唯一、范围、LIKE前缀匹配查询。从索引根部遍历到满足条件的起始叶块然后沿叶块链表顺序扫描直到条件不满足。索引全扫描INDEX FULL SCAN 按索引键值的顺序读取所有叶块。当查询只需要索引列的数据即覆盖索引且需要的结果已排好序时优化器可能选择此路径避免全表扫描和排序。例如SELECT indexed_column FROM table ORDER BY indexed_column。索引快速全扫描INDEX FAST FULL SCAN 类似于全表扫描但扫描的是索引段。它使用多块读取且不保证返回的数据有序。当查询只需要索引列且不需要有序数据时这可能比索引全扫描更快。索引跳跃扫描INDEX SKIP SCAN 如前所述当复合索引的前导列未出现在查询条件中但优化器认为扫描索引仍比全表扫描快时使用。4.2 导致索引失效的常见陷阱与规避很多情况下索引明明存在优化器却弃之不用选择了全表扫描。这就是常说的“索引失效”。以下是一些高频陷阱对索引列使用函数或运算-- 索引失效 SELECT * FROM orders WHERE TO_CHAR(order_date, ‘YYYYMMDD’) ‘20231001’; SELECT * FROM products WHERE price 10 100; -- 解决方案重写查询或创建函数索引 SELECT * FROM orders WHERE order_date TO_DATE(‘20231001’, ‘YYYYMMDD’); CREATE INDEX idx_func_date ON orders(TO_CHAR(order_date, ‘YYYYMMDD’));隐式类型转换-- 假设user_id是VARCHAR2类型但建有索引 SELECT * FROM users WHERE user_id 123; -- Oracle会将列隐式转换为数字导致索引失效 -- 解决方案确保类型匹配 SELECT * FROM users WHERE user_id ‘123’;使用!或NOTSELECT * FROM users WHERE status ! ‘ACTIVE’; -- 大概率全表扫描 -- 解决方案考虑改为IN或OR或评估数据分布。如果非活跃用户很少全表扫描可能更快。 SELECT * FROM users WHERE status IN (‘INACTIVE’, ‘SUSPENDED’);使用LIKE ‘%value%’前导通配符LIKE ‘%ABC’或LIKE ‘%ABC%’无法利用索引的有序性因为不知道从哪里开始比较。只有LIKE ‘ABC%’后缀匹配才能有效使用索引。组合索引未使用前导列 如前所述查询条件必须包含复合索引的第一列否则索引可能无法使用跳跃扫描效率较低。表数据量很小 对于只有几十或几百行的小表优化器认为全表扫描的成本低于索引扫描因为索引扫描需要先读索引块再根据ROWID回表读数据块可能产生额外的I/O。这是合理的优化器行为不是问题。统计信息过时 如果表的统计信息很久没更新优化器基于错误的数据分布例如它认为表只有100行但实际上有1亿行做出了错误判断。定期收集统计信息是保证执行计划稳定的关键。EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname‘SCOTT’, tabname‘EMP’, estimate_percentDBMS_STATS.AUTO_SAMPLE_SIZE, cascadeTRUE);4.3 索引选择性与成本计算优化器决定是否使用索引核心是计算“选择性Selectivity”和“成本Cost”。选择性指满足条件的行数占总行数的比例。比例越低选择性越高索引越有效。选择性 DISTINCT(column) / COUNT(*)。唯一索引的选择性为1最佳。优化器通过统计信息中的NUM_DISTINCT和NUM_ROWS来估算选择性。成本计算简化模型 索引扫描的成本 ≈ 索引树遍历成本 回表成本索引树遍历成本取决于索引高度BLEVEL。回表成本取决于需要回表的行数Cardinality和聚簇因子Clustering Factor。聚簇因子越高回表时需要访问的离散数据块越多成本越高。当优化器估算出通过索引扫描获取数据的成本高于直接全表扫描的成本时它就会放弃索引。这就是为什么有时你“强制”使用索引/* INDEX(table_name index_name) */反而更慢的原因。5. 高级索引策略与实战案例剖析掌握了基础我们来看一些更高级的索引使用策略和真实场景下的决策。5.1 覆盖索引避免回表的性能利器如果一个查询所需要的所有列都包含在索引的键值中那么数据库只需要访问索引就能完成查询无需再回表TABLE ACCESS BY INDEX ROWID。这被称为“覆盖索引Covering Index”或“仅索引扫描Index Only Scan”是性能优化的一大杀器。案例-- 表结构orders(order_id, customer_id, order_date, amount, status) -- 查询经常需要按客户查订单日期和金额 SELECT order_date, amount FROM orders WHERE customer_id 1001; -- 如果只在customer_id上建索引查询需要回表取order_date和amount。 -- 创建覆盖索引 CREATE INDEX idx_cust_date_amt ON orders(customer_id, order_date, amount); -- 现在这个查询只需要扫描idx_cust_date_amt索引的叶块即可得到全部数据性能极大提升。设计建议分析核心查询的SELECT列表将频繁查询的非过滤列如上面的order_date,amount作为“包含列”加到复合索引的后面在Oracle中可以通过函数索引或直接包含非键值列但在标准B-Tree中它们作为键的一部分也能达到覆盖效果。但要注意增加索引列会增大索引体积需权衡利弊。5.2 索引组织表主键查询的终极优化对于以主键访问为主的表可以考虑使用索引组织表Index-Organized Table, IOT。在IOT中表的数据就存储在主键索引的叶块里而不是另外的堆表Heap Table中。这消除了回表操作对于主键查询性能有质的飞跃。创建IOTCREATE TABLE order_items ( order_id NUMBER, line_item_id NUMBER, product_id NUMBER, quantity NUMBER, PRIMARY KEY (order_id, line_item_id) ) ORGANIZATION INDEX; -- 关键语句IOT的优缺点优点主键查询极快。数据按主键有序存储范围查询效率高。节省空间无需存储ROWID。缺点非主键列的访问可能较慢如果未建二级索引。插入性能可能受影响因为要维护严格的排序。表结构修改如增加列比堆表更复杂。适用场景代码查找表、地址表等以主键访问为绝对主导、数据量适中、结构相对稳定的表。5.3 不可见索引与虚拟索引灰度发布与测试利器不可见索引Invisible Index 索引对优化器“不可见”不会被自动用于查询但依然会被DML操作维护。这就像一个“隐身”的索引。CREATE INDEX idx_trial ON table(col) INVISIBLE; -- 或修改现有索引 ALTER INDEX idx_existing INVISIBLE;用途索引灰度发布在大型表上创建新索引可能耗时很长且影响业务。可以先创建为INVISIBLE在后台静默构建完成然后在业务低峰期通过ALTER INDEX ... VISIBLE一键“上线”风险极低。删除索引前的保险怀疑某个索引无用想删除但又怕出错。可以先将其设为INVISIBLE观察一段时间系统运行情况确认无影响后再删除。虚拟索引Virtual Index / No-Volume Index 这是一个“假”索引只在数据字典中有定义不占用任何物理空间也不被维护。需要通过特定事件如_use_nosegment_indexes或Hint才能让优化器在生成执行计划时考虑它。用途纯粹用于测试。在创建一个大索引之前先用虚拟索引模拟看看优化器是否会选择它执行计划成本如何避免创建无用索引浪费资源。但使用虚拟索引需要较高的权限和对隐藏参数的谨慎操作。5.4 分区表的索引策略本地与全局之选对于分区表索引策略变得复杂主要有两种类型本地索引Local Index 每个表分区都有一个独立的、与之对应的索引分区。索引分区与表分区一一对应并且分区键相同。CREATE INDEX idx_local ON partitioned_table(column) LOCAL;优点维护性好当进行分区维护操作如DROP PARTITION,TRUNCATE PARTITION时对应的本地索引分区会自动维护不影响其他分区。高可用性单个分区索引失效不影响其他分区查询。缺点如果查询条件不包含分区键可能需要对所有分区索引进行扫描分区裁剪失效。适用场景OLTP系统维护窗口紧张查询条件通常包含分区键。全局索引Global Index 一个跨越所有表分区的单一索引。索引分区与表分区没有直接对应关系。CREATE INDEX idx_global ON partitioned_table(column) GLOBAL;优点对于不包含分区键的查询可能效率更高因为只需要扫描一个全局索引结构。缺点维护成本高任何分区维护操作尤其是DROP/TRUNCATE PARTITION都可能导致整个全局索引失效除非使用UPDATE GLOBAL INDEXES子句但会影响操作性能。分区裁剪对其无效。适用场景数据仓库中针对非分区键的、高选择性的点查询或者分区键经常变化的场景。选择建议在分区表上优先考虑本地索引除非有非常明确的理由如频繁的非分区键唯一性查询需要使用全局索引。全局索引带来的管理复杂度很高。6. 索引监控、诊断与常见问题排查手册理论最终要服务于排障。这里整理一份实战中索引相关问题的排查清单和工具。6.1 如何判断索引是否被使用查询V$SQL_PLAN或DBA_HIST_SQL_PLAN 这是最直接的方法。找到你关心的SQL语句的执行计划查看其中是否有INDEX相关的操作如INDEX RANGE SCAN。-- 查找特定SQL的执行计划需要SQL_ID SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(‘sql_id’));监控索引使用情况Oracle 10g Oracle可以收集索引的使用次数监控。-- 开启索引监控 ALTER INDEX index_name MONITORING USAGE; -- 运行一段时间业务后查看使用情况 SELECT * FROM V$OBJECT_USAGE WHERE index_name ‘INDEX_NAME’; -- 如果MON为YESUSED为NO说明这段时间内索引未被使用可以考虑删除。 -- 关闭监控 ALTER INDEX index_name NOMONITORING USAGE;6.2 索引碎片化诊断与处理流程症状索引大小持续增长但删除大量数据后空间不释放索引扫描速度变慢。诊断分析索引空间使用ANALYZE INDEX index_name VALIDATE STRUCTURE; SELECT name, height, lf_rows, del_lf_rows, (del_lf_rows / lf_rows) * 100 as pct_deleted FROM index_stats;如果pct_deleted已删除叶行百分比很高如20%且height索引高度没有降低说明碎片严重。检查聚簇因子见4.3节。处理轻度碎片pct_deleted 30%使用ALTER INDEX ... COALESCE;。中度到重度碎片使用ALTER INDEX ... REBUILD ONLINE;。重建可以回收空间降低树高度。因聚簇因子高导致的性能问题考虑重组表ALTER TABLE ... MOVE;然后重建所有索引或评估是否适合改为IOT。6.3 索引竞争与等待事件在高并发OLTP系统中索引可能成为热点引发竞争。enq: TX - index contention最常见的是索引块竞争。对于单调递增的主键索引如序列所有插入都集中在索引树最右侧的叶块形成“右侧热点”。解决方案考虑使用反向键索引或哈希分区全局索引来打散热点。buffer busy waits多个会话同时想访问同一个正在被读入内存或正在被修改的数据块可能是索引根块、分支块或热点叶块。解决方案优化索引结构如反向键增加PCTFREE减少块内竞争或者考虑使用自动段空间管理ASSM的表空间。6.4 一个真实的案例从20秒到0.2秒背景一张日志表有上亿条数据有一个复合索引(create_time, level)。有一个高频查询SELECT * FROM log WHERE level‘ERROR’ AND create_time SYSDATE - 1需要20多秒。排查检查执行计划发现优化器选择了全表扫描。理由是level列的选择性不高只有几个枚举值优化器认为走索引回表成本太高。但开发人员确认一天内ERROR级别的日志其实很少约占总量的0.1%选择性其实很高。问题出在统计信息过时表刚经过一次历史数据清理并插入了大量新数据但统计信息未更新优化器还认为ERROR级别数据很多。解决立即收集该表的统计信息EXEC DBMS_STATS.GATHER_TABLE_STATS(...);再次检查执行计划优化器正确地选择了索引范围扫描查询时间降至0.2秒。教训统计信息是优化器的眼睛。定期的、在数据量发生重大变化后的统计信息收集是稳定数据库性能的基石。可以考虑在大型ETL作业或数据归档作业后加入收集统计信息的步骤。

相关新闻