IX_SEL(Index Selectivity,索引选择率)是Oracle CBO基于成本优化器中的核心指标之一,它反映了通过索引主导列访问表数据行的比例。理解IX_SEL的计算原理,对于分析执行计划、诊断索引效率问题至关重要。

一、IX_SEL的定义

根据Oracle 10053 trace的官方描述:

ix_sel - Index selectivity. Fraction of the table rows accessed by the indexes leading column in order to meet the predicate supplied.

翻译:IX_SEL是满足谓词条件时,通过索引主导列访问的表行占总行数的比例。

关键点:仅索引的主导列(Leading Column)用于计算IX_SEL,而非所有索引列。

二、IX_SEL的计算规则

IX_SEL的计算取决于两个因素:谓词类型和变量可见性。

2.1 变量可见的情况

当使用硬编码值或绑定变量可窥视(Bind Peeking启用)时:

谓词类型 IX_SEL计算公式
等式谓词(=) IX_SEL = 1 / (NDV1 × NDV2 × ...)
开放范围(> 或 <) IX_SEL = (MAX - 代入值) / (MAX - MIN)

2.2 变量不可见的情况

cursor_sharing=FORCE_optim_peek_user_binds=FALSE时,优化器无法窥视绑定变量值:

谓词类型 IX_SEL值 说明
等式谓词(=) 列的Density 使用统计信息中的密度值
开放范围(> 或 <) 0.009 硬编码常量
闭包范围(> AND <) 0.0045 硬编码常量(0.009 / 2)

三、实战示例:计算IX_SEL

示例1:等式谓词的IX_SEL计算

-- 创建复合索引
CREATE INDEX ind_maclean ON sh.sales(prod_id, cust_id, time_id);

-- 收集统计信息
EXEC dbms_stats.gather_table_stats('SH','SALES',cascade=>true,method_opt=>'FOR ALL COLUMNS SIZE 1');

-- 启用10053事件
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';

-- 执行查询
EXPLAIN PLAN FOR 
SELECT * FROM sh.sales WHERE prod_id = 13 AND cust_id = 987;

10053 trace输出:

Column (#1): PROD_ID(NUMBER)
  AvgLen: 4.00 NDV: 72 Nulls: 0 Density: 0.013889

Column (#2): CUST_ID(NUMBER)
  AvgLen: 5.00 NDV: 5828 Nulls: 0 Density: 1.7159e-04

Access Path: index (RangeScan)
  Index: IND_MACLEAN
  ix_sel: 2.3831e-06  ix_sel_with_filters: 2.3831e-06

验证计算:

IX_SEL = 1 / (NDV_prod_id × NDV_cust_id)
       = 1 / (72 × 5828)
       = 1 / 419616
       = 2.3831e-06 ✓

示例2:开放范围谓词(变量不可见)

-- 禁用绑定变量窥视
ALTER SYSTEM SET "_optim_peek_user_binds" = FALSE;

-- 使用绑定变量查询
VARIABLE i NUMBER;
SELECT count(*) FROM test WHERE object_id > :i;

10053 trace输出:

Access Path: index (IndexOnly)
  Index: TEST_IDX
  ix_sel: 0.009  ix_sel_with_filters: 0.009

示例3:闭包范围谓词(变量不可见)

VARIABLE i NUMBER;
VARIABLE b NUMBER;
SELECT count(*) FROM test WHERE object_id > :i AND object_id < :b;

10053 trace输出:

Access Path: index (IndexOnly)
  Index: TEST_IDX
  ix_sel: 0.0045  ix_sel_with_filters: 0.0045

四、Oracle源代码中的默认选择率常量

以下是Oracle内核代码中定义的默认选择率常量(kke.h头文件):

/* 默认选择率常量 */

/* 比较操作符的默认选择率 */
#define KKEDSREL  0.05    /* < <= > >= 的默认选择率 */
#define KKEDSEQ   0.01    /* = 的默认选择率 */
#define KKEDSNE   0.05    /* != 的默认选择率 */
#define KKEDSDF   0.05    /* 其他操作符的默认选择率 */

/* 索引列的默认选择率 */
#define KKEDSIRL  0.009   /* 索引列上关系运算的默认选择率 */
#define KKEDSBRL  0.009   /* 索引列上绑定变量关系运算的默认选择率 */
#define KKEDSIEQ  0.004   /* 索引列上等式运算的默认选择率 */

/* 默认的多块读取因子 */
#define KKEDMBR   8       /* 默认多块读取因子 */
#define KKEDMBW   8       /* 默认多块写入因子 */

/* 固定表和远程表的默认值 */
#define KKEDFNR   100.0   /* 固定表默认基数 */
#define KKEDFRL   20      /* 固定表默认行长度 */
#define KKEDDNR   2000.0  /* 远程表默认基数 */
#define KKEDDRL   100     /* 远程表默认行长度 */
#define KKEDDNB   100     /* 默认块数 */
#define KKEDDSC   13.0    /* 默认扫描成本 */

/* 索引的默认值 */
#define KKEDILV   1       /* 默认索引层级 */
#define KKEDILB   25      /* 默认索引叶块数 */
#define KKEDLBK   1       /* 每个键的叶块数 */
#define KKEDDBK   1       /* 每个键的数据块数 */
#define KKEDKEY   100     /* 默认不同键值数 */
#define KKEDCLF   (KKEDDNB*8)  /* 默认聚簇因子 */

/* 其他常量 */
#define KKECRI    1.5     /* 远程表访问成本增加因子 */
#define KKECFSC   1.0     /* 固定表扫描成本 */

关键常量解读

常量名 含义
KKEDSIRL 0.009 索引列上>、<、>=、<=的默认选择率
KKEDSBRL 0.009 索引列上绑定变量范围查询的默认选择率
KKEDSIEQ 0.004 索引列上等式查询的默认选择率
KKEDSREL 0.05 非索引列上范围查询的默认选择率
KKEDSEQ 0.01 非索引列上等式查询的默认选择率

设计原因:Oracle将默认选择率设置得较低,目的是:

  1. 为资源限制器(Resource Limiter)保持较低的成本值
  2. 在KKO(Kernel Query Optimizer)的排列组合截断中保持较低成本

五、IX_SEL与IX_SEL_WITH_FILTERS的区别

在10053 trace中,经常同时看到两个值:

ix_sel: 2.3831e-06  ix_sel_with_filters: 2.3831e-06
指标 说明
ix_sel 仅基于索引主导列计算的选择率
ix_sel_with_filters 考虑了额外过滤条件后的选择率

当查询条件完全匹配索引列时,两者相等。当存在额外的非索引过滤条件时,ix_sel_with_filters可能更小。

六、诊断IX_SEL的方法

方法1:使用10053事件

-- 启用10053事件
ALTER SESSION SET EVENTS '10053 trace name context forever, level 1';

-- 执行查询(EXPLAIN PLAN或实际执行)
EXPLAIN PLAN FOR SELECT ...;

-- 关闭10053事件
ALTER SESSION SET EVENTS '10053 trace name context off';

-- 查找trace文件
SELECT value FROM v$diag_info WHERE name = 'Default Trace File';

方法2:使用SQL Optimizer Trace(11g+)

-- 启用SQL优化器trace
ALTER SESSION SET EVENTS 'trace[rdbms.SQL_Optimizer.*]';

-- 执行查询
SELECT ...;

-- 关闭trace
ALTER SESSION SET EVENTS 'trace[rdbms.SQL_Optimizer.*] off';

在trace文件中查找IX_SEL

# 在trace文件中搜索
grep -i "ix_sel" <trace_file>

七、IX_SEL对执行计划的影响

IX_SEL直接影响索引访问路径的成本计算:

索引访问成本 ≈ blevel + (leaf_blocks × ix_sel) + (clustering_factor × ix_sel)

IX_SEL越小,表示索引的过滤效果越好,索引访问成本越低,优化器越倾向于选择索引访问路径。

常见问题诊断

现象 可能原因 解决方案
IX_SEL过大,不走索引 NDV过低或统计信息过时 重新收集统计信息
IX_SEL恒为0.009 绑定变量不可窥视 检查_optim_peek_user_binds参数
IX_SEL与预期不符 数据分布不均匀 创建直方图

八、总结

IX_SEL是CBO优化器判断索引效率的核心指标,其计算规则如下:

  1. 等式谓词(变量可见)IX_SEL = 1 / (NDV1 × NDV2 × ...)
  2. 范围谓词(变量可见)IX_SEL = (MAX - 值) / (MAX - MIN)
  3. 范围谓词(变量不可见):开放范围固定为0.009,闭包范围固定为0.0045
  4. 等式谓词(变量不可见):使用列的Density值

理解这些规则,可以帮助DBA准确预测优化器的行为,诊断索引选择异常问题。

延伸阅读:了解更多CBO成本计算细节,可参考《Oracle优化器如何计算索引成本》一文。