zoukankan      html  css  js  c++  java
  • MySQL学习(七) 索引选择(半原创)

    概述

    该篇文章主要阐述一个例子(例子来自参考资料,侵删),然后总结今天相关的知识点。

    例子 (例子来自参考文章,非原创)

    创建表并插入数据,并执行查询

    CREATE TABLE `t` (
      `id` int(11) NOT NULL,
      `a` int(11) DEFAULT NULL,
      `b` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `a` (`a`),
      KEY `b` (`b`)
    ) ENGINE=InnoDB;
    
    
    
    delimiter ;;
    create procedure idata()
    begin
      declare i int;
      set i=1;
      while(i<=100000)do
        insert into t values(i, i, i);
        set i=i+1;
      end while;
    end;;
    delimiter ;
    call idata();
    
    
    mysql> select * from t where a between 10000 and 20000;
    
    
    

    1297993-20200221141343123-1792799911.png

    可以看到该语句查询使用到了索引,然后进行如下操作

    1297993-20200221141533317-1436448332.png

    下面的三条SQL语句,就是这个实验过程。

    set long_query_time=0;
    select * from t where a between 10000 and 20000; /*Q1*/
    select * from t force index(a) where a between 10000 and 20000;/*Q2*/
    
    • 第一句,是将慢查询日志的阈值设置为0,表示这个线程接下来的语句都会被记录入慢查询日志中;
    • 第二句,Q1是session B原来的查询;
    • 第三句,Q2是加了force index(a)来和session B原来的查询语句执行情况对比。 我们在第三条语句指定了强制使用a 索引,假如第三天语句执行的时间快过第二条的,那么我们可以认为数据库选错了索引,相反没有选错, 如图3所示是这三条SQL语句执行完成后的慢查询日志。

    1297993-20200224095820158-131253931.png

    可以看到数据库确实选错了索引,我们要知道为什么选错了索引就要知道数据库是如何选择索引的。

    优化器

    我们从开始MySQL 架构图可以知道执行语句要经过一个优化器的组件,这个组件就相当于决策大脑,为语句选择合适的索引。

    分析

        一个索引上不同的值的个数,我们称之为“基数”(cardinality)。也就是说,这个基数越大,索引的区分度越好。 而选择索引肯定看区分度高的索引,区分度高的索引能够准确找到符合条件的记录。思路如下 : 选择索引 -> 选择区分度高的索引 --> 如何找到区分度高的索引 --> 抽样统计算法
        我们可以使用show index方法,看到一个索引的基数。如图4所示,就是表t的show index 的结果 。虽然这个表的每一行的三个字段值都是一样的,但是在统计信息中,这三个索引的基数值并不同,而且其实都不准确。

    1297993-20200224100228758-948133202.png

        假如数据库一行行去统计,对于大的表肯定是不行的,于是数据库就使用抽样统计。 采样统计的时候,InnoDB默认会选择N个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过1/M的时候,会自动触发重新做一次索引统计。在MySQL中,有两种存储索引统计的方式,可以通过设置参数innodb_stats_persistent的值来选择:

    • 设置为on的时候,表示统计信息会持久化存储。这时,默认的N是20,M是10。
    • 设置为off的时候,表示统计信息只存储在内存中。这时,默认的N是8,M是16。 由于是采样统计,所以不管N是20还是8,这个基数都是很容易不准的。扫描的行数是一方面,还有一方面,例如使用非聚集索引的话还需要回表也是需要时间成本,优化器会从各个方面综合考虑最终得出最优解。

    矫正统计错误

        可以看到我们上面的统计是存在误差的,那么纠正这个偏差的方法肯定是让优化器再次统计一下。

    1297993-20200224101718063-1600097088.png

    补充题外话

    count(*)、count(主键id)、count(字段)和count(1) 的区别?

    count(*)、count(主键id)和count(1) 都表示返回满足条件的结果集的总行数; 而count(字段),则表示返回满足条件的数据行里面,参数“字段”不为NULL的总个数。

    对于count(主键id)来说,InnoDB引擎会遍历整张表,把每一行的id值都取出来,返回给server层。server层拿到id后,判断是不可能为空的,就按行累加。

    注意哦,sever 自己判断

    对于count(1)来说,InnoDB引擎遍历整张表,但不取值。server层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。

    单看这两个用法的差别的话,你能对比出来,count(1)执行得要比count(主键id)快。因为从引擎返回id会涉及到解析数据行,以及拷贝字段值的操作。

    对于count(字段)来说:

    如果这个“字段”是定义为not null的话,一行行地从记录里面读出这个字段,判断不能为null,按行累加;

    如果这个“字段”定义允许为null,那么执行的时候,判断到有可能是null,还要把值取出来再判断一下,不是null才累加。

    也就是前面的第一条原则,server层要什么字段,InnoDB就返回什么字段。

    #### 但是count()是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count()肯定不是null,按行累加。

    按照效率排序的话,count(字段)<count(主键id)<count(1)≈count(),所以我建议你,尽量使用count()

    InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
    
    

    参考资料

    • 《MySQL 45讲》
  • 相关阅读:
    C#关于日期 月 天数 和一年有多少周及根据某年某周获取时间段的计算
    配置tomcat
    C# 动态编译及反射执行
    数据库日志自增量太大导致超时
    validateRequest验证引发错误“检测到有潜在危险的 Request.Form 值”
    入手了DELL Ultrabook,还不错哦!
    Spring.NET学习笔记1——控制反转(基础篇)
    Win7 64位下Kindle4、闪迪Cruzer BladeU盘等无法安装驱动问题的解决方法
    转:CentOS 5.5下LVM分区在线扩容
    [转]关于64位Sublime Text 2破解注册码的方法
  • 原文地址:https://www.cnblogs.com/Benjious/p/12355815.html
Copyright © 2011-2022 走看看