mysql原理(七)索引与算法

作者: 我犟不过你 | 来源:发表于2021-05-17 16:52 被阅读0次

    InnoDB存储引擎支持以下几种索引:
    1)B+树索引
    2)哈希索引:InnoDB会根据使用情况自动生成自适应哈希索引,不需要人为干预。
    3)全文索引

    注意:B+树索引并不能找到数据所在的行,而是找到数据行所在的页,将页加载到内存中,然后再内存中寻找,得到需要的数据。

    一、数据结构与算法

    关于这部分,在其他文集单独有介绍。请参考我其他的文章:https://www.jianshu.com/nb/48308723,在这里不再做详细的介绍了。

    二、B+树索引

    关于索引的细节可以查看我的这篇文章:https://www.jianshu.com/p/a1ced64b7a76

    B+树索引的本质就是B+树在数据库中的实现。B+树索引在数据库中的特点就是高扇出性(通常指负载能力的高低,这里指数据库的IO次数),因为其树高通常在2 ~ 4层,只需要2 ~ 4次的磁盘IO。

    B+树索引又可分为聚集索引和辅助索引,内部都是B+树的,区别在于,聚集索引的叶子节点存放的是一整行数据,而辅助索引的叶子节点存放的是聚集索引的值。

    2.1 聚集索引

    聚集索引就是按照每张表的主键构造一个B+树,同时叶子节点存放整行记录数,也将聚集索引的叶子节点称为数据页。每个数据页都通过一个双向链表进行链接。而在非数据页节点,存放的是键值和偏移量。

    每张表只有一个聚集索引的B+树,并且查询优化器倾向于使用聚集索引。因为可以在聚集索引的叶子节点 上直接找到数据。由于定义了数据的逻辑顺序,聚集索引能够快速的找到范围内的数据。

    2.2 辅助索引

    辅助索引也称为非聚集索引,叶子结点并不包含行的所有记录。其叶子节点包含键值和一个书签(bookmark)。该书签用来告诉存储引擎在哪里可以找到与索引相应的行记录。因为Innodb是索引组织表,所以该书签就是该行记录的聚集索引键值。

    当通过辅助索引去索引某条数据时,会先在辅助索引中去寻找到聚集索引的键,然后再聚集索引中根据键再去找到对应的行数据。如果辅助索引和聚集索引的树高都是3的话,则此次查询共经过了6次IO。

    三、Cardinality

    并不是在任何的查询条件上都需要加上索引,根据经验,只有访问表中很少一部分数据时,索引才有意义。例如对于性别等字段,他们可选取的范围很小,称为低选择性。

    那么如何确定索引是否是高选择性呢?通过show index中的Cardinality值来确定。该值表示索引中不重复记录数量的预估值,且该值不是一个精确的值。在实际应该用中,cardinality/n_rows_in_table应尽可能的接近1。如果该值非常小,则用户需要考虑是否有建立索引的必要。

    Cardinality是如何统计的呢?
    每种存储引擎对B+树的实现是不同的,所以该统计是在存储引擎层进行的。

    在生产环境中,索引的更新操作很频繁,如果每次都对Cardinality进行统计,必然会造成很大的性能开销,通常来说,数据库对于cardinality的统计都是通过采样(sample)来完成的。

    更新cardinality的策略:
    1)表中1/16的数据已发生变化
    2)stat_modified_counter > 2 000 000 000:当修改次数达到该值,就更新。

    我们举个例子,如下图所示:

    mysql> show index from bssp.bssp_sys_menu;
    +---------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
    | Table         | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
    +---------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
    | bssp_sys_menu |          0 | PRIMARY       |            1 | id          | A         |          42 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
    | bssp_sys_menu |          1 | INX_STATUS    |            1 | is_enable   | A         |           1 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
    | bssp_sys_menu |          1 | INX_STATUS    |            2 | is_public   | A         |           2 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
    | bssp_sys_menu |          1 | IDX_LABEL     |            1 | label       | A         |          42 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
    | bssp_sys_menu |          1 | IDX_PARENT_ID |            1 | parent_id   | A         |          11 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
    +---------------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
    5 rows in set (0.01 sec)
    

    在上面的表中,我们看到了总共有5个索引,其中表记录总共有42条,其中有两个cartinality的值是42,分别是主键id和lable字段,cartinality/表行数 = 1,符合建立索引的条件,而在两个状态字段is_enable和is_public,则不太适合,而parent_id则可以根据后面的数据量自行调整。

    四、B+树索引的使用

    4.1 联合索引

    先模拟一个联合索引的存储结构是什么样子的,以两个key 做联合索引,仍然是在一棵树上,排序先比较前面的key,在比较后面的key,如下图:

    联合索引结构

    举个例子描述下最左匹配:
    有一张学生表,字段分别是name,age,adrress...等,将name和age做为联合索引(name,age),依靠最左匹配原则,会有下面四种情况查询条件:

    1 where name = ? and age =? # 走组合索引
    2 where age =? and name = ? # 走组合索引
    3 where name = ? # 走组合索引
    4 where age =? # 不走组合索引
    

    上面1、2、3中会走组合索引,因为其满足最左匹配原则,至少有name字段是可以匹配到索引,2中之所以能够走索引,是因为在mysql的server中查询优化器会对sql重新排序,达到最优的效果。4则完全无法匹配到组合索引。

    实际验证一下,在最后一种情况中,extra中using where using index,使用了索引的部分,但是不是左前缀,仍然扫描了全表:

    student
    mysql> explain select  * from bssp.student where name = '张三' and age = 35;
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------------+------+----------+-------------+
    | id | select_type | table   | partitions | type | possible_keys | key          | key_len | ref         | rows | filtered | Extra       |
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------------+------+----------+-------------+
    |  1 | SIMPLE      | student | NULL       | ref  | idx_name_age  | idx_name_age | 1028    | const,const |    1 |   100.00 | Using index |
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select  * from bssp.student where name = '张三';
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
    | id | select_type | table   | partitions | type | possible_keys | key          | key_len | ref   | rows | filtered | Extra       |
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
    |  1 | SIMPLE      | student | NULL       | ref  | idx_name_age  | idx_name_age | 1023    | const |    1 |   100.00 | Using index |
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select  * from bssp.student where age = 35 and name = '张三' ;
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------------+------+----------+-------------+
    | id | select_type | table   | partitions | type | possible_keys | key          | key_len | ref         | rows | filtered | Extra       |
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------------+------+----------+-------------+
    |  1 | SIMPLE      | student | NULL       | ref  | idx_name_age  | idx_name_age | 1028    | const,const |    1 |   100.00 | Using index |
    +----+-------------+---------+------------+------+---------------+--------------+---------+-------------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select  * from bssp.student where age = 35;
    +----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+--------------------------+
    | id | select_type | table   | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra                    |
    +----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+--------------------------+
    |  1 | SIMPLE      | student | NULL       | index | idx_name_age  | idx_name_age | 1028    | NULL |    3 |    33.33 | Using where; Using index |
    +----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+--------------------------+
    1 row in set, 1 warning (0.00 sec)
    

    4.2 覆盖索引

    上面由于使用了select * 进行查询,导致了回表。当使用select id from table where 普通索引 = '某值'时,这时使用了索引覆盖(using index)

    举例子,分别执行以下两个sql,在结果一回表的过程中,没有使用到索引覆盖,而在结果二中,使用到了索引覆盖:

    explain select * from bssp_sys_menu where parent_id = 101;
    explain select id from bssp_sys_menu where parent_id = 101;
    
    结果1:回表 结果2:索引覆盖

    尽量使用索引覆盖能减少磁盘的io,因为避免了回表。

    4.3 MRR(Mutil-Range Read)

    MRR 通过把「随机磁盘读」,转化为「顺序磁盘读」,从而提高了索引查询的性能。

    在mysql中有两个配置:
    mrr: on/off
    mrr_cost_based: on/off

    #开启mrr
    set optimizer_switch='mrr=on';
    #基于成本的逻辑判断开关,开启的话mysql会判断是否需要使用mrr
    set optimizer_switch='mrr_cost_based=on';
    

    开启mrr并且sql确实用到后会在explain语句中看到,如下:

    explain select * from bssp_sys_menu where parent_id between 100 and 104;
    
    MRR

    如上图我们在查找范围数据时,会在二级索引拿到数据的主键id,及聚簇索引的key,这时候需要回表到聚簇索引,如果没有MRR,则我们拿到的id可能是乱序的,这样到磁盘读取数据会导致磁盘重复转动,经过MRR排序后,会在聚簇索引顺序读取

    MRR好处:
    1)使数据访问变得较为顺序。查询辅助索引时,首先根据得到的查询结果,按照主键进行排序,并按照主键排序的顺序进行书签查找。
    2)减少缓冲池中页被替换的次数。
    3)批量处理对键值的查询操作。

    对于InnoDB和MyISam的的范围查找和JOIN操作,MRR的工作方式如下:
    1)将查询到的辅助索引键值存放到缓存中,这是缓存中的数据时根据辅助索引的键值排序的。
    2)将缓存中的键值根据rowid进行排序。
    3)根据rowid的排序顺序来访问实际的数据文件。

    4.4 ICP(Index Condition Pushdown,索引下推)

    要想深入理解 ICP 技术,必须先理解数据库是如何处理 where 中的条件的,对 where 中过滤条件的处理,根据索引使用情况分成了三种:index key, index filter, table filter
    摘自https://www.cnblogs.com/digdeep/p/4994130.html

    1. index key
    用于确定SQL查询在索引中的连续范围(起始范围+结束范围)的查询条件,被称之为Index Key。由于一个范围,至少包含一个起始与一个终止,因此Index Key也被拆分为Index First KeyIndex Last Key,分别用于定位索引查找的起始,以及索引查询的终止条件。也就是说根据索引来确定扫描的范围。

    2. index filter
    在使用 index key 确定了起始范围和介绍范围之后,在此范围之内,还有一些记录不符合where 条件,如果这些条件可以使用索引进行过滤,那么就是 index filter。也就是说用索引来进行where条件过滤。

    3. table filter
    where 中的条件不能使用索引进行处理的,只能访问table,进行条件过滤了。

    通过上面可以得到mysql的where条件过滤分为三个步骤:index key -> index filter -> table filter.

    在 MySQL5.6 之前,并不区分Index Filter与Table Filter,统统将Index First Key与Index Last Key范围内的索引记录,回表读取完整记录,然后返回给MySQL Server层进行过滤。而在MySQL 5.6之后,Index Filter与Table Filter分离,Index Filter下降到InnoDB的索引层面进行过滤,减少了回表与返回MySQL Server层的记录交互开销,提高了SQL的执行效率。

    所以所谓的 ICP 技术,其实就是 index filter 技术而已。只不过因为MySQL的架构原因,分成了server层和引擎层,才有所谓的“下推”的说法。所以ICP其实就是实现了index filter技术,将原来的在server层进行的table filter中可以进行index filter的部分,在引擎层面使用index filter进行处理,不再需要回表进行table filter。

    如下表示使用了索引下推:

    mysql> explain select * from bssp.bssp_sys_menu where parent_id > 1000;
    +----+-------------+---------------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
    | id | select_type | table         | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+---------------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | bssp_sys_menu | NULL       | range | IDX_PARENT_ID | IDX_PARENT_ID | 9       | NULL |    5 |   100.00 | Using index condition |
    +----+-------------+---------------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    

    相关文章

      网友评论

        本文标题:mysql原理(七)索引与算法

        本文链接:https://www.haomeiwen.com/subject/sdyjjltx.html