Mysql之索引

…衆ロ難τιáo~ 提交于 2020-03-07 00:49:54

索引

  1. 只有当索引帮助存储引擎快速查找到记录的带来的好处大于其带来的额外工作时,索引才是有效的。对于非常小的表,大部分情况下简单的全表扫描更高效

  2. 在一个100w条数据的表中,如果某一列没有添加索引,那么每一句select语句都要随机地逐条扫描100w行数据,每次都要从中寻找0或者更多匹配的行。虽然这些数据最初是按照顺序加载的,但sql也不能理解这种顺序,它必须要处理所有行才能找到匹配的数据。添加索引并不总能自动改善所有类型的SQL查询的性能。有时候执行全表扫描反而更加高效,这取决于所要求的行数。这就是两种不同访问方式的差异,即通过随机IO操作来获取个别行的数据和使用查询索引及有序IO操作来读取所有数据。

  3. 索引除了在给定表上限制需要读取的数据外,索引的另一个主要用途就是快捷高效地在相关的表之间做Join操作。在需要Join的列上使用索引可以显著提升性能,并可以在另一个表中快速找到一个匹配的值。

  4. 优点

    • 索引大大减少了服务器需要扫描的数据量

    • 索引可以帮助服务器避免排序和临时表

    • 索引可以将随机IO变为顺序IO

  5. MyISAM和InnoDb存储引擎的表默认创建的都是BTREE索引。MyISAM的表索引的前缀长度可以达到1000字节长,而对于InnoDB存储引擎的表,索引的前缀长度最长是767字节

  6. 查看索引的使用

    SHOW STATUS LIKE 'Handler_read%';
    
    handler_read_key:这个值越高越好,越高表示使用索引查询到的次数。
    handler_read_rnd_next:这个值越高,说明查询低效。
    

索引原理

  1. 在数据库中B+树的高度一般在2~4层,意味着查找某一键值最多只需要2到4次IO操作,这还不错,因为现在一般的磁盘每秒至少可以做100次IO操作,2~4次的IO操作意味着查询时间只需要0.02~0.04
  2. 在Mysql中,索引是在存储引擎层实现的,这意味着每个引擎的B+树索引的实现方式可能是不同的,取决于存储引擎实现的本身。

索引技巧与注意事项

  1. 应该在哪些列上创建索引

    • 在经常需要搜索的列上,可以加快搜索的速度;在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构
    • 在经常用于连接的列上,这些列主要是一些外键,可以加快连接的速度
    • 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定范围是连续的
    • 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间
    • 在经常使用在where子句的列上创建索引,加快条件的判断速度
  2. 哪些列适合添加索引

    • 较频繁的作为查询条件字段应该创建索引

    • 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件

    • 更新非常频繁的字段不适合创建索引

    • 不会出现在WHERE子句中字段不该创建索引

    • 修改性能远远大于检索性能时,即当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。

    • Text image bit数据类型的列不应该创建索引

  3. 过多创建索引的坏处

    • 创建索引和维护索引要消耗时间,这种时间随着数据量的增加而增加
    • 索引需要占用物理空间,并降低写的操作性能,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,一般情况下这个问题不太严重,如果要建立聚集索引,那么需要的空间就会更大。在修改表的时候,索引必须更新,有时可能需要重构,因此索引越多,所花的时间越长
    • 当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度

建立索引的原则

  1. 最左前缀匹配原则
    • mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
    • 使用like关键字,如果匹配字符串的第一个字符为%,索引不会起作用。只有%不在第一个位置,索引才会起作用
    • 当我们创建一个联合索引的时候,如(key1,key2,key3),相当于创建了(key1)、(key1,key2)和(key1,key2,key3)三个索引,这就是最左匹配原则。
  2. =和in可以乱序
    • 比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
  3. 使用OR关键字:当OR前后的两个条件中的列都是索引时,查询中才会使用索引,否则不会使用索引
  4. 尽量选择区分度高的列作为索引
    • 区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0
    • 区分度较低的,索引效果差,没有必要在此列建立索引
  5. 索引列不能参与计算
    • B+树中存的都是数据表中的字段值
  6. 尽量扩展索引,而不是新建索引
    • 比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可

利用索引排序

  1. mysql 有两种方式可以产生有序结果,一种是使用文件排序,另一种是扫描有序的索引。尽量使用索引来排序。

    • 尽量保证索引列和order by 的列相同,且各列按照相同的顺序排序,比如在表table1的复合索引idx_a_b_c(创建在a,b,c上),以下查询都可以利用有序索引来加速检索顺序。

      select * from table1 order by  a,b,c;
      select * from table1 where a=? and b =? order by c
      
    • 如果连接多张表,那么order by 引用的列需要在表连接顺序的首张表内

InnoDB索引模型

  1. 在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。InnoDB使用了B+树索引模型,所以数据都是存储在B+树中。每一个索引在InnoDB里面对应一棵B+树。
  2. 主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clustered index)。在创建一张表时,如果不主动创建主键,InnoDB会选择第一个不包含有null值的唯一索引作为主键。如果没有唯一索引,InnoDB就会为该表默认生成一个6字节的rowid作为主键。
  3. 非主键索引的叶子节点内容是自己本身的键值和主键的值,在InnoDB里,非主键索引也被称为二级索引(secondary index)。
  4. 主键索引与非主键索引查询区别
    • select * from user where id=500,即通过主键查询,只需要搜索id这棵B+树
    • select * from user where username=‘jannal’,即通过普通索引查询方式,需要先搜索username索引树,得到Id的值为500,再到id索引树搜索一次,这个过程称为回表。也就是说基于非主键索引的查询需要多扫描一棵索引树。
    • 聚集索引(clustered index)和非聚集索引(secondary index)内部都是B+树,聚集索引叶子节点存放所有的数据,非聚集索引叶子节点存放的是主键。
  5. 覆盖索引:select id from user where age between 3 and 5。这里只需要查id的值,而id的值已经在age索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引age已经"覆盖了"查询的需求,称为覆盖索引(覆盖索引可以减少B+树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段)
  6. B+树为了维护索引有序性,在插入新值的时候需要做一些维护。自增主键的插入数据模式,每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小
  7. 业务字段直接做主键的场景: 只有一个索引,且是唯一索引。
  8. 索引下推:
    • innodb引擎的表,索引下推只能用于二级索引
    • innodb的主键索引树叶子结点上保存的是全行数据,所以索引下推并不会起到减少查询全行数据的效果
    • MySQL 5.6之前只能一个个回表。到主键索引上找出数据行,再对比字段值。MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

联合索引

  1. 联合索引本质上也是一个B+树,不同的是联合索引的键值的数量不是1而是大于等于2。假设有两个键值名为a和b,B+树如图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vpHp93z4-1583500072437)(https://gitee.com/jannal/images/raw/master/mysql/image-20190831152934753.png)]

  2. 键值是排序,通过叶子节点可以逻辑上顺序地读取所有数据,(1,1)(1,2)(2,1)(2,4)(3,1)(3,2)数据按照(a,b)顺序的存放。因此对于查询

    select * from table where a=xx and b=xx
    

    可以使用(a,b)这个联合索引,对于单个查询

    select * from table where a=xx
    

    也可以使用(a,b)这个联合索引,但是对于b列的单个查询

    select * from table where b=xx
    

    是无法使用(a,b)这个联合索引,因为叶子节点上的b值为1,2,1,4,1,2,它不是排序好的

  3. 联合索引是根据a、b进行排序的

    1. 对于(a,b)联合索引,联合索引能直接得出结果
    select ... from table where a=xxx order by b
    
    2. 对于(a,b,c)联合索引,联合索引能直接得出结果
    select ... from table where a=xxx order by b
    select ... from table where a=xxx and b=xxx order by c
    
    3. 对于(a,b,c)联合索引,联合索引不能直接得出结果,还需要一次filesort排序,因为(a,c)并未排序
    select ... from table where a=xxx  order by c
    
  4. 当不需要考虑排序和分组时, 将选择性最高的列放到索引最前列通常是最好的。性能不只是依赖于所有索引列的选择性(整体基数),也和查询条件的具体值有关,也就是和值的分布有关,这种情况可能需要根据那些运行频率最高的查询来调整索引列的顺序

聚集索引

  1. InnoDB存储引擎表是索引组织表,即表中的数据按照主键顺序存放。聚集索引(clustered index)就是按照每张表的主键构造一颗B+树,同时叶子节点(也称数据页)中存放的即为整张表的行记录数据,每个数据页通过一个双向链表进行链接。
  2. 聚集索引的存储并不是物理上连续的,而是逻辑上连续的。聚集索引对于主键的排序查找和范围查找速度非常快,因为叶子节点的数据就是要查询的数据。
  3. 聚集索引优点
    • 可以把相关数据保存在一起,只需要从磁盘读取少数的数据页就能获取全部数据
    • 数据访问更快。索引和数据保存在同一个B+Tree中,因此从聚集索引中获取全部数据通常比在非聚集索引中要快,因为非聚集索引数据保存的主键,需要回表查询
    • 使用覆盖索引扫描的查询可以直接使用页节点中的主键值
  4. 聚集索引的缺点
    • 聚集数据最大限度低提高了I/O密集型应用的性能,但是如果数据全部都放在内存中,聚集索引就没什么优势了
    • 插入速度严重依赖于插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。但是如果不按照主键顺序加载数据,那么在加载完成后最好使用OPTIMIZE TABLE重新组织一下表
    • 更新聚集索引列的代价高,因为会强制InnoDB将每个被更新的行移动到新的位置
    • 基于聚集索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临"页分裂(page split)"的问题。当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作。 页分裂会导致表占用更多的磁盘空间。
    • 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。

辅助索引

  1. 辅助索引(Secondary Index,也称非聚集索引)中叶子节点并不包含全部数据,叶子节点包含行数据的聚集索引键。
  2. 当通过非聚集索引来查找数据时,InnoDB存储引擎会遍历非聚集索引并通过叶级别的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。比如,如果一个非聚集索引的树高度为3,即需要对这棵辅助索引树遍历3次找到指定的主键,如果聚集索引的树高度也为3,那么还需要对聚集索引树进行3次查找,最终找到完整的行数据所在的页,即一共需要6次逻辑IO访问以得到最终的一个数据页。

覆盖索引

  1. InnoDB存储引擎支持覆盖索引(covering index),即从辅助索引中就可以查询的记录,不需要查询聚集索引中的记录。使用覆盖索引的好处是非聚簇索引不包括整行记录,所以索引大小远小于聚集索引,因此可以减少大量的IO操作

  2. 覆盖索引的另一个好处是对某些统计问题而言的

    select count(*) from user
    

    InnoDB存储引擎并不会选择通过查询聚集索引来进行统计,因为user表上还有非聚集索引,而非聚集索引远小于聚集索引,选择非聚集索引可以减少磁盘IO

  3. 优点

    • 索引条目通常远小于数据行大小,所以如果只需要读取索引
    • 因为索引是按照列值顺序存储的(至少在单个页内是如此) ,所以对于I/O 密集型的范围查询会比随机从磁盘读取每一行数据的 I/O 要少得多
    • 由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。 InnoDB的二级索引在叶子节点中保存了行的 主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。

索引合并

  1. 在多个列上建立独立的索引大部分情况并不能提高MySQL的查询性能。MySQL5.0及之后版本引入一种叫**索引合并(index merge)**的策略,一定程度上可以使用表上的多个单列索引来定位指定的行。

  2. 对于表t_abc在a和b上都有一个单列索引

    1. 新建表
    DROP TABLE IF EXISTS `t_abc`;
    CREATE TABLE `t_abc` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `a` int(11) NOT NULL COMMENT 'a',
      `b` int(11) NOT NULL COMMENT 'b',
      `c` int(11) NOT NULL COMMENT 'c',
      PRIMARY KEY (`id`),
      KEY `idx_a` (`a`) USING BTREE,
      KEY `idx_b` (`b`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    2. 创建数据
    delimiter ;;
    create procedure cdata()
    begin
      declare i int;
      set i=1;
      while(i<=100)do
        insert into t_abc values(i, i*2,i*3, i*4);
        set i=i+1;
      end while;
    end;;
    delimiter ;
    call cdata();
    
  3. 对于以下的查询

    select a,b from t_abc where a = '100'  or b='200'
    

    在MySQL5.0之前,MySQL对于以上的查询需要全表扫描,除非修改为union的方式。在MySQL5.0,能够同时使用两个单列索引进行扫描,并将结果合并(or和and),查看执行计划

    explain select a,b from t_abc where a = '100'  or b='200'
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3sM6CDEn-1583500072440)(https://gitee.com/jannal/images/raw/master/mysql/image-20190903141239630.png)]

  4. 索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建得很糟

    • 当出现服务器对多个索引做相交操作时(通常有多个 AND条件) ,通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
    • 当服务器需要对多个索引做联合操作时(通常有多个 OR条件),通常需要耗费大量 CPU 和内存资源在算法的缓存、排序和合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据的时候。
    • 优化器不会把这些计算到**查询成本( cost)**中,优化器只关心随机页面读取。这会使得查询的成本被“低估”,导致该执行计划还不如直接走全表扫描。 这样做不但会消耗更多的 CPU和内存资源,还可能会影响查询的井发性,但如果是单独运行这样的查询则往往会忽略到并发性的影响 。将查询改写成UNION 的方 式往往更好

Cardinality(基数)

查看索引

  1. 创建表t_abc

    DROP TABLE IF EXISTS `t_abc`;
    CREATE TABLE `t_abc` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `a` int(11) NOT NULL COMMENT 'a',
      `b` int(11) NOT NULL COMMENT 'b',
      `c` int(11) NOT NULL COMMENT 'c',
      PRIMARY KEY (`id`),
      KEY `idx_a` (`a`) USING BTREE,
      KEY `idx_b_c` (`b`,`c`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  2. 查看索引

    1. 当前空表状态下查看索引信息
    show index from t_abc
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-22fuPkZL-1583500072441)(https://gitee.com/jannal/images/raw/master/mysql/image-20190831140020182.png)]

    通过命令可以看到t_abc表有3个索引,分别是主键索引、a列的非聚集索引、b和c列的联合索引

  3. 索引信息字段详解

    • Non_unique:非唯一索引,为0表示唯一索引
    • Key_name:索引的名字,可以通过名字执行删除索引的操作(DROP INDEX)
    • Seq_in_index: 索引中该列的位置,所以idx_b_c有两行,b在第一个位置,c在第二个位置
    • Column_name:索引列的名称
    • Collation:列以什么方式存储在索引中,可以是A或者NULL。B+树的索引总是A,即排序的。
    • Cardinality:基数,表示索引中唯一值的数据估计值,Cardinality与表的行数应该尽可能接近1(Cardinality/表行数≈1),如果非常小,那么需要考虑是否可以删除此索引,因为这里是空表,所以显示的是0
    • Sub_part:是否是列的部分被索引。如果这里显示100,表示对该列的前100个字符进行索引,如果索引整个列,则该字段为NULL
    • Packed:关键字如何被压缩,如果没有被压缩,为NULL
    • Null:是否索引的列含有NULL值
    • Index_type:索引的类型
  4. 插入100条数据

    delimiter ;;
    create procedure idata()
    begin
      declare i int;
      set i=1;
      while(i<=100)do
        insert into t_abc values(i, i*2,i*3, i*4);
        set i=i+1;
      end while;
    end;;
    delimiter ;
    call idata();
    
  5. 再次查看索引信息,可以看到Cardinality变为了100,因为这100条数据没有重复的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zl0BUj6T-1583500072444)(https://gitee.com/jannal/images/raw/master/mysql/image-20190831141654661.png)]

  6. 优化器会根据Cardinality的值来判断是否使用这个索引,但是这个值并不是每次索引更新都会被更新,即不是实时更新的(因为索引更新可能很频繁,而且如果表数据量大,统计一次需要很长时间。所以对于Cardinality的统计是采用采样(Sample)的方法来完成的),也就是这个值不太准确,只是一个大概的值。如果要更新索引Cardinality的信息,可以使用ANALYZE TABLE

    ANALYZE TABLE t_abc
    
  7. 对于以下两种情况最好的解决办法就是在非高峰时间做一次ANALYZE TABLE(当表数据量比较大并且表中存在多个非聚集索引时,执行会非常慢),这样能使优化器和索引更好的为你工作

    • 索引建立但是没有用到

    • 对同一条基本一样的语句执行EXPLAIN,但是结果不一样,一个使用索引,一个全表扫描

Cardinality

  1. 并不是在所有的查询条件中出现的列都需要添加索引。在访问表中很少一部分时使用B+树索引才有意义,对于性别字段、类型字段、状态字段,可取值的范围很小,称为低选择性

    select * from user where sex = 'M'
    

    按照性别查询时,可选择的范围一般只有M和F,即区分度非常低(低选择性),此时添加索引时完全没有必要的。如果某个字段的取值范围很广,几乎没有重复,比如订单号,则属于高选择性(区分度高),此时使用B+树索引是最合适的

  2. 通过show index from 表名查看列Cardinality的值判断索引是否有高选择性

  3. 在InnoDB存储引擎中,Cardinality统计信息的更新发生在两个操作中:Insert和Update,更新策略为

    • 距离上次统计,表中1/16的数据已经发生过变化
    • 表中的数据没有增加,而是行不断被更新,stat_modified_counter>2000 000 000

优化器选择不使用索引的情况

  1. 对于上面的t_abc数据(100条),查询SQL的执行计划如下,possible_keys显示的是能使用哪些索引,key表示Mysql实际决定使用idx_b_c索引,因为有联合索引(b,c),possible_keyskey显示的值与我们所期望的一致。

    explain select * from t_abc t where t.b >40 and t.b<50
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2PJzopD-1583500072447)(https://gitee.com/jannal/images/raw/master/mysql/image-20190831232622450.png)]

  2. 再看另一条SQL的执行计划,发现key是NULL,type为ALL表示全表扫描,为什么没有使用到联合索引(b,c)呢?这是因为如果使用(b,c)索引,每次从b拿到一个主键ID,都要回表到聚集索引上查找整行数据,这个代价优化器也会考虑,优化器认为直接扫描主键索引会更快

    0.001s
    explain select * from t_abc t where t.b >40 and t.b<100
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-udJeVhAw-1583500072448)(https://gitee.com/jannal/images/raw/master/mysql/image-20190831232501984.png)]

    可以使用force index(索引名)来强制使用某个索引,
    explain select * from t_abc t force index(idx_b_c) where t.b >40 and t.b<100
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ReLUkuul-1583500072448)(https://gitee.com/jannal/images/raw/master/mysql/image-20190831234202785.png)]

    花费时间0.002s
    select * from t_abc t force index(idx_b_c) where t.b >40 and t.b<100
    花费时间0.001s
    select * from t_abc t where t.b >40 and t.b<100
    

    单从花费时间来看,优化器的选择是正确的,但是也并不是每次都会选择正确,Cardinality统计的偏差可能导致优化器选择错误,所以如果发现可以使用analyze table t_abc 重新统计索引信息,让优化器作出更正确的选择,如果还是无法作出正确的选择,可以使用 force index(idx_b_c)的方式来强制使用索引,如果觉得 force index(idx_b_c)这种方式侵入sql太严重,可以考虑修改语句或者修改索引、删除误用的索引,引导Mysql使用我们期望的索引,

MRR

  1. MRR(Multi-Range Read Optimization)是Mysql5.6开始支持的优化(默认开启)。Multi-Range Read优化的目的就是为了减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问。

  2. MRR 适用于以下两种情况。

  • range access
  • ref and eq_ref access, when they are using Batched Key Access
  1. 不使用MRR的情况

    • 在二级索引查找后,根据得到的主键到聚簇索引找出需要的数据

    • 二级索引查找得到的主键的顺序是不确定的,因为二级索引的顺序与聚簇索引的顺序不一定一致

    • 聚簇索引查找时就可能出现乱序读取数据页,这对于机械硬盘是及其不友好的。

      select non_key_column from tb where ey_column=x;
      
      1. 先根据where条件中的辅助索引获取辅助索引与主键的集合,结果集为rest
      select key_column, pk_column from tb where key_column=x order by key_column
      
      2. 通过第一步获取的主键来获取对应的值。
      for each pk_column value in rest do:
      select non_key_column from tb where pk_column=val
      
  2. 使用MRR的情况

    • 优化器将二级索引查询到的记录放到一块缓冲区中

    • 如果二级索引扫描到文件的末尾或者缓冲区已满,则使用快速排序对缓冲区中的内容按照主键进行排序

    • 根据排序后的主键去聚簇索引访问实际的数据文件(将某些范围查询,拆分为键值对,来进行批量的数据查询)

      select non_key_column from tb where ey_column=x;
      
      1. 先根据where条件中的辅助索引获取辅助索引与主键的集合,结果集为rest
      select key_column, pk_column from tb where key_column = x order by key_column
      
      2. 将结果集rest放在buffer里面(read_rnd_buffer_size 大小直到buffer满了),然后对结果集rest按照pk_column排序,得到结果集是rest_sort
      
      3. 利用已经排序过的结果集,访问表中的数据,此时是顺序IO.
      select non_key_column fromtb where pk_column in (rest_sort)
      
      
  3. 在没有MRR的情况下,访问主键索引的次数会增加,非聚集索引得到多少行,基本就要回表多少次,而使用MRR,大约为rows/buffer_size,即MRR是一批数据回表一次,不是每一行回表一次

  4. MRR优化的好处

    • 使数据访问变得较为顺序
    • 减少缓冲池中页被替换的次数
    • 批量处理对键值的查询操作
  5. 当优化器使用了 MRR 时,执行计划的 Extra 列会出现 “Using MRR”

  6. 查看MRR是否开启,mrr_cost_based=on 表示是否通过 cost based 的方式来选择使用 MRR

    select @@optimizer_switch 
    
    index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,subquery_materialization_cost_based=on,use_index_extensions=on
    
  7. 查看buffer大小。参数用来控制键值的缓冲区大小,默认 256K,当大于该参数值时,执行器根据主键对已缓存的数据进行排序,然后再通过主键取得行数据。

    select @@read_rnd_buffer_size ; 
    

验证MRR

  1. DROP TABLE IF EXISTS `t_abc2`;
    CREATE TABLE `t_abc2` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `a` int(11) NOT NULL COMMENT 'a',
      `b` int(11) NOT NULL COMMENT 'b',
      `c` int(11) NOT NULL COMMENT 'c',
      PRIMARY KEY (`id`),
      KEY `idx_a_b` (`a`,`b`) USING BTREE,
      KEY `idx_c` (`c`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  2. 关闭MRR

    set optimizer_switch='mrr=off';
    
  3. 插入数据

    delimiter ;;
    create procedure crateabc2data()
    begin
     declare i int;
     set i=1;
     while(i<=100)do
       insert into t_abc values(i, i*2,i*3, i*4);
       set i=i+1;
     end while;
    end;;
    delimiter ;
    call crateabc2data();
    
  4. 查看执行计划

    explain select * from t_abc2 where (a between 1 and 20) and (c between 1 and 20) ;
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LzYXTkLA-1583500072449)(https://gitee.com/jannal/images/raw/master/mysql/image-20190902112637345.png)]

  5. 开启MRR,重启mysql(清除buffer,避免影响结果)

    set optimizer_switch='mrr=on,mrr_cost_based=off';
    
    在MySQL5.6以及5.7版本中,基于成本的算法都过于保守,导致大部分情况下优化器都不会选择mrr特性。建议关闭基于成本的算法来确定是否需要开启MRR特性,以保证有效开启MRR
    
  6. 查看执行计划

    explain select * from t_abc2 where (a between 1 and 20) and (c between 1 and 20) ;
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GjD8bIv5-1583500072450)(https://gitee.com/jannal/images/raw/master/mysql/image-20190902115315374.png)]

ICP

  1. ICP(Index Condition Pushdown),索引下推是mysql5.6开始支持的一种根据索引进行查询的优化方式。部分WHERE条件能使用索引中的字段,MySQL Server 会把这部分下推到引擎层。存储引擎通过使用索引条目,然后下推索引条件进行评估,使用这个索引把满足的行从表中读取出。ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数。总之是 ICP的优化在引擎层就能够过滤掉大量的数据,这样无疑能够减少了对base table和mysql server的访问次数

  2. 没有ICP的情况:当进行索引查询时,首先根据索引来查记录,然后根据where条件来过滤记录。比如对于以下语句

    select * from t_user where register_date ='2019-08-08' and username like '%jannal%'
    假设register_date,username建立联合索引
    

    Mysql可以只能通过索引从存储层先抓取所有符合register_date='2019-08-08’的记录,然后再Server层过滤where之后的条件

  3. 在有ICP的情况:当进行索引查询时,首先根据索引和where条件来查记录,即在where的部分在过滤放在了存储层,在某些查询下,可以大大减少上层SQL层对记录的抓取,从而提升数据库性能。比如对于以下语句

    select * from t_user where register_date ='2019-08-08' and username like '%jannal%'
    假设register_date,username建立联合索引
    

    Mysql通过索引在存储层抓取数据时,可以通过where条件直接过滤存储层的数据,过滤完了再返回所有符合的记录给Server层,这将大大提高查询效率。当然where可以过滤的条件是要该索引可以覆盖到的范围

  4. Index Condition Pushdown支持

    • range
    • ref
    • eq_ref
    • ref_or_null
  5. 当优化器选择Index Condition Pushdown优化时,可在执行计划的列Extra看到Using index condition

  6. ICP的使用限制

    • 当sql需要全表访问时,ICP的优化策略可用于range, ref, eq_ref, ref_or_null 类型的访问数据方法

    • 支持InnoDB和MyISAM表

    • ICP只能用于二级索引,不能用于主索引

    • 并非全部where条件都可以用ICP筛选,如果where条件的字段不在索引列中,还是要读取整表的记录到Server端做where过滤

    • ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例

    • 5.6 版本的不支持分表的ICP 功能,5.7 版本的开始支持

    • 当 SQL 使用覆盖索引时但只检索部分数据时,ICP 无法使用

    • 在查询的时候即使正确的使用索引的前N个字段(即遵循前缀索引的原则),还是会用到 ICP,无故的多了 ICP 相关的判断,这应该是一个退化的问题

验证ICP

  1. 准备数据

    DROP TABLE IF EXISTS `t_person`;
    CREATE TABLE `t_person` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `desc` varchar(50) NOT NULL COMMENT '描述',
      `username` varchar(32) NOT NULL COMMENT '用户名',
      `age` int(11) NOT NULL COMMENT '年龄',
      `sex` char(1) NOT NULL COMMENT '性别,M和F',
      `create_time` datetime NOT NULL COMMENT '创建时间',
      PRIMARY KEY (`id`),
      KEY `idx_username` (`age`,`username`,`desc`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    插入数据
         		Person person = null;
            String sex = null;
            ArrayList<Person> personList = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                person = new Person();
                person.setAge(1);
                int random = RandomUtils.getRandom(1, 200000);
                sex = random % 2 == 1 ? "M" : "F";
                person.setSex(sex);
                person.setUsername("a"+i);
                person.setCreateTime(RandomUtils.randomDate("2013-07-01", "2019-08-29"));
                person.setDesc(person.toString());
                personList.add(person);
            }
            System.out.println("数据产生完毕");
            personService.insertBatch(personList);
    
  2. 关闭ICP性能测试

    set optimizer_switch="index_condition_pushdown=off";
    1. 查看性能参数
    select * from t_person where age = 1 and `desc` like '%1%';
    
    starting							0.0000560			7.301
    checking permissions	0.0000060			0.782
    Opening tables				0.0000410			5.346
    init									0.0000230			2.999
    System lock						0.0000040			0.522
    optimizing						0.0000030			0.391
    statistics						0.0000080			1.043
    preparing							0.0000070			0.913
    Creating tmp table		0.0000120			1.565
    Sorting result				0.0000020			0.261
    executing							0.0003060			39.896
    Sending data					0.0001700			22.164
    Creating sort index		0.0000270			3.520
    end										0.0000030			0.391
    removing tmp table		0.0000030			0.391
    end										0.0000020			0.261
    query end							0.0000020			0.261
    closing tables				0.0000020			0.261
    removing tmp table		0.0000020			0.261
    closing tables				0.0000020			0.261
    freeing items					0.0000750			9.778
    cleaning up						0.0000110			1.434
    
    2. show session status like '%Handler_read_rnd%';
    
    
    Handler_read_rnd	86
    Handler_read_rnd_next	1105
    
    3. 查看执行计划
    explain select * from t_person where age = 1 and `desc` like '%1%';
    
    
    id	select_type	table	type	possible_keys			key					key_len	ref	  rows	Extra
    1		SIMPLE			t_person		ref	idx_username	idx_username	4			const	4990	Using index condition
    

  1. 打开ICP性能测试

    set optimizer_switch="index_condition_pushdown=on";  默认时开启的
    
    1. 查看性能参数
    select * from t_person where age = 1 and `desc` like '%1%';
    
    starting							0.0000270	2.576
    checking permissions	0.0000060	0.573
    Opening tables				0.0000250	2.385
    init									0.0000070	0.668
    System lock						0.0000030	0.286
    optimizing						0.0000030	0.286
    statistics						0.0000070	0.668
    preparing							0.0000070	0.668
    executing							0.0003150	30.057
    Sending data					0.0005690	54.294
    end										0.0000110	1.050
    query end							0.0000030	0.286
    closing tables				0.0000030	0.286
    removing tmp table		0.0000070	0.668
    closing tables				0.0000040	0.382
    freeing items					0.0000360	3.435
    cleaning up						0.0000150	1.431
    
    
    2. show session status like '%Handler_read%';
    
    
    Handler_read_rnd	49
    Handler_read_rnd_next	561
    
    3. 查看执行计划
    explain select * from t_person where age = 1 and `desc` like '%1%';
    
    
    id	select_type	table	type	possible_keys			key					key_len	ref	  rows	Extra
    1		SIMPLE			t_person		ref	idx_username	idx_username	4			const	4990	Using index condition
    
  2. 在二级索引是复合索引且前面的条件过滤性较低的情况下(这里age一直都是1),打开 ICP 可以有效的降低 server 层和 engine 层之间交互的次数,从而有效的降低在运行时间。

  3. 在查询的时候即使正确的使用索引的前N个字段(即遵循前缀索引的原则),还是会用到 ICP,无故的多了 ICP 相关的判断,这应该是一个退化的问题

    mysql> explain select * from t_person where age = 1 and username = 'a200';
    +----+-------------+----------+------+---------------+--------------+---------+-------------+------+-----------------------+
    | id | select_type | table    | type | possible_keys | key          | key_len | ref         | rows | Extra                 |
    +----+-------------+----------+------+---------------+--------------+---------+-------------+------+-----------------------+
    |  1 | SIMPLE      | t_person | ref  | idx_username  | idx_username | 102     | const,const |    1 | Using index condition |
    +----+-------------+----------+------+---------------+--------------+---------+-------------+------+-----------------------+
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!