MySQL数据库索引

1、数据库索引介绍

索引是一种特殊的文件(MySql数据表上的索引是表空间的一个组成部分),它们包含着对数据表里全部记录的引用指针,直接在索引中查找符合条件的选项,加快数据库的查询速度,而不是一行一行去遍历数据后才选择出符合条件的。若是没有索引,执行查询时MySQL必须从第一个记录开始扫描整个表的全部记录,直至找到符合要求的记录。表里面的记录数量越多,这个操做的代价就越高。若是做为搜索条件的列上已经建立了索引,MySQL无需扫描任何记录便可迅速获得目标记录所在的位置。node

索引的本质是什么?索引有什么优势,缺点是什么?

索引是帮助MySQL高效获取数据的数据结构。所以,索引的本质是一种数据结构。
在数据以外,数据库系统还能够维护知足特定查找算法的数据结构,这些数据结构以某种方式指向真实数据,这样就能够在这些数据结构上实现高级查找算法,这种数据结构就是索引。 mysql

优势:算法

一、提升数据检索效率,下降数据库的IO成本;
二、经过索引对数据进行排序,下降了数据排序的成本,下降了CPU的利用率; sql

缺点:数据库

一、索引实际上也是一张表,索引会占用必定的存储空间;
二、更新数据表的数据时,须要同时维护索引表,所以,会下降insert、update、delete的速度;数组

2、MySQL索引类型包括哪些?

(1)普通索引

这是最基本的索引,它没有任何限制。它有如下几种建立方式:缓存

◆ 建立索引数据结构

CREATE INDEX indexName ON mytable(username(length));

若是是CHAR,VARCHAR类型,length能够小于字段实际长度;若是是BLOB和TEXT类型,必须指定 length,下同。架构

◆ 修改表结构函数

ALTER mytable ADD INDEX [indexName] ON (username(length))

◆ 建立表的时候直接指定

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
INDEX [indexName] (username(length))  
);

删除索引的语法:

DROP INDEX [indexName] ON mytable;

(2)惟一索引

它与前面的普通索引相似,不一样的就是:索引列的值必须惟一,但容许有空值。若是是组合索引,则列值的组合必须惟一。它有如下几种建立方式:

◆建立索引

CREATE UNIQUE INDEX indexName ON mytable(username(length))

◆修改表结构

ALTER mytable ADD UNIQUE [indexName] ON (username(length))

◆建立表的时候直接指定

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
UNIQUE [indexName] (username(length))  
);

(3)主键索引

它是一种特殊的惟一索引,不容许有空值。通常是在建表的时候同时建立主键索引:

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
PRIMARY KEY(ID)  
);

固然也能够用 ALTER 命令。记住:一个表只能有一个主键。

(4)组合索引

为了形象地对比单列索引和组合索引,为表添加多个字段:

CREATE TABLE mytable(  
ID INT NOT NULL,   
username VARCHAR(16) NOT NULL,  
city VARCHAR(50) NOT NULL,  
age INT NOT NULL 
);

为了进一步榨取MySQL的效率,就要考虑创建组合索引。就是将 name, city, age建到一个索引里:

ALTER TABLE mytable ADD INDEX name_city_age (name(10),city,age);

建表时,usernname长度为 16,这里用 10。这是由于通常状况下名字的长度不会超过10,这样会加速索引查询速度,还会减小索引文件的大小,提升INSERT的更新速度。

若是分别在 usernname,city,age上创建单列索引,让该表有3个单列索引,查询时和上述的组合索引效率也会大不同,远远低于咱们的组合索引。虽然此时有了三个索引,但MySQL只能用到其中的那个它认为彷佛是最有效率的单列索引。

创建这样的组合索引,实际上是至关于分别创建了下面三组组合索引:

usernname,city,age  
usernname,city  
usernname

为何没有 city,age这样的组合索引呢?这是由于MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并非只要包含这三列的查询都会用到该组合索引,下面的几个SQL就会用到这个组合索引:

SELECT * FROM mytable WHREE username="admin" AND city="郑州" 
SELECT * FROM mytable WHREE username="admin"

而下面几个则不会用到:

SELECT * FROM mytable WHREE age=20 AND city="郑州" 
SELECT * FROM mytable WHREE city="郑州"

3、 InnoDB存储索引

在数据库中,若是索引太多,应用程序的性能可能会受到影响;若是索引太少,又会对查询性能产生影响。因此,咱们要追求二者的一个平衡点,足够多的索引带来查询性能提升,又不由于索引过多致使修改数据等操做时负载太高。

InnoDB支持3种常见索引:

 ● 哈希索引

 ● B+ 树索引

 ● 全文索引

咱们接下来要详细讲解的就是B+ 树索引和全文索引。

哈希索引

学习哈希索引以前,咱们先了解一些基础的知识:哈希算法。哈希算法是一种经常使用的算法,时间复杂度为O(1)。它不只应用在索引上,各个数据库应用中也都会使用。

哈希表

哈希表(Hash Table)也称散列表,由直接寻址表改进而来。

1.jpg

在该表中U表示关键字全集,K表示实际存在的关键字,右边的数组(哈希表)表示在内存中能够直接寻址的连续空间,哈希表中每一个插槽关联的单向链表中存储实际数据的真实地址。

若是右边的数组直接使用直接寻址表,那么对于每个关键字K都会存在一个h[K]且不重复,这样存在一些问题,若是U数据量过大,那么对于计算机的可用容量来讲有点不实际。而若是集合K占比U的比例太小,则分配的大部分空间都要浪费。

所以咱们使用哈希表,咱们经过一些函数h(k)来肯定映射关系,这样让离散的数据尽量均匀分布的利用数组中的插槽,但会有一个问题,多个关键字映射到同一个插槽中,这种状况称为碰撞(collision),数据库中采用最简单的解决方案:连接法(chaining)。也就是每一个插槽存储一个单项链表,全部碰撞的元素会依次造成链表中的一个结点,若是不存在,则链表指向为NULL。

而使用的函数h(k)成为哈希函数,它必须可以很好的进行散列。最好可以避免碰撞或者达到最小碰撞。通常为了更好的处理哈希的关键字,咱们会将其转换为天然数,而后经过除法散列、乘法散列或者全域散列来实现。数据库通常使用除法散列,即当有m个插槽时,咱们对每一个关键字k进行对m的取模:h(k) = k % m。

InnoDB存储引擎中的哈希算法

InnoDB存储引擎使用哈希算法来查找字典,冲突机制采用链表,哈希函数采用除法散列。对于缓冲池的哈希表,在缓存池中的每页都有一个chain指针,指向相同哈希值的页。对于除法散列,m的值为略大于2倍缓冲池页数量的质数。如当前innodb_buffer_pool_size大小为10M,则共有640个16KB的页,须要分配1280个插槽,而略大于的质数为1399,所以会分配1399个槽的哈希表,用来哈希查询缓冲池中的页。

而对于将每一个页转换为天然数,每一个表空间都有一个space_id,用户要查询的是空间中某个连续的16KB的页,即偏移量(offset),InnoDB将space_id左移20位,再加上space_id和offset,即K=space_id<<20+space_id+offset,而后使用除法散列到各个槽中。

自适应哈希索引

自适应哈希索引采用上面的哈希表实现,属于数据库内部机制,DBA不能干预。它只对字典类型的查找很是快速,而对范围查找等却无能为力,如:

select * from t where f='100';

咱们能够查看自适应哈希索引的使用状况:

mysql> show engine innodb status\G;
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2019-05-13 23:32:21 7f4875947700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 32 seconds
...
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1226, seg size 1228, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 276671, node heap has 1288 buffer(s)
0.16 hash searches/s, 16.97 non-hash searches/s

咱们能够看到自适应哈希的使用状况,能够经过最后一行的hash searches/non-hash searches来判断使用哈希索引的效率。

咱们可使用innodb_adaptive_hash_index参数来禁用或启用此特性,默认开启。

B+ 树索引

B+ 树索引是目前关系型数据库系统中查找最为经常使用和有效的索引,其构造相似于二叉树,根据键值对快速找到数据。B+ 树(balance+ tree)由B树(banlance tree 平衡二叉树)和索引顺序访问方法(ISAM: Index Sequence Access Method)演化而来,这几个都是经典的数据结构。而MyISAM引擎最初也是参考ISAM数据结构设计的。

基础数据结构

想要了解B+ 树数据结构,咱们先了解一些基础的知识。

(1)二分查找法

又称为折半查找法,指的是将数据顺序排列,经过每次和中间值比较,跳跃式查找,每次缩减一半的范围,快速找到目标的算法。其算法复杂度为log2(n),比顺序查找要快上一些。

如图所示,从有序列表中查找48,只须要3步:

2.jpg

详细的算法能够参考二分查找算法。

(2)二叉查找树

二叉查找树的定义是在一个二叉树中,左子树的值老是小于根键值,根键值老是小于右子树的值。在咱们查找时,每次都从根开始查找,根据比较的结果来判断继续查找左子树仍是右子树。其查找的方法很是相似于二分查找法。

3.jpg

(3)平衡二叉树

二叉查找树的定义很是宽泛,能够任意构造,可是在极端状况下查询的效率和顺序查找同样,如只有左子树的二叉查找树。

4.jpg

若想构造一个性能最大的二叉查找树,就须要该树是平衡的,即平衡二叉树(因为其发明者为G. M. Adelson-Velsky 和 Evgenii Landis,又被称为AVL树)。其定义为必须知足任何节点的两个子树的高度最大差为1的二叉查找树。平衡二叉树相对结构较优,而最好的性能须要创建一个最优二叉树,但因为维护该树代价高,所以通常平衡二叉树便可。

平衡二叉树查询速度很快,但在树发生变动时,须要经过一次或屡次左旋和右旋来达到树新的平衡。这里不发散讲。

B+ 树

了解了基础的数据结构后,咱们来看下B+ 树的实现,其定义十分复杂,简单来讲就是在B树上增长规定:

一、叶子结点存数据,非叶子结点存指针

二、全部叶子结点从左到右用双向链表记录

目标是为磁盘或其余直接存取辅助设备设计的一种平衡查找树。在该树中,全部的记录都按键值的大小放在同一层的叶子节点上,各叶子节点之间有指针进行链接(非连续存储),造成一个双向链表。索引节点按照平衡树的方式构造,并存在指针指向具体的叶子节点,进行快速查找。

下面的B+ 树为数据较少时,此时高度为2,每页固定存放4条记录,扇出固定为5(图上灰色部分)。叶子节点存放多条数据,是为了下降树的高度,进行快速查找。

5.jpg

当咱们插入2八、70、95 3条数据后,B+ 树因为数据满了,须要进行页的拆分。此时高度变为3,每页依然是4条记录,双向链表未画出可是依然是存在的,如今能够看出来是一个平衡二叉树的雏形了。

6.jpg

InnoDB的B+ 树索引

InnoDB的B+ 树索引的特色是高扇出性,所以通常树的高度为2~4层,这样咱们在查找一条记录时只用I/O 2~4次。当前机械硬盘每秒至少100次I/O/s,所以查询时间只需0.02~0.04s。

数据库中的B+ 树索引分为汇集索引(clustered index)和辅助索引(secondary index)。它们的区别是叶子节点存放的是否为一整行的完整数据。

汇集索引

汇集索引就是按照每张表的主键(惟一)构造一棵B+ 树,同时叶子节点存放整行的完整数据,所以将叶子节点称为数据页。因为定义了数据的逻辑顺序,汇集索引也能快速的进行范围类型的查询。

汇集索引的叶子节点按照逻辑顺序连续存储,叶子节点内部物理上连续存储,做为最小单元,叶子节点间经过双向指针链接,物理存储上不连续,逻辑存储上连续。

汇集索引可以针对主键进行快速的排序查找和范围查找,因为是双向链表,所以在逆序查找时也很是快。

咱们能够经过explain命令来分析MySQL数据库的执行计划:

# 查看表的定义,能够看到id为主键,name为普通列
mysql> show create table dimensionsConf;
| Table          | Create Table    
| dimensionsConf | CREATE TABLE `dimensionsConf` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `remark` varchar(1024) NOT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `fullindex_remark` (`remark`)
) ENGINE=InnoDB AUTO_INCREMENT=178 DEFAULT CHARSET=utf8 |
1 row in set (0.00 sec)
 
# 先测试一个非主键的name属性排序并查找,能够看到没有使用到任何索引,且须要filesort(文件排序),这里的rows为输出行数的预估值
mysql> explain select * from dimensionsConf order by name limit 10\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 57
        Extra: Using filesort
1 row in set (0.00 sec)
 
# 再测试主键id的排序并查找,此时使用主键索引,在执行计划中没有了filesort操做,这就是汇集索引带来的优化
mysql> explain select * from dimensionsConf order by id limit 10\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 10
        Extra: NULL
1 row in set (0.00 sec)
 
# 再查找根据主键id的范围查找,此时直接根据叶子节点的上层节点就能够快速获得范围,而后读取数据
mysql> explain select * from dimensionsConf where id>10 and id<10000\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dimensionsConf
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 56
        Extra: Using where
1 row in set (0.00 sec)

辅助索引

辅助索引又称非汇集索引,其叶子节点不包含行记录的所有数据,而是包含一个书签(bookmark),该书签指向对应行数据的汇集索引,告诉InnoDB存储引擎去哪里查找具体的行数据。辅助索引与汇集索引的关系就是结构类似、独立存在,但辅助索引查找非索引数据须要依赖于汇集索引来查找。

7.jpg

全文索引

咱们经过B+ 树索引能够进行前缀查找,如:

select * from blog where content like 'xxx%';

只要为content列添加了B+ 树索引(汇集索引或辅助索引),就可快速查询。但在更多状况下,咱们在博客或搜索引擎中须要查询的是某个单词,而不是某个单词开头,如:

select * from blog where content like '%xxx%';

此时若是使用B+ 树索引依然是全表扫描,而全文检索(Full-Text Search)就是将整本书或文章内任意内容检索出来的技术。

倒排索引

全文索引一般使用倒排索引(inverted index)来实现,倒排索引和B+ 树索引都是一种索引结构,它须要将分词(word)存储在一个辅助表(Auxiliary Table)中,为了提升全文检索的并行性能,共有6张辅助表。辅助表中存储了单词和单词在各行记录中位置的映射关系。它分为两种:

inverted file index(倒排文件索引),表现为{单词,单词所在文档ID}
full inverted index(详细倒排索引),表现为{单词,(单词所在文档ID, 文档中的位置)}
对于这样的一个数据表:

8.jpg

倒排文件索引类型的辅助表存储为:

9.jpg

详细倒排索引类型的辅助表存储为,占用更多空间,也更好的定位数据,比提供更多的搜索特性:

10.jpg

全文检索索引缓存

辅助表是存在与磁盘上的持久化的表,因为磁盘I/O比较慢,所以提供FTS Index Cache(全文检索索引缓存)来提升性能。FTS Index Cache是一个红黑树结构,根据(word, list)排序,在有数据插入时,索引先更新到缓存中,然后InnoDB存储引擎会批量进行更新到辅助表中。

当数据库宕机时,还没有落盘的索引缓存数据会自动读取并存储,配置参数innodb_ft_cache_size控制缓存的大小,默认为32M,提升该值,能够提升全文检索的性能,但在故障时,须要更久的时间恢复。

在删除数据时,InnoDB不会删除索引数据,而是保存在DELETED辅助表中,所以一段时间后,索引会变得很是大,能够经过optimize table命令手动删除无效索引记录。若是须要删除的内容很是多,会影响应用程序的可用性,参数innodb_ft_num_word_optimize控制每次删除的分词数量,默认为2000,用户能够调整该参数来控制删除幅度。

全文检索限制

全文检索存在一个黑名单列表(stopword list),该列表中的词不须要进行索引分词,默认共有36个,如the单词。你能够自行调整:

mysql> select * from information_schema.INNODB_FT_DEFAULT_STOPWORD;
+-------+
| value |
+-------+
| a     |
| about |
| an    |
| are   |
| as    |
| at    |
| be    |
| by    |
| com   |
| de    |
| en    |
| for   |
| from  |
| how   |
| i     |
| in    |
| is    |
| it    |
| la    |
| of    |
| on    |
| or    |
| that  |
| the   |
| this  |
| to    |
| was   |
| what  |
| when  |
| where |
| who   |
| will  |
| with  |
| und   |
| the   |
| www   |
+-------+
36 rows in set (0.00 sec)

其余限制还有:

 ● 每张表只能有一个全文检索索引

 ● 多列组合的全文检索索引必须使用相同的字符集和字符序,不了解的能够参考MySQL乱码的缘由和设置UTF8数据格式

 ● 不支持没有单词界定符(delimiter)的语言,如中文、日语、韩语等

全文检索

咱们建立一个全文索引:

mysql> create fulltext index fullindex_remark on dimensionsConf(remark);
Query OK, 0 rows affected, 1 warning (0.39 sec)
Records: 0  Duplicates: 0  Warnings: 1
 
mysql> show warnings;
+---------+------+--------------------------------------------------+
| Level   | Code | Message                                          |
+---------+------+--------------------------------------------------+
| Warning |  124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)

全文检索有两种方法:

 ● 天然语言(Natural Language),默认方法,可省略:(IN NATURAL LANGUAE MODE)

 ● 布尔模式(Boolean Mode):(IN BOOLEAN MODE)

天然语言还支持一种扩展模式,后面加上:(WITH QUERY EXPANSION)。

其语法为MATCH()...AGAINST(),MATCH指定被查询的列,AGAINST指定何种方法查询。

天然语言检索

mysql> select remark from dimensionsConf where remark like '%baby%';
+-------------------+
| remark            |
+-------------------+
| a baby like panda |
| a baby like panda |
+-------------------+
2 rows in set (0.00 sec)
 
mysql> select remark from dimensionsConf where match(remark) against('baby' IN NATURAL LANGUAGE MODE);
+-------------------+
| remark            |
+-------------------+
| a baby like panda |
| a baby like panda |
+-------------------+
2 rows in set (0.00 sec)
 
# 查看下执行计划,使用了全文索引排序
mysql> explain select * from dimensionsConf where match(remark) against('baby');
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
| id | select_type | table          | type     | possible_keys    | key              | key_len | ref  | rows | Extra       |
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
|  1 | SIMPLE      | dimensionsConf | fulltext | fullindex_remark | fullindex_remark | 0       | NULL |    1 | Using where |
+----+-------------+----------------+----------+------------------+------------------+---------+------+------+-------------+
1 row in set (0.00 sec)

咱们也能够查看各行数据的相关性,是一个非负的浮点数,0表明没有相关性:

mysql> select id,remark,match(remark) against('baby') as relevance from dimensionsConf;
+-----+-----------------------+--------------------+
| id  | remark                | relevance          |
+-----+-----------------------+--------------------+
| 106 | c                     |                  0 |
| 111 | 运营商             |                  0 |
| 115 | a baby like panda     | 2.1165735721588135 |
| 116 | a baby like panda     | 2.1165735721588135 |
+-----+-----------------------+--------------------+
4 rows in set (0.01 sec)

布尔模式检索

MySQL也容许用修饰符来进行全文检索,其中特殊字符会有特殊含义:

● +: 该word必须存在
● -: 该word必须排除
● (no operator): 该word可选,若是出现,相关性更高
● @distance: 查询的多个单词必须在指定范围以内
● >: 出现该单词时增长相关性
● <: 出现该单词时下降相关性
● ~: 出现该单词时相关性为负
● *: 以该单词开头的单词
● ": 表示短语

# 表明必须有a baby短语,不能有man,能够有lik开头的单词,能够有panda,
select remark from dimensionsConf where match(remark) against('+"a baby" -man lik* panda' IN BOOLEAN MODE);

扩展查询

当查询的关键字过短或不够清晰时,须要用隐含知识来进行检索,如database关联的MySQL/DB2等。但这个我并没太明白怎么使用,后续补充吧。

相似的使用是:

select * from articles where match(title,body) against('database' with query expansion);

若是任何问题或者建议,欢迎留言交流。

更多学习内容请访问从码农成为架构师的修炼之路

相关文章
相关标签/搜索