zoukankan      html  css  js  c++  java
  • mysql本身用错索引+给字符串字段加索引

    mysql为什么有时会选错索引

    • 场景例子:一张表里有a,b两个字段,并分别建立以下索引

      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;
      
      • 表中数据从(1,1,1)到(100000,100000,100000)共10万行记录。

      • 接下来分析一条sql语句:select * from t where a between 10000 and 20000;

      • 做如下操作:

      • 我们可以设置慢日志阈值为0,表示这个线程接下来的语句都会被记入慢查询日志中;set long_query_time=0;可以看到执行B时,走了全表扫描,没有走索引。

    • 为什么没有按指定的a索引扫描呢?

      • 而优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的CPU资源越少。
        当然,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。我们这个简单的查询语句并没有涉及到临时表和排序,所以MySQL选错索引肯定是在判断扫描行数的时候出问题了。
      • 基数:
        • mysql进行扫描前,并不知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。即索引区分度,一个索引上不同的值越多,这个区分度越好,而一个索引上不同的值的个数称为基数,基数越大,索引区分度越好。可以使用show index from 表名,查看结果。
      • 优化器会估算回表的代价,同时评估主键索引扫描的没有额外代价,选择执行哪个扫描。
      • 使用analyze table 表名 命令,可以重新统计索引信息。纠正错误结果
    • 另外一个语句:mysql> select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1

      • 如果使用索引a进行查询,那么就是扫描索引a的前1000个值,然后取到对应的id,再到主键索引
        上去查出每一行,然后根据字段b来过滤。显然这样需要扫描1000行。
        如果使用索引b进行查询,那么就是扫描索引b的最后50001个值,与上面的执行过程相同,也是
        需要回到主键索引上取值再判断,所以需要扫描50001行。

      • 可以看到,返回结果中key字段显示,这次优化器选择了索引b,而rows字段显示需要扫描的行
        数是50198。
        从这个结果中,你可以得到两个结论:

        1. 扫描行数的估计值依然不准确;
        2. 这个例子里MySQL又选错了索引。
      • 原本可以执行得很快的SQL语句,执行速度却比你预期的慢很多,你应该怎么办呢?

        • 一种方法是,像我们第一个例子一样,采用force index强行选择一个索引。
        • 第二种方法就是,我们可以考虑修改语句,引导MySQL使用我们期望的索引。比如,在这个例子里,显然把“order by b limit 1” 改成“order by b,a limit 1” ,语义的逻辑是相同的。
          • 之前优化器选择使用索引b,是因为它认为使用索引b可以避免排序(b本身是索引,已经是有序的了,如果选择索引b的话,不需要再做排序,只需要遍历),所以即使扫描行数多,也判定为代价更小。
            现在order by b,a 这种写法,要求按照b,a排序,就意味着使用这两个索引都需要排序。因此,扫描行数成了影响决策的主要条件,于是此时优化器选了只需要扫描1000行的索引a。
        • 第三种方法是,在有些场景下,我们可以新建一个更合适的索引,来提供给优化器做选择,或删掉误用的索引。(删除索引b)

    怎么给字符串字段加索引

    • 场景例子:维护一个由邮箱构成的登录系统数据库

    • 两种索引方式:

      • alter table SUser add index index1(email);
        • 这一种包含了每个记录的整个字符串
      • alter table SUser add index index2(email(6));
        • 对每个记录只取前6个字节
        • 优点:占用空间小
        • 增加额外的扫描次数
    • 如果使用index1扫描顺序

      • sql语句:select id,name,email from SUser where email='hjjxyz@xxx.com'
      1. 先在index1索引树找到主键是ID2的行,判断email值是否正确,加入到结果集;
      2. 到主键上查找主键值是ID2的行,判断email是否正确,加入到结果集
      3. 在index1所引述刚刚查到的位置吓一跳记录,发现不满足条件,循环结束。
      • 整个过程值回了一次表,可以认为只扫描了一行。
    • 如果用index2顺序

      1. 在index2索引树找到满足索引值是hjjxyz的记录,找到了ID1
      2. 从主键上查找ID1的行,判断是否满足where条件,不是则丢弃
      3. 在index2查到的位置继续查找下一条,仍然是hjjxyz,取出,再到ID索引树判断,满足where条件,加入到结果集
      4. 重复上一步,直到index2不是hjjxyz,循环结束
      • 在整个过程中,回表了多次,相当于扫描了多行
    • 如果将index的长度变长,这样区分度更大,这样扫描的次数就减少了,但是占用了空间也变大了,那么如何控制好索引长度呢?

      • 首先 select count(distinct email) as L from SUser;算出这个列有多少不同的值

      • 查看4-7个字节的前缀索引,查出的个数

      • mysql> select
        count(distinct left(email,4))as L4,
        count(distinct left(email,5))as L5,
        count(distinct left(email,6))as L6,
        count(distinct left(email,7))as L7,
        from SUser;
        
      • 如果可以得到95%以上的数据时就可以选取该长度

    • 前缀索引一定要回表,无法使用覆盖索引

      • 如果用index1可以直接返回结果,不需要回表,但是如果是index2即使字段很长,innodb还是要回表查询。
    • 如何最大化使用前缀索引提高搜索性能?

      • 比如身份证,前6位如果是一个区的完全相同,区分度不高
      1. 倒叙存储 select field_list from t where id_card = reverse('身份证号码')
      2. 使用hash字段,在表上创建一个新字段,来hash保存身份证号的值,重复概率很小,可以认为每次只扫描了一行
      • 这两种方式都不支持范围扫描
  • 相关阅读:
    简单说说数据库表设计的三种范式
    存储过程简单的动态订单号
    Asp.Net页面生命周期
    jq 小笔记,上传判断其格式
    吃一垫长一智
    离散事件模拟
    二叉树查找树
    冷暖自知
    基督徒的人生箴言
    迷宫寻路
  • 原文地址:https://www.cnblogs.com/jimmyhe/p/11036208.html
Copyright © 2011-2022 走看看