简单索引是基于一个列或者函数创建的索引,与之相对应的是复合索引。复合索引也叫组合索引,即索引包含两个或者更多的列。GBase 8t 的复合索引最多支持 16 个索引列,列的总和大小要小于 390 bytes。
复合索引示例如下:
CREATE INDEX ix_items ON items(stock_num,manu_code);
如图所示是该复合索引的示例图。
采用复合索引可以减少索引的个数,增强索引的唯一性,同时可以提高基于多列组合的连接查询性能。例如,索引 ix_itmes 可以提高如下查询语句的性能:
SELECT * FROM items WHERE stock_num=2 and manu_code=’HSK’;
同时, 也可以使用到该索引进行如下查询:
SELECT * FROM items WHERE stock_num=2;
在创建复合索引时,我们需要考虑列的顺序,应该是从最常用的列到最不常用的列的顺序,从列的区分度由高到低的顺序。
(1)需要考虑在应用中使用的 SQL 语句的情况以及哪些 谓词 (也就是 where 条件使用的列)出现的次数最多
(2)需要考虑列的区分度,列的区分度即列中所有不同值的数量。一般把区分度高的列放在前面,把区分度低的列放在后面。假设有 A、B 两列,A 的区分度为 100000,而 B的区分度为 10000,那么不考虑其他因素,建议在创建复合索引时使用(A,B)。
在如上两个条件出现冲突的情况下,需要考虑应用中所偏重的查询 SQL 的情况以及实际的测试性能结果,来确定索引列的顺序。如上例中,A 的区分度为 100000,B 的区分度为 10000,假设有 50%的 SQL 语句只使用了 B 列的查询,40%的使用了 A、B 列,那么建创建为(B,A)顺序。此时,若应用对只查询 A 列的性能要求较高,那么需要考虑在A 上创建一个单一索引。
在区分度相差不大的情况下,需要根据实际测试结果来确定索引列的顺序。
下面看看多个单一索引和复合索引的对比情况。我们从上面的内容中已经知道了复合索引的好处,但是复合索引的多个索引列会增加索引列的长度,导致索引层次过高。所以在创建复合索引时,需要考虑记录量非常大时,索引树的高度会由于复合索引的多个列变高,从而导致索引性能下降的情况。故在某些特定的情况下,还需要考虑是否创建为单一索引,避免复合索引层数过高带来的性能影响。我们通过下面来自某业务系统的实例来理解如何选择单一索引和复合索引。
表结构如下,包含近百个字段,这里其他字段省略。该表有超过 6 亿条的记录。
- create table epr_patient _record(
-
- patient_key char(8) not null ,
-
- sub_category1 varchar(100),
-
- entity_id integer,
-
- case_no char(8),
-
- ... );
表在优化前包含如下复合索引:
- create index idx_record on
-
- epr_patient_record(patient_key,sub_category1,entity_id);
在业务系统中有如下 SQL 语句,每天执行上万次:
- Select * from epr_patient_record
-
- where patient_key=? And sub_category1=? And entity_id =?;
该 SQL 语句可以正确地使用到在上面创建的复合索引(patient_key, sub_category1, entity_id)中,由于表的记录数非常大,该 SQL 需要运行 1.10 秒。
优化分析如下。
(1)索引的层次及占用空间。通过查询 GBase 8t 系统表发现索引 idx_record 占用了24GB 的空间,索引层次为 6 层。
(2)索引字段区分度。通过查询,字段 patient_key 的不同值为 4225352,而 sub_category1为 10832,entity_id 仅为 1029。
(3)分析如下两个查询语句的返回结果,第一个 SQL 返回 150 条记录,第二个 SQL返回 160 条记录优化措施如下。
- select count(*) from epr_patient_record where patient_key=’ACD2012’ ;
-
- select count(*) from epr_patient_record
-
- where patient_key=’ACD2012’ and sub_category1=’12kuyk’ and entity_id=1231;
通过上述分析可知复合索引 idx_record 的效率并不好,索引层次过高且占用过大的存储空间。
SQL 语句中最重要的一个过滤条件 patient_key 基本决定了查询速度,而该案例创建的复合索引后面的两个字段只起到了很小的作用,从上面两个 SQL 语句运行的结果可以看出,后面的两个条件只是让结果集从 160 条减少到 150 条。而利用如上复合索引 idx_record进行查询时,首先需要从磁盘读取索引的数据,然后进行高层次索引的搜索,再读取记录。
在此过程中,索引处理过程消耗了过多的时间。
创建一个单一索引取代复合索引:
create index idx_record_s on epr_patient_record(patient_key);
该索引的占用空间仅为 10GB,索引层次也从 6 层减少为 5 层。
再次测试如下查询语句的效率,只需消耗 0.8 秒。
- Select * from epr_patient_record
-
- where patient_key=? And sub_category1=? And entity_id =?;
使用单一索引 idx_record_s 时,数据库首先通过索引查找到满足条件的 160 条记录,然后再通过后面两个条件过滤为 150 条,在查询过程中,索引可以快速地从 6 亿记录中找到 160 条记录。
故在使用复合索引时,需要考虑索引占用空间、索引层次,特别是字段的区分度。否则会出现复合索引性能比单一索引性能低的情况。