MySQL中主要有两种索引,BTree索引和hash索引。默认情况下innodb和myisam都是使用的Btree索引,memory表使用的是hash索引。hash索引的查询复杂度为o(1)。也就是说hash索引查询速度比Btree快。但是hash索引由于是随机散列函数,数据会被放在不连续的地方,所以无法利用hash索引快速的查询某个区间的值。hash索引无法对排序进行优化,在涉及排序的查询时,btree索引的速度远远大于hash索引。hash索引如果对”nihao”建立了索引,那么在查找”ni”时,就无法利用到索引。而btree却可以,因为在底层,btree是一棵平衡二叉树,查询时可以利用到左前缀。
。
比如select * from table_test where name=”zzz” and age=”17”,在这个句子中,要么name的索引发挥作用,要么age的索引发挥作用,而不会两者都发挥作用。
多列复合索引的使用要满足左前缀规则。比如我们有一个复合索引index(x,y,z)。我们再查询时使用了以下几种方式查询:
1.select * from table_test where x=1 and y=2 and z=3
2.select * from table_test where x=1 and y>2 and z=3
3.select * from table_test where x=1 and y=2 and z>3
4.select * from table_test where x=1 and y like “%abc” and z=3
第一条可以使用到x列索引,y列索引,z列索引。
第二条可以使用到x列索引,y列索引,无法使用z列索引。
第三条可以使用到x列索引,y列索引,z列索引。
第四条可以使用到x列索引,y列索引,无法使用到z列索引。
所谓左前缀使用规则,是指在使用一个复合多列索引的时候,从左往右看过去,没有发生过截断。发生截断是指该复合索引在使用索引是,无法精确的定位到一条记录,而是定位到一部分记录。
myisam中,所有的索引都不真正存储数据,而是用一个指针指向数据真正的物理存放地址。每次使用索引查询数据时,先使用索引查询,然后再根据索引指针到物理存储中取出数据。
innodb中,主索引(primary key)文件上,直接存放了真正的数据信息。其他的索引,通过指针指向主索引的位置。
像innodb这样,索引和数据存放再一起的索引,我们称之为聚簇索引。在innodb中,如果没有主键,会unique索引会成为主键,如果没有unique索引,那么系统会自己生成rowid当作主键。
聚簇索引的优点在于查询数据时,索引和数据在一起,省去了通过指针到磁盘上寻找数据的过程(即不用回行)。缺点在于,因为数据和索引存放在一起,如果存入的主键不规则,会导致底层的二叉树频繁的调整树结构(页分裂),导致插入数据速度降低。因此聚簇索引的主键值,应尽量是连续增长的值,而不是要是随机值, 不要用随机字符串或UUID,否则会造成大量的页分裂与页移动。
索引覆盖是指如果查询的列刚好建立了索引,那么查询只需要再索引文件上进行,而不用再回行取数据。这样可以大大提高查询速度。这称之为索引覆盖。理想的索引一般建立在查询频率非常高的字段上面,尽量能覆盖常用的查询字段。
索引长度直接影响索引文件的大小,影响增删改的速度,并间接影响查询速度。建立的索引越长,重复度越低,区分度越高,索引效果越好,但是增删改变慢,影响查询速度。
建立的索引越短,重复度越高,区分度越低,查询效果越不好。
为了在区分度和长度之间取得一个平衡,我们可以使用sql语句来测试区分度和索引长度之间的关系。假如我们要在word上建立索引,可以通过下面的sql语句来测试区分度。
select count(distinct left(word,n))/count(*) from dict
将n从1依次增大,然后查看区分度,最后在长度和区分度之间做出权衡。
有时候数据库中存放的数据前缀相似,比如网址。http://www.baidu.com,http://www.sina.com中,
前11个字符都是相同的,在这种情况下,为了达到较高的区分度,需要建立的索引长度过长,一个好的解决办法是在存储网址的时候,倒序存储。另一个办法就是使用伪hash。使用伪hash时,我们需要在数据表中多存放一个字段(空间换时间)。还拿这个例子来说,我们可以定义一个hash函数,这个hash函数可以将网址映射成一串数字。存储的时候,先用hash函数来计算出网址的哈希值,再用哈希值来查找。在数据表中,建立索引的时候,不在网址字段上建立,而在哈希字段上建立。由于哈希字段是整数类型,因此索引只需要4个字节的长度。
举个例子,我们使用以下的sql语句来查询:
select * from table_test where title like "%today%"
这条sql语句在查询时无法利用索引,即时在today字段上我们建立了索引 。我们改写这个sql语句如下:
select a.* from table_test as a inner join (select id from table_test where title like "%today%") as b on a.id=b.id
这两个sql语句具有相同的效果。唯一不同之处在于,第二个语句先把符合条件的id统一查找出来,然后再通过id去查找。第一个语句是扫描全表对比是否符合where子句的条件,如果符合就用id去回行取数据,边查询边取数据。第二种方法实际上是将回行取数据的过程给延后了。但是却可以提高效率。这称之为延迟查询。
如果经常需要对某个字段进行排序,就可以将这个字建立索引。因为这这样可以避免file sort,索引本身是有序的。