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将默认选择率设置得较低,目的是:
- 为资源限制器(Resource Limiter)保持较低的成本值
- 在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优化器判断索引效率的核心指标,其计算规则如下:
- 等式谓词(变量可见):
IX_SEL = 1 / (NDV1 × NDV2 × ...) - 范围谓词(变量可见):
IX_SEL = (MAX - 值) / (MAX - MIN) - 范围谓词(变量不可见):开放范围固定为0.009,闭包范围固定为0.0045
- 等式谓词(变量不可见):使用列的Density值
理解这些规则,可以帮助DBA准确预测优化器的行为,诊断索引选择异常问题。
延伸阅读:了解更多CBO成本计算细节,可参考《Oracle优化器如何计算索引成本》一文。