首页 > 代码库 > 12 索引
12 索引
广义上区分索引分为3部分: B-树索引, 位图索引, 索引组织表
位图索引适用于不经常进行更新, 插入和删除的列. 例如数据仓库中的人口统计信息的性别.
一般, 选择相对来说较少的数据时, 适用索引时比较正确的. 典型的基于索引的访问路径通常包含以下3步:
1) 遍历索引树并在将 SQL 语句中的谓语应用到索引列后收集叶子块的行编号
2) 使用行编号从表数据块中获取数据行
3) 在所获取的数据行上应用其余的谓语来得出最终的结果集.
如果在第1步中返回了大量的行编号, 第2步访问表数据块的代价就会更高, 对于来自索引叶子块的每一个行编号, 都需要访问表数据块. 并且这可能会导致多次物理I/O从而引起性能问题. 同时, 从物理上来说1次只能访问1个表数据块, 进一步放大了性能问题.
列的选择
选择进行索引的最佳列对于提高SQL访问性能是非常关键的. 下面这些就是选择最优索引列时需要考虑的内容
- 如果应用代码访问某张表的时候在某一列上使用了等式或范围谓语, 考虑对这一列进行索引就是一个很好的策略. 对于多列索引, 引导列应该是在大多数谓语中被使用的列.
- 考虑谓语的基数以及列的选择度也是很重要的. 例如, 如果某个列只有两个唯一值并且是均匀分布的, 那么这一列可能不适合建立B-树索引, 因为在这一列上使用等式谓语将会获取50%数据行, 另一方面, 如果这个列有两个唯一值但不是均匀分布的, 也就是说有一个值仅在很少的数据行中出现且应用使用这个不常出现的列值来访问表, 这种情况下就最好在这一列上建立索引. 可以使用直方图信息来使得优化器可以根据使用的是常亮或绑定变量来选择最优执行计划.
- 安排好索引中列值的顺序以使其与应用访问模式相适应. 主要还是根据bussiness逻辑需要, 经常在哪列建立谓词 等
- 考虑索引的成本. 插入, 删除以及更新(更新索引列)都需要维护索引.
- 考虑列的长度, 建有所有的列越长, 索引也就越大. 索引的成本就可能会超过由索引带来的全部好处. 较大的索引尺寸页会增加 undo 和 redo 区的大小.
- 在多列索引中, 如果引导列只有很少的唯一值, 考虑将该索引建立位压缩索引, 这些索引的尺寸将会变的更小, 因为压缩索引中不保存重复值.
- 如果谓语在索引列上使用函数, 这一列上的索引就不会被选用.
- 不要在需要大幅修改的列上建立位图索引, 位图索引内部实现更适合于只有很少唯一值的只读列, 如果索引列进行了更新, 位图索引的大小可能会迅速增大. 对于一个位图索引的过多修改还可能会导致大量的锁资源争夺. 位图所有再数据仓库应用中的使用更普遍.
空值问题
在SQL语句中经常会声明 IS NULL 谓语, 空值不存储在某个单独列的索引中, 因此谓语 IS NULL 将不会使用单列索引, 但空值是存储在多列索引中的.
这里在说一下位图索引的结构
使用位图来表示列值的行编号, 另外如果是数据仓库, 需要定期加载数据, 需要在加载之前先删除位图索引, 然后再进行加载.
可以看到, Bitmap 实际上也是一个树结构, 只不过叶子节点存储的不是rowid, 而是 bitmap, 这个bitmap 是一个关于物理块的映射, 而且是多块映射. 所以, 当你修改数据时, 需要全部修改这个映射, 这是很耗时的事情.
索引组织表
表本身被组织为一个索引, 所有列存储在索引树自身中, 使用主键来访问数据行将只会包含索引访问, 访问高效, 因为只要访问索引就可以了, 但是同时, 在常规的表中, 每一行都有一个行编号, 一旦在表中建立了一行数据, 它们就不再移动(可能会有行连接或行迁移, 但行的头部不会移动), 不同的是, IOT数据行存储在索引结构自身中. 因此, 数据行可能由于DML运算而迁移到不同的叶子块中, 从而引起索引叶子块结构的分裂与合并. 即在 IOT 表中没有物理行编号
IOT 适合于具有下面特点的表
- 数据行长度较短的表 数据列较少并且很短的表适合于IOT, 如果数据行长度更长, 索引结构就会过大, 导致比堆表使用更多的资源
- 大多使用主键列进行访问的表 尽管可以在IOT上建立次级索引, 如果主键列较长则次级索引也可能会耗占大量资源.
索引组织表是一种能够有效减少数据行较短且需要进行大量DML和select活动的表中额外索引的特殊结构, 但如果IOT主键列较长, 在其中加入次级索引可能会增大索引大小, 重做区大小以及撤销区大小.
分区索引 (再说)
索引也可以像表那样进行分区.
压缩索引
这种类型的索引更适合于引导列中具有重复值的列.
create index <index name> on <schema.table_name> ( col1 [,col2 … coln])
compress N storage-parameter-clause
compress N 表示压缩索引引导列书目, 例如 在一个3列索引中对两个引导列进行压缩, 可以声明子句 compress 2. 另外, 你只能对引导列进行压缩, 比如 你可以压缩前两列, 不能压缩第1列 和 第3列的组合.
反转键索引
列值 12345 在索引中存储为 54321, 因为列值是按照反向顺序存储的, 连续的列值将会存储在不同的索引叶子块中, 从而避免了右侧增长索引带来的资源争夺问题, 但是在表数据块中, 这些列值还是存储为 12345的.
反转键索引的两个问题
1) 反转键索引的范围扫描不能使用范围运算符 如 between, <> 等, 这是可以理解的, 因为索引范围扫描的基本假设就是列值按照逻辑键升序或降序来存储, 反转键索引由于列值按照反转顺序存储, 没有按照逻辑键的顺序来谓语违反了这个假设. 因此索引范围扫描不适用于反转键索引.
2) 反转键索引可能会人为的增加物理读取的次数, 因为列值被存储在很多个叶子块中, 而这些叶子块可能需要读取到缓冲区缓存来修改块.
所以, 有时, 你可能需要考虑分区索引, 而不是反转键索引.
降序索引
索引默认按照升序存储列值, 但可以通过使用降序索引来切换为降序存储. 如果应用按照某个特定的顺序上百万次的获取数据, 则这类索引是非常有用的. 例如, 取自客户交易表的按照时间顺序逆序排列的客户数据.
个人感觉, 使用的也不多, 因为即便是升序索引, 你需要的是倒序输出, oracle 也会智能的做好优化.
管理问题的解决方案
不可见索引
在某些场合, 你可能需要增加一个索引来对SQL语句的性能进行调优, 但你不太确定索引所带来的负面影响. 不可见索引在以较小的风险来衡量新索引所带来的影响方面非常有用. 一个索引可以加入到数据库中并标记为不可见, 这样优化器就不会选用这个索引, 可以在确定某个索引没有负面影响或对执行计划乜有负面影响后将它标记为可见.
在数据库加入索引后, 你可以在会话中将 optimizer_use_invisible_indexes 参数设置为 true. 这样不会影响应用性能, 然后复查 SQL 语句的执行计划. 另一个用处是, 这种索引有助于在删除不适用的索引时用来降低风险, 从生产数据库中删除不适用的索引并不是令人愉快的经历, 可能之后却意外的认识到删除的索引在一个重要的报表中用到了. 即使在经过充分的分析之后, 也有可能被删除的索引在某个商务过程中是必须的, 而重建索引可能会导致应用停机. 从 oracle 11g 以来, 你可以将索引标记为不可见, 等过了几周之后, 如果没有任何进程要用到这个索引, 则可以较为安全的将其删除. 如果在标记为不可见后发现索引是需要的, 则可以很快的使用一个SQL语句来将索引还原为可见.
12 索引