上一篇《Sql优化器究竟帮你作了哪些工做?》讲解了,sql优化器所作的工做,有很多同窗询问,为什么没有看到如何设计索引,这一篇主要来说解下索引设计中须要遵循的规范。sql
这是数据库设计第三篇:数据库
《DB——数据的读取和存储方式》数据库设计
若是你从网上或者教程中,查询索引设计相关规范,经常会看到一些不知所云的规则和注意事项,这些规则看似颇有道理,但等你应用时,会发现仍然让你一头雾水,不知如何下手,就像咱们提到的知识的边界同样,这些干涩的知识点,对咱们的索引设计并无任何帮助,只能令DB新手望而却步。学习
本篇尝试从《Relational Database index design and the optimizers》书中提到的三星索引,提炼出索引设计的准则和原理,期待可以对索引设计准则起到一个锚定做用,期待你们一块儿探讨学习。优化
从本文,你能够学习到:spa
什么是三星索引.net
三星索引的原理是什么设计
如何设计最佳索引code
CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_name` varchar(100) DEFAULT NULL, `sex` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, `c_date` datetime DEFAULT NULL, PRIMARY KEY (`id`), # 索引 KEY `id_name_sex` (`id`,`user_name`,`sex`), KEY `name_sex_age` (`user_name`,`sex`,`age`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
咱们假设 talbe test
里面有10000行数据,其中 user_name 有100种不一样的值,其FF=1%,咱们查看如下两个sql:
sql1: select user_name,sex,c_date from test where user_name ='test0' order by sex sql2: select user_name,sex,c_date from test
以上咱们能够预测到:
sql1 要比sql2 的查询速度快,由于sql用到了索引
name_sex_age
,而sql2 是全表扫描。
那么真实状况是这样么?
咱们加上查询打印时间,查看下:
select user_name,sex,c_date from test where user_name ='test0' order by sex; 101 rows in set (0.02 sec) select user_name,sex,c_date from test; 10013 rows in set (0.01 sec)
以上咱们能够看到,全表扫描(sql2)的耗时要低于用索引(sql1)的耗时,这是为什么?
sql1 中,虽然用到了索引,但用的是辅助索引name_sex_age
, 再加上 select的时候是全行查询,因此从索引片检索出数据以后,还要去聚簇索引中查询一次,这就产生了大量的随机IO,从上一篇《》中,咱们知道随机IO会占用大量的查询时间的。
咱们不考虑数据库缓存以及磁盘缓存
sql1 查询耗时=10ms(第一次查索引IO)+0.01ms100(索引行,顺序读取)+10010ms(主表随机读) =1010 ms
sql2 查询耗时=10ms(第一次查索引IO)+0.01ms*10000(索引行,顺序读取)=110 ms
那么我该如何设计咱们的索引,以及查询?
对于一个查询而言,一个三星索引,多是其最好的索引。
若是查询使用三星索引,一次查询一般只须要进行一次磁盘随机读以及一次窄索引片的扫描,所以其相应时间一般比使用一个普通索引的响应时间少几个数量级。
那么索引的星级是如何定义?
第一颗星:与查询相关的索引行是相邻的,也就是where后面的等值谓词,能够匹配索引列顺序
第二颗星:索引行的顺序与查询语句需求一致,也就是order by 中的排序和索引顺序是否一致
第三颗星:索引行包含查询语句中全部的列
第一颗星,也是咱们上篇文章所提的匹配列,where后面的谓词和索引列匹配的越多,索引片越窄,最终扫描的数据行也是越小
第二颗星,是避免排序,若是结果集采用现有顺序读取,那么就会避免一次排序,避免提早物化结果集
第三颗星,避免每个索引行查询,都须要去聚簇索引进行一次随机IO查询
上述中第三颗星也是咱们常说的宽索引,它能够保证查询只需访问索引而无需访问表。
好比咱们上述的查询:
select user_name,sex,c_date from test where user_name ='test0' order by sex; 最终须要的三星索引是: (user_name,sex ,c_date)
三星索引是一种理想的索引设计方式,真实状况中每每很难达到,它是一个标尺或者是引路人的方式,要求咱们设计索引时必需要注意的要素。
现实状况每每很难达到三星索引,咱们分状况来进行说明。一般状况下,第三颗星(索引行包含查询语句中全部的列)是最容易达成的,第2、三星每每不可以一块儿达成。
select user_name,sex,age from test where user_name like 'test%' and sex =1 ORDER BY age
(
user_name
,sex
,age
)
三星索引对齐:
第三颗星,知足,select查询的列都在索引列中
第一颗星,知足,user_name 能够匹配到一个索引列列 user_name和一个过滤列sex,他们是相邻的
第二颗星,不知足,user_name 采用了范围匹配,sex 是过滤列,此时age 列并非有序的,不知足 order by age要求
上述咱们看到,此时索引(user_name
,sex
,age
)并不能知足三星索引中的第二颗星(排序),想要知足必须得让 age 列在 user_name 列前面,索引咱们才去方案2
(
sex
,age
,user_name
)
三星索引对齐:
第一颗星,不知足,只能够匹配到sex 索引列,sex索引列是一个宽索引片
第二颗星,知足,等值sex 的状况下,age是有序的
第三颗星,知足,select查询的列都在索引列中
对于索引(sex
,age
,user_name
)咱们能够看到,此时没法知足第一颗星,窄索引片的需求。
以上2个索引,都是没法同时知足三星索引设计中的三个需求的,咱们只能尽力知足2个。而在多数状况下,可以知足2颗星,已经能缩小很大的查询范围了,具体最终要保留那一颗星(排序星 or 窄索引片星),这个就须要看查询者本身的着重点了,没法给出标准答案。
在三星索引中,第三颗星要求(索引行包含查询语句中全部的列),也就是常说的宽索引。
这颗星很好达到,又很难把控。或者说,为了达到这颗星,是否应该把全部的列都设计到索引列里面?
若是每一个索引中包含表的全部列,首先能带来的优点是,匹配到的sql查询更多,查询到的数据不用在进行聚簇索引查询,节省了随机IO。
但劣势也很明显,长列索引会形成频繁的page split 和 page merge ,每一个page可以存入的索引数据更少,并且会有更多的 page split,这对插入和查询来讲,效率都会下降很多。
那咱们应该怎么作,咱们要回顾索引设计的本心,设计索引是为了方便查询的,而不是只为设计索引,脱离了查询的索引设计,并不高效。
宽索引要求咱们尽可能包含多的列在索引中,并非全部,因此除了针对where 后的经常使用谓词,咱们要设计索引之外,咱们还得为经常使用select 后面的谓词,加入到索引,以加快查询速度。