目录
1、聚集索引 和 非聚集索引(辅助索引)
1.1、索引覆盖
1.2、优化器选择索引
1.3、Multi-Range Read 优化
1.4、Index Condition Pushdown (ICP) 优化
2、自适应哈希索引
3、倒排索引
4、全文检索
4.1、全文搜索 NATURAL LANGUAGE 模式
4.2、联合全文索引
4.3、全文检索 BOOLEAN 模式
4.4、扩展检索
5、索引的属性
5.1、Cardinality 属性
1、聚集索引 和 非聚集索引(辅助索引)
聚集索引:每张表只能有一个聚集索引,即以主键作为索引值所创建的结构,本质上是基于 B+树的,非叶子节点只用于存储索引值,叶子节点既存储索引值,也存储索引值对应的该行记录,因此,一张表的所有数据都存储在该 B+ 树的叶子节点上,而且叶子节点之间通过双向链表连接起来,更加适合范围查询。
非聚集索引:没张表可以有多个非聚集索引,也是 B+ 树,非叶子节点仍然只存储索引值,叶子节点既存储索引值,也存储一个指针,这个指针指向对应的聚集索引中的主键,因此,如果根据非聚集索引去查询数据的话,要经过2次查询,第一次是在非聚集索引上查询,找到主键,再拿着主键到聚集索引上找到数据。
1.1、索引覆盖
索引覆盖是一种查询思想,如果我们要查询主键数据,一般我们把 where 条件包含主键,这样会按照聚集索引去查询,直到聚集索引的叶子节点为止。 换一种思想(索引覆盖),非聚集索引的叶子节点不是包含了主键吗?如果我们让 where 条件包含非聚集索引的列去查询,就会按照非聚集索引去查询,直到叶子节点,再取出主键,不是更加方便吗?聚集索引包含了数据,查询时,会有更多的磁盘IO,但是非聚集索引不包含数据,主包含主键,所以磁盘IO少,更加快速。
但是需要注意,使用这种思想时,非聚集索引应该与主键唯一对应,比如 (键1,主键1),(键1,主键2),这样的话,根据非聚集所以查出来就有2个主键,不知道你要哪个。
优化器会对SQL进行分析,选择一个优化器认为好的一种方案去执行SQL,比如查询,它是按照索引覆盖的思想去查呢?还是按照聚集索引去查。
还需注意的是:非聚集索引的存储时离散的,聚集索引的存储时顺序的,这也是优化器考虑的一个方面。
1.2、优化器选择索引
优化器的功能就是对SQL语句进行分析,得出一个它认为最优的执行方案去执行,比如一条查询语句,用这个索引可以完成查询,用另一个索引也能完成查询,但是究竟用哪个呢?这就是优化器要分析的。在大多数时候,优化器都能有最优的分析,但是偶尔也会有分析不对的时候,如果我们看到慢查询日志里有因为优化器的错误分析而导致查询很慢的,可以自己定义用哪个索引查询,强制优化器去选择我们定义的方案。
1、建议优化器用哪个索引 use index(索引名)
比如 select * from student use index(id) where name = 'dan'; 只是建议,优化器在考虑选择的时候,多了一个方案而且,如果优化器认为你建议的方案不好,也是不会采纳的。
2、强制优化器用哪个索引 force index(索引名)
比如 select * from student force index(id) where name = 'dan'; 强制让优化器执行这个方案。
1.3、Multi-Range Read 优化
范围读取优化,这种优化是在非聚集索引上查询的情况下,如果在聚集索引上查询,就没有这一说了。如果我们查询一个范围内的数据,查询出来的是按照非聚集索引的顺序的,对应的主键未必有序(乱序的),非聚集索引上的顺序和主键的顺序不一样,因此,根据主键去聚集索引查询数据时,没有顺序可言,一会儿在这个节点里面,一会儿又在另一个节点里面,一会儿又跳回这个节点里,导致数据页频繁地被换入换出,磁盘IO增多,性能低。
在获取到乱序的主键后,Multi-Rang Read 会将主键存到一个缓冲里,排好序,再按照顺序去根据主键查询聚集索引,聚集索引里的数据存储顺序和主键的顺序一致,所以按照顺序来,一页一页的读取,降低磁盘IO。
optimizer_switch 参数可以设置此优化,里面有很多个详细的参数。默认开启。
下面这个参数用于设置 缓冲大小,满了的话,就先读取查询了之后,再用。
1.4、Index Condition Pushdown (ICP) 优化
以前条件查询时,如果条件里面既包含了索引,又包含非索引,会先按照索引把所有满足索引列条件的记录全查出来,在按照非索引列的条件进行过滤。
ICP优化是指,在按照索引查询时,就直接进行过滤,最终获取到的数据就是最终的数据。
2、自适应哈希索引
InnoDB 引擎下的主键索引是 b+树 索引,b + 树的特点是非叶子节点只存储主键索引值,叶子节点既存储了索引值,还存储了对应的行记录,所以数据都在叶子节点,而 InnoDB 是用页为单位去存储 b + 树的节点,一页存一个节点,故一个节点可以存很多条索引信息,树的高度一般最多也就在 3 ~ 4 层,精准查询的话,会有 3 ~ 4 次查询。
数据库里存储的数据非常多,被访问频繁非常高的数据被称为热点数据,针对热点数据,InnoDB 做了一个性能的提升,就是采用了自适应哈希索引,为热点页建立一个哈希表,哈希表的表项对应着该页的每条记录,当我们精准查询时,会根据查询的条件值计算出哈希,以时间复杂度 O(1) 就能找到哈希项,哈希项又指向了一条记录,即找到了数据。自适应哈希索引说的是 InnoDB 自己内部自动创建的,跟我们用户没有关系,是它内部为了优化而创建的一种结构,判断一个页是否为热点页,可能是个什么算法,如果某个页晋升为热点页,自然会为它创建自适应哈希索引,如果某个页降级了,不再是热点页,就会删除掉对应的哈希表。
优点:提升了查询性能。
缺点:只适合等值查询(=, IN),并发性不行,因为哈希表是有锁机制的,把整个表锁起来(这是旧版本的)。
我们可以设置是否开启自适应哈希索引,innodb_adaptive_hash_index 默认是开启。 innodb_adaptive_hash_index_parts 参数为新版本的,用于将哈希表分为多个子表,这样,可以分别对子表进行加锁,大大提升并发性,默认为 8, 最大为 512。
3、倒排索引
InnoDB 引擎的全文检索需要倒排索引,将我们将某一列设置成 fulltext key ,就可以对这一列进行全文检索,原因是数据库对这一列创建了额外的倒排索引,有两种形式。
第一种形式。索引存储的是 { 词,所在的该条记录标识(主键)}。
第二种形式。索引存储的是 { 词,所在的该条记录标识,词在该条记录中的该字段里的位置 }。
所以在进行全文检索时,是根据这个倒排索引去查询的,找到哪些记录包含了要检索的词,甚至词所处的位置。
全文检索就是基于倒排索引的,这个倒排索引也是非常大的,所以动态维护起来也是不容易的,如果没修改一次,那就要维护一次倒排索引,那还是很难受的。因此,InnoDB 给出了一个全文检索索引缓存,当需要对倒排索引进行修改维护时,将修改先缓存起来,等某个时机(缓存满了等等)再一次性进行更新修改。
我们可以设置全局参数 innodb_ft_cache_size 来设置全文检索索引缓存的大小,越大,当然性能也越好,当然也不宜太大,因为一旦内存宕机,缓存里的数据就没有了,InnoDB 需要更多的时间去回复。
全文索引是有限制要求的:1、每张表只能有一个全文索引。 2、联合全文索引包含的列必须使用相同的字符集和排序规则。 3、不支持没有单词界定符的语言,如 中文,日文,韩文等。
4、全文检索
要想对某个字段使用全文检索,就要为该字段创建全文索引,FULLTEXT KEY。
4.1、全文搜索 NATURAL LANGUAGE 模式
NATURAL LANGUAGE MODEL 是默认的模式(自然语言模型)。
举个例子就明白了,SELECT * FROM student WHERE MATCH(interest) AGAINST('football' IN NATURAL LANGUAGE MODE); 查询student表里兴趣 interest 里面包含有 football 单词的记录。
也可以直接写成, SELECT * FROM student WHERE MATCH(interest) AGAINST('football');
检索出来如果有多条记录,这些记录的顺序是怎么样的呢? 按照一定的相关性,相关性计算规则是:
1、关键词是否出现在文档中。
2、关键词出现在文档中的次数。
3、关键词在索引列中的数量。
4、多少个文档包含指定的关键词
4.2、联合全文索引
不仅可以把一列作为全文索引,而且还可以将多列作为联合全文索引,举个例子你就明白了。student 表,name 字段 和 interest 字段联合设置一个全文索引。
SELECT * FROM `student` WHERE MATCH(interest) AGAINST('football'); 这样运行会报错的, 错误信息:Can't find FULLTEXT index matching the column list。
为什么报错,因为你都将2列作为一个索引了,所以要将2列看成一个整体,不能够再单独地检索一列了。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('kkk football'); 这样就可以,运行结果如下:
再说明一下,AGAINST() 里的内容是以词为单位的,词只包含字母和数字,除此之外的字符都被认为是分割符,比如 kkk football 被认为是两个词,因为中间有一个空格, kkk&football 也被认为是两个词,因为中间有个&字符。AGAINST() 里面的内容没有顺序之分,比如 MATCH(`name`, interest) AGAINST('kkk football'); 这句话的意思是,name 和 interest 字段中只要包含了kkk football 其中的一个关键词都行,name 字段包含了 football 也行,name 字段包含了 kkk 也行,name 字段什么都包含,但是 interest 字段包含了关键词也行。
4.3、全文检索 BOOLEAN 模式
自然语言模式只是单纯检索是否含有关键词,功能单一,BOOLEAN 模式的功能更加丰富。
+ 表示该词必须被包含。
- 表示该词必须不被包含。
(no operator) 表示该词可被包含,也可不被包含,被好含的话,相关性更高。
@distance 表示检索的词之间必须在间隔多少个字符以内。
> 表示出现该词时增加相关性。
< 表示出现该词时降低相关性。
~ 表示出现该词时,相关性直接为负。
* 表示以该词开头的单词。
" 表示短语。"" 扩起来的内容看成一个整体,看成一个词,不可拆分。
举例说明,还是以 student 表为例:
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('+kkk +football' IN BOOLEAN MODE); 这句执行结果为空,因为既包含kkk ,又包含 football 的记录不存在。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('-kkk +football' IN BOOLEAN MODE); 这句执行结果如下,不包含 kkk,包含 football 的记录有两条。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('(no operator)sing dance' IN BOOLEAN MODE); 这句执行结果如下,包含了 sing 的记录相关性更高,排在前面。
至于 @distance 用法,我测试了一下,感觉有点问题,希望知道这个知识点的大佬能够讲解一下,谢谢。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('>sing dance' IN BOOLEAN MODE); 这句执行结果如下,包含了 sing 的记录相关性增加,因此,排在了前面。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('<sing dance' IN BOOLEAN MODE); 结果如下,因为包含 sing 的记录被降低了相关性,因此,排在了后面。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('si*' IN BOOLEAN MODE); 结果如下, si 开头的记录有。 我不建议这样模糊匹配,因为全文检索的模糊查询也会导致全表扫描,这是我自己的理解,如果不正确,请在评论给出正确的原理,非常感谢。
SELECT * FROM `student` WHERE MATCH(`name`, interest) AGAINST('"sing dance"' IN BOOLEAN MODE); 这句的执行结果为空,因为 sing dance 为一个整体。
4.4、扩展检索
扩展检索 是对 全文检索 的一个扩展,什么是扩展检索呢?比如你全文检索一个词 “鼠标”,但是也许不止是鼠标呢?也许你还想检索出来一点和鼠标有关的信息呢?比如桌垫,电脑等等,你用扩展检索就能办到。
扩展检索原理:第一次使用全文检索,查询出包含 “鼠标” 的文本,然后将查询出这些文本进行分词,第二次再将这些词进行全文检索。 等于是在第一次全文检索的基础上再进行一次全文检索。
语法(举例说明):SELECT * FROM student WHERE MATCH(interest) AGAINST('football' IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION);
或者 :SELECT * FROM student WHERE MATCH(interest) AGAINST('football' WITH QUERY EXPANSION);
WITH QUERY EXPANSION 只能用于自然语言模式。
5、索引的属性
索引也是包含了一些属性的,我们可以用 SHOW INDEX 语句查看,比如 SHOW INDEX FROM student;
Table属性:此索引属于哪张表。
Non_unique属性:是非唯一索引吗,0 表示唯一索引,1 表示非唯一索引。
Key_name属性:索引名。
Seq_in_index属性:此索引是第几列。
Column_name属性:此索引对应的列名。
Collation属性:索引采取的结构是什么,A表示以B+树,NULL表示其他。
Cardinality属性:此索引中唯一值的数量的预估值,一般不是精准的,只是一个大概的近似值。
Sub_part属性:此索引是以该列的多少内容为索引的,如果是varchar,可能是以前10个字符作为索引,如果是以整个列内容作为索引的话,此属性值为NULL。
Packed属性:被压缩的方式,如果没压缩,则 NULL。
Null属性:索引是否含有NULL值,有就是 yes,没有就是NULL。
Index_type属性:索引类型,BTREE 表示B+树。
Comment属性:我想这是表的注释。
Index_comment属性:索引的注释。
5.1、Cardinality 属性
这个属性比较重要,介绍一下。Cardinality 表示索引中唯一值的数量,比如 1,2,2,3,3,这里面的Cardinality = 3。为什么要设计出这个属性呢?作用是什么呢?作用是:给我们提供一个参考信息,让我们明白是否有必要删除这个索引。索引的目的就是能够快速地查询,因此索引列尽量数据不重复,如果一个列的数据重复性很高,那不建议创建索引,即便你创建了索引,只会添麻烦,因为索引需要维护,浪费空间,在你查询的时候,由于数据重复率高,查询效率也不高(创建索引获得的好处小于了创建索引的代价)。比如一个学生信息系统里面,每一个学生都有一个字段(年级),总共就那么几个年级,自然就没有比较对年级创建索引了。
Cardinality / 表的总行数; 如果这个值越是接近1,越说明数据重复率低,越是可以创建索引,如果这个值非常小,越是接近0,越说明重复率高,越没有必要创建索引。
不可能每次数据更新都去统计一次 Cardinality,会造成大的性能开销,所以 Cardinality 值的更新是需要2种事件去触发的,1、表中 1/16 的数据已经变化了; 2、表已经进行了 2000 000 000 次修改操作了(未必是这么多次,我想每一版的这个次数会稍有不同吧)。 这两个事件中任意一个事件出现,都会导致进行一次 Cardinality 统计更新。
那怎么统计呢?统计出这表有多少个叶子节点(数据节点)N,再随机挑选 8 个数据节点进行统计,根据下面这个公式:
8 个数据节点的唯一值数量 / 8 * N ,就统计出了整个表的唯一值的数量了。
下图给出了几个全局参数。
innodb_stats_method:有3种值,nulls_equal,nulls_unequal,nulls_ignored; 在统计 Cardinality 时,针对 NULL值的处理办法,其中,nulls_equal 表示将 NULL 值也算作是一种值,多个NULL算作是一个重复值;nulls_unequal 表示将 NULL 值算作是一种值,多个NULL 算作是多个重复值,即每个NULL被认为是单独的一个唯一值; nulls_ignored 表示遇到 NULL 就忽略,不做计算。
innodb_stats_on_metadata:当使用 SHOW TABLESTATUS、 SHOW INDEX 语句时, 访问INFORMATION_SCHEMA 库下的 TABLES 和 STATISTICS 表时,是否需要统计一个 Cardinality。 默认 OFF。
innodb_stats_persistent:是否将 Cardinality 持久化到磁盘(包括了Cardinality),持久化的好处是下一次数据库重启就不要再去计算一次表的状态了。默认为 ON。
innodb_stats_persistent_sample_pages: 在 innodb_stats_persistent 开启的情况下,表示Cardinality 采集的数据节点数量。 默认为20。
innodb_stats_sample_pages:Cardinality 采样的数据节点数量,默认为 8,这是老版本的参数,保留在这是为了兼容老版本。
innodb_stats_transient_sample_pages:Cardinality 采样的数据节点数量,默认为 8 ,这是新版本的。