Mysql索引那些事

索引的分类

从存储结构上来划分:

BTree索引(B-Tree或B+Tree索引),Hash索引,full-index全文索引,R-Tree索引。

这里所描述的是索引存储时保存的形式

从应用层次来分:

普通索引,唯一索引,复合索引

  • 普通索引:即一个索引只包含单个列,一个表可以有多个单列索引

  • 唯一索引:索引列的值必须唯一,但允许有空值。如果能确定某个数据列只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该用关键字 UNIQUE 把它定义为一个唯一性索引。
    创建唯一性索引的目的往往不是为了提高访问速度,而是为了避免数据出现重复。

  • 复合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并

根据数据的物理顺序与键值的逻辑(索引)顺序关系:

聚集索引,非聚集索引

  • 聚簇索引(聚集索引):并不是一种单独的索引类型,而是一种数据存储方式。具体细节取决于不同的实现,InnoDB的聚簇索引其实就是在同一个结构中保存了B-Tree索引(技术上来说是B+Tree)和数据行。

  • 非聚簇索引:不是聚簇索引,就是非聚簇索引


存储结构划分

如大家所知道的,Mysql目前主要有以下几种索引类型:FULLTEXT,HASH,BTREE,RTREE。

那么,这几种索引有什么功能和性能上的不同呢?


FULLTEXT(全文检索)

目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用CREATE INDEX创建FULLTEXT索引,要比先为一张表建立FULLTEXT然后再将数据写入的速度快很多。

全文索引并不是和MyISAM一起诞生的,它的出现是为了解决WHERE name LIKE “%word%"这类针对文本的模糊查询效率较低的问题。在没有全文索引之前,这样一个查询语句是要进行遍历数据表操作的,可见,在数据量较大时是极其的耗时的,如果没有异步IO处理,进程将被挟持,很浪费时间,当然这里不对异步IO作进一步讲解,想了解的童鞋,自行谷哥。

全文索引的使用方法并不复杂:

创建

ALTER TABLE table ADD INDEXFULLINDEXUSING FULLTEXT(cname1[,cname2…]);

使用

SELECT * FROM table WHERE MATCH(cname1[,cname2…]) AGAINST ('word' MODE );

其中, MODE为搜寻方式,可选值如下

  • IN BOOLEAN MODE

布尔模式,允许word里含一些特殊字符用于标记一些具体的要求,如+表示一定要有,-表示一定没有,*表示通用匹配符,是不是想起了正则,类似吧;

  • IN NATURAL LANGUAGE MODE

自然语言模式,就是简单的单词匹配;

  • IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION / WITH QUERY EXPANSION

对搜索引擎稍微有点了解的同学,肯定知道分词这个概念,FULLTEXT索引也是按照分词原理建立索引的。西文中,大部分为字母文字,分词可以很方便的按照空格进行分割。但很明显,中文不能按照这种方式进行分词。那又怎么办呢?这个向大家介绍一个Mysql的中文分词插件**Mysqlcft**,有了它,就可以对中文进行分词,当然还有其他的分词插件可以使用。


HASH

**Hash**这个词,可以说,自打我们开始码的那一天起,就开始不停地见到和使用到了。其实,hash就是一种(key=>value)形式的键值对,如数学中的函数映射,允许多个key对应相同的value,但不允许一个key对应多个value。正是由于这个特性,hash很适合做索引,为某一列或几列建立hash索引,就会利用这一列或几列的值通过一定的算法计算出一个hash值,对应一行或几行数据(这里在概念上和函数映射有区别,不要混淆)。在java语言中,每个类都有自己的hashcode()方法,没有显示定义的都继承自object类,该方法使得每一个对象都是唯一的,在进行对象间equal比较,和序列化传输中起到了很重要的作用。hash的生成方法有很多种,足可以保证hash码的唯一性,例如在MongoDB中,每一个document都有系统为其生成的唯一的objectID(包含时间戳,主机散列值,进程PID,和自增ID)也是一种hash的表现。额,我好像扯远了-_-!

  • Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。

由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。

  • Hash 索引无法被用来避免数据的排序操作。

由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;

  • Hash 索引不能利用部分索引键查询。

对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。

  • Hash 索引在任何时候都不能避免表扫描。

前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。

  • Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。

对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。

稍作补充,讲一下HASH索引的过程,顺便解释下上面的第4,5条:

当我们为某一列或某几列建立hash索引时(目前就只有MEMORY引擎显式地支持这种索引),会在硬盘上生成类似如下的文件:

hash值 存储地址
1db54bc745a1 77#45b5
4bca452157d4 76#4556,77#45cc…

hash值即为通过特定算法由指定列数据计算出来,磁盘地址即为所在数据行存储在硬盘上的地址(也有可能是其他存储地址,其实MEMORY会将hash表导入内存)。

这样,当我们进行WHERE age = 18 时,会将18通过相同的算法计算出一个hash值==>在hash表中找到对应的储存地址==>根据存储地址取得数据。

所以,每次查询时都要遍历hash表,直到找到对应的hash值,如(4),数据量大了之后,hash表也会变得庞大起来,性能下降,遍历耗时增加,如(5)。


BTREE

BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中,相信学过数据结构的童鞋都对当初学习二叉树这种数据结构的经历记忆犹新,反正愚安我当时为了软考可是被这玩意儿好好地折腾了一番,不过那次考试好像没怎么考这个。如二叉树一样,每次查询都是从树的入口root开始,依次遍历node,获取leaf。

BTREE在MyISAM里的形式和Innodb稍有不同

在 Innodb里,有两种形态:

  • primary key(主键索引),其leaf node里存放的是数据,而且不仅存放了索引键的数据,还存放了其他字段的数据。

  • secondary index(二次索引),其leaf node和普通的BTREE差不多,只是还存放了指向主键的信息.

而在MyISAM里,主键和其他的并没有太大区别。不过和Innodb不太一样的地方是在MyISAM里,leaf node里存放的不是主键的信息,而是指向数据文件里的对应数据行的信息.


RTREE

RTREE在mysql很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。

相对于BTREE,RTREE的优势在于范围查找.


数据的物理顺序与键值的逻辑(索引)顺序关系分类

创建一个名为user的表,其包括id,name,age,sex等字段信息。此外,id为主键聚簇索引,idx_name为非聚簇索引。

CREATE TABLE `user` (  `id` varchar(10) NOT NULL DEFAULT '',  `name` varchar(10) DEFAULT NULL,  `age` int(11) DEFAULT NULL,  `sex` varchar(10) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_name` (`name`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我们将其设置10条数据,便于下面的索引的理解。

INSERT INTO `user` VALUES ('1', 'andy', '20', '女');INSERT INTO `user` VALUES ('10', 'baby', '12', '女');INSERT INTO `user` VALUES ('2', 'kat', '12', '女');INSERT INTO `user` VALUES ('3', 'lili', '20', '男');INSERT INTO `user` VALUES ('4', 'lucy', '22', '女');INSERT INTO `user` VALUES ('5', 'bill', '20', '男');INSERT INTO `user` VALUES ('6', 'zoe', '20', '男');INSERT INTO `user` VALUES ('7', 'hay', '20', '女');INSERT INTO `user` VALUES ('8', 'tony', '20', '男');INSERT INTO `user` VALUES ('9', 'rose', '21', '男');

聚簇索引(主键索引)

image

它包含两个特点:

1.使用记录主键值的大小来进行记录和页的排序。

页内的记录是按照主键的大小顺序排成一个单项链表。

各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。

2.叶子节点存储的是完整的用户记录。

注:聚簇索引不需要我们显式的创建,他是由InnoDB存储引擎自动为我们创建的。如果没有主键,其也会默认创建一个。

非聚簇索引(二级索引)

上面的聚簇索引只能在搜索条件是主键时才能发挥作用,因为聚簇索引可以根据主键进行排序的。如果搜索条件是name,在刚才的聚簇索引上,我们可能遍历,挨个找到符合条件的记录,但是,这样真的是太蠢了,MySQL不会这样做的。

如果我们想让搜索条件是name的时候,也能使用索引,那可以多创建一个基于name的二叉树。如下图

image

它与聚簇索引的不同:

1.叶子节点内部使用name字段排序,叶子节点之间也是使用name字段排序。

2.叶子节点不再是完整的数据记录,而是name和主键值。

为什么不再是完整信息?

MySQL只让聚簇索引的叶子节点存放完整的记录信息,因为如果有好几个非聚簇索引,他们的叶子节点也存放完整的记录绩效,那就不浪费空间啦。

如果我搜索条件是基于name,需要查询所有字段的信息,那查询过程是啥?

  • 根据查询条件,采用name的非聚簇索引,先定位到该非聚簇索引某些记录行。

  • 根据记录行找到相应的id,再根据id到聚簇索引中找到相关记录。这个过程叫做回表。


MySQL数据库索引优化策略

索引列上不能使用表达式或者函数

例子:select ...... from product where to_days(out_date) - to_days(current_data)<=30 to_days就是使用了函数,out_date就是索引列
优化后:select ...... from product where out_date<=data_add(current_data,interval 30 day) 这样对out_date索引列就没有使用函数

mysql支持对字符串的前缀建立索引,前缀索引和索引列的选择性

create index index_name on table(col_name(n));
索引的选择性是不重复的索引值和表的记录数的比值

联合索引,如何选择索引的顺序

经常会被使用到的列优先
选择性高的列优先
宽度小的列优先

覆盖索引

索引覆盖是指 如果查询的列恰好是索引的一部分,那么查询只需要在索引文件上进行,不需要回行到磁盘再找数据,这种查询速度非常快,称为”索引覆盖”
优点:

  • 优化缓存,减少磁盘IO操作
  • 可以减少随机IO,变随机IO操作变为顺序IO操作
  • 可以避免对Innodb主键索引的二次查询
  • 可以避免MyISAM表进行系统调用
    无法使用覆盖索引的场景:
  • 不是所有的存储引擎都支持覆盖索引
  • 查询中使用了太多的列
  • 使用了双%号的like查询

索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

索引列排序

MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

使用短索引

对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
CREATE INDEX index_name ON table(column(10 or 20));

like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

不要在列上进行运算

select * from users where YEAR(adddate)<2007
将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:
select * from users where adddate<’2007-01-01′
关于这一点可以围观:一个单引号引发的MYSQL性能损失。

innodb的主索引文件上 直接存放该行数据,称为聚簇索引,次索引指向对主键的引用

myisam中, 主索引和次索引,都指向物理行(磁盘位置).
注意:对innodb来说
1、主键索引既存储索引值,又在叶子中存储行的数据
2、如果没有主键, 则会Unique key做主键
3、如果没有unique,则系统生成一个内部的rowid做主键.
4、像innodb中,主键的索引结构中,既存储了主键值,又存储了行数据,这种结构称为”聚簇索引”
聚簇索引
优势:根据主键查询条目比较少时,不用回行(数据就在主键节点下)
劣势:如果碰到不规则数据插入时,造成频繁的页分裂.
高性能索引策略:
对于innodb而言,因为节点下有数据文件,因此节点的分裂将会比较慢
对于innodb的主键,尽量用整型。而且是递增的整型
如果是无规律的数据,将会产生的页的分裂,影响速度

最后说一下:MySQL只对一下操作符才使用索引:<、<=、=、>、>=、between、in以及某些时候的like(不以通配符%或_开头的情形)。而理论上每张表里面最多可创建16个索引。但不要超过4个,超过后会影响效率,因为Mysql执行索引查询时有内置机制会计算哪种方式更快速高效,索引过多这部分工作会影响效率。

你可能感兴趣的:(Mysql索引那些事)