数据库不同,其底层实现多少有些差异,所以在设计数据库前需要先了解下数据库的原理和设计实现,这些往往也会对索引的设计产生影响,本章笔者会列出一些比较常见的因素供读者参考。
数据库管理系统对索引的限制
索引列的数量
能够复制到索引上的列的个数上限在16至64之间,但合理的建议是复合索引中最多使用5列。
索引列的总长度
复制到索引的列的总长度存在一个上限,该上限的值取决于数据库管理系统。另一个实际的限制是索引页的大小。如果每个叶子页的空闲空间不够存放两个索引行,那么叶子页的分裂可能在重整后不久就又开始了。如果插入操作并非插入至索引末端,那么比叶子页的长度大20%(对于4KB大小的页而言是800个字节,对于8KB大小的页而言是1600字节)的索引行可能意味着频繁的索引重整。
变长列
如果数据库管理系统将变长的列按照其实际长度紧挨着填满索引,那么一个宽索引中的索引行可能很容易就超出限制了。由于这个原因,基于VARCHAR列的宽索引不常被使用,如果当列被复制到索引时仍保持变长,那么这类数据库的索引调优空间就被根本性地提升了。
单表索引数量上限
在单表索引数量限制方面,许多数据库产品要么没有上限,要么上限太高以至于无关紧要。随着索引数量的增加,访问路径选择过程所花费的时间将会增加,不过这一花费只有在每次执行都需进行一次路径成本估算的情况下才会变得显著。
索引大小上限
典型的索引大小上限为几GB,而且这一上限正在持续增大。就像大表一样,大索引通常是分区的,这样能够使执行维护程序的成本最小化,并且能将索引分散到多个磁盘驱动器或RAD组上。
索引锁定
从更新的时间点到提交的时间点内,如果数据库管理系统给一个索引页或者一个索引页的一部分(如一个子页)加了锁,那么该索引页或子页很可能会成为瓶颈,因为插入操作将会变为顺序的。有两种锁定类型:
- 索页缓冲池:一个页只有在读取或修改期间才会被加上门锁,在当前的处理器条件下耗费时间不到一微秒。
- 数据完整性:是通过对索引行所指向的表页或表行加上普通锁来保证的(仅对数据上锁)。当程序修改一个表行或表页,这些锁一直不会释放,直至修改被提交。
数据库索引选项
索引行压缩
有些数据库支持,有些不太支持。如果被索引的字段的值是固定的(典型的比如空字符串或者0),那么就可以避免创建索引段或者索引行。这一选项对于那些为了发现异常情况而创建的索引很有用,不是为了节省磁盘空间,而是为了减少不必要的索引维护代价。一种比较常见的作法是的自己实现索引行压缩的方法是,使用触发器来维护一个小型的类索引表。如果DBMS不支持,那么为了达到减少维护索引代价的目的,我们需要建一张独立的类索引表用于存储源表记录的主键。
索引键以外的其他索引列
索引键决定了这一索引行在索引结构中的位置。当索引键被修改后,DBMS会删除原来的索引行,并将其插入到新的位置上。在最差情况下,索引行会被移动到其他的叶子页上。DB2 for LUW允许将CREATE INDEX语句中的列分为两组:索引键列和非索引键列(CREATE INDEX中的NCLUDE选项)。举个例子,在索引(A,B,C,D)中,索引键列可能是(A,B),而(C,D)是非索引键列。于是,更新C、D列的值并不会移动索引行的位置。这样或许能为每一个更新的行节省一次磁盘驱动器的随机读取(10s)。
区分索引键列的另一个好处是减少非叶子页的数量:非叶子页只会保存索引键列。这样一来,将非叶子页缓存在内存中所需的数据库缓冲池的空间也会更少。如果DBMS不支持非键值索引列(那么就不能将其他列添加到主键索引中了,因为数据库管理系统只支持整个索引列的唯一性。在这种情况下,只能创建一个额外的索引。
唯一约束
索引可以被用来约束主键的唯一性。这可以通过引入PRIMARY KEY约束来做到。当一个候选键必须保证唯一时,可以使用UNIQUE约束连同以候选键作为前导列的索引来确保唯一性。
从不同的方向扫描数据库索引
如果DBMS能够逆向扫描索引,那么从不同的方向扫描就可以在同一个索引上实现了,不再需要通过排序来完成。否则,就需要创建两个索引来避免排序操作。比如db2中有allow reverse scans选项。
索引键截断
索引键截断是指在非叶子页上DBMS只保存了部分索引键列,这部分索引键能够决定下一层级的索引节点,这样能够有效地减少非叶子页的数量。
基于函数的索引
假设CUST表中CNAME列的值是大小写混合的,但SELECT查询语句的绑定变量传入值是大写的。比较常见的解决方案是在CUST表存储两份CNAME的值,一个是大小写混合的原始值,一个是大写值。然后在后面的这个列上建立一个索引,这两列的值必须保持同步,当谓词包含了相同的函数时,优化器就会考虑使用这个基于函数的索引。这样就避免了在CUST表上创建冗余列的需要。