mysql9

复习

  • 不要在索引上加操作
  • count查询、limit分页优化
    • count中null处理是分裂的
    • 分页中通过id查询
  • 分区表
    • 把一个物理大表拆分成很多子表
    • 不可控
    • 仍然在一台机器上,性能有限
    • 市面上的分库分表,mycat、shardingjdbc、oneproxy
  • 索引合并
    • 多个二级索引,充分利用多个二级索引
    • 如果查看执行计划,发现用到了索引合并,还可以考虑用联合索引替换索引合并

5.mysql的底层执行原理

连接查询

  • 全连接,俗称的笛卡尔积

内连接和外连接

  • 实例表

    • student

      • numbernamemajor
        20200901Jack网络工程
        20200902Mark计算机科学
        20200903James计算机科学
        20200904King软件工程
    • score

      • numbersubjectscore
        20200901数据结构和算法88
        20200901网络通信原理90
        20200902数据结构和算法95
        20200902网络通信原理89
        20200903离散数学70
  • 要统计每一个人的成绩

    • SELECT s1.number, s1.name, s2.subject, s2.score FROM student AS s1, score

      AS s2 WHERE s1.number = s2.number;

    • 由于king没有考试成绩,所以没有这条记录

      • 假如要显示这条记录,就就提出了内连接和外连接概念
  • 内连接

    • 对于内连接的两个表,驱动表中的记录在被驱动表中找不到匹配的记录,该

      记录不会加入到最后的结果集,我们上边提到的连接都是所谓的内连接。

  • 外连接

    • 对于外连接的两个表,驱动表中的记录即使在被驱动表中没有匹配的记录,

      也仍然需要加入到结果集。

左外连接
  • SELECT * FROM e1 LEFT [OUTER] JOIN e2 ON 连接条件 [WHERE 普通过滤条件];

  • left join 是左边的表是驱动表

右外连接
  • SELECT * FROM e1 RIGHT [OUTER] JOIN e2 ON 连接条件 [WHERE 普通过滤条件];

  • right join 是右边的表是驱动表

  • where条件

    • WHERE 子句中的过滤条件就是我们平时见的那种,不论是内连接还是外连

      接,凡是不符合 WHERE 子句中的过滤条件的记录都不会被加入最后的结果集。

  • on条件

    • 对于外连接的驱动表的记录来说,如果无法在被驱动表中找到匹配 ON 子句

      中的过滤条件的记录,那么该记录仍然会被加入到结果集中,对应的被驱动表记

      录的各个字段使用 NULL 值填充。

    • 需要注意的是,这个 ON 子句是专门为外连接驱动表中的记录在被驱动表找

      不到匹配记录时应不应该把该记录加入结果集这个场景下提出的,所以如果把ON 子句放到内连接中,MySQL 会把它和 WHERE 子句一样对待,也就是说:内

      连接中的 WHERE 子句和 ON 子句是等价的。

内连接
  • 内连接涉及两表的查询条件,用on和用where是等价的
  • 写法
    • select * from e1,e2
    • select * from e1 join e2
    • select * from e1 inner join e2
    • select * from e1 cross join e2
  • 对于内连接,把谁写在前,谁写在后,没有任何影响,真正决定谁是驱动表,谁是被驱动表,只有在执行的时候才会决定

嵌套循环连接(Nested-Loop Join)

  • 连接查询的伪代码可以认为是

    • for each row in e1 {   #此处表示遍历满足对e1单表查询结果集中的每一条记录
      
          for each row in e2 {   #此处表示对于某条e1表的记录来说,遍历满足对e2单表查询结果集中的每一条记录
      
                  for each row in e3 {   #此处表示对于某条e1和e2表的记录组合来说,对t3表进行单表查询
      
                  if row satisfies join conditions, send to client
      
              }
          }
      }
      
      
    • 时间复杂度是O(n乘m乘l)

  • 驱动表只被访问一次,被驱动表被访问多次,如果被驱动表是全表扫描的话,驱动表被查出来多少条数据,被驱动表就要进行多少次全表扫描,这是灾难性的

使用索引加快连接速度

  • SELECT * FROM e1, e2 WHERE e1.m1 > 1 AND e1.m1 = e2.m2 AND e2.n2 < ‘d’;
    • 对e2表的m2或者n2建立索引

基于块的嵌套循环连接(Block Nested-Loop Join)

  • 被驱动表中的数据需要反复从磁盘读到内存,然后释放,然后又读到内存,需要重复n次(n为驱动表中查出来满足条件的数据数)

  • mysql相想办法解决上述问题

    • 事先在内存中创建一块内存join buffer,将驱动表中的多条数据装载到join buffer中,当去被驱动表中查询时,将这些join buffer中的多条数据,一次性和被驱动进行匹配
  • 在extra中,有一种提示using join buffer(Block Nested Loop)

  • show variables like ‘join_buffer_size’

    • 单位是字节,默认是256kb
  • 连接查询除了对被驱动关联条件字段加索引外,如果机器的内存足够,还可以想办法调大join buffer的大小

  • 无论单表还是多表,为什么不提倡使用 select * from

    • select中的列和where条件中的列才会往join buffer中放
      • select * from e1,e2,就会把e1中所有的列放入join buffer中
      • select e1.m1 from e1,e2,就只会把e1的m1列放入join buffer中,自然能放更多的数据
    • 那什么时候情况下可以使用select * from,如果是要把查询的数据中放入缓存

Mysql的查询成本计算

什么是成本?

  • io成本
    • 我们的表经常使用的 MyISAM、InnoDB 存储引擎都是将数据和索引都存储到 磁盘上的,当我们想查询表中的记录时,需要先把数据或者索引加载到内存中然 后再操作。这个从磁盘到内存这个加载的过程损耗的时间称之为 I/O 成本。
  • cpu成本
    • 读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作 损耗的时间称之为 CPU 成本。
  • 对于 InnoDB 存储引擎来说,页是磁盘和内存之间交互的基本单位,MySQL规定读取一个页面花费的成本默认是 1.0,读取以及检测一条记录是否符合搜索条件的成本默认是 0.2。1.0、0.2 这些数字称之为成本常数,这两个成本常数我们最常用到,当然还有其他的成本常数。
  • 注意,不管读取记录时需不需要检测是否满足搜索条件,其成本都算是 0.2。

单表查询的成本

基于成本的优化步骤
  • 1、根据搜索条件,找出所有可能使用的索引
    • possible_keys
  • 2、计算全表扫描的代价
  • 3、计算使用不同索引执行查询的代价
  • 4、对比各种执行方案的代价,找出成本最低的那一个

实战分析

示例sql
  • SELECT * FROM order_exp WHERE order_no IN (‘DD00_6S’, ‘DD00_9S’, ‘DD00_10S’) AND expire_time> ‘2021-03-22 18:28:28’ AND expire_time<= ‘2021-03-22 18:35:09’ AND insert_time> expire_time AND order_note LIKE ‘%7排1%’ AND order_status = 0;

  • 建立的索引

    • u_idx_day_status (insert_time, order_status, expire_time)
    • idx_order_no (order_no)
    • idx_expire_time (expire_time)
  • 分析过程

    • 1.索引使用

      • order_no IN (‘DD00_6S’, ‘DD00_9S’, ‘DD00_10S’)
      • expire_time> ‘2021-03-22 18:28:28’ AND expire_time<= ‘2021-03-22 18:35:09’
    • 2.全表扫描,就是把聚簇索引中的全部数据读取到内存中,然后判断是否满足where条件

      • 1.包括io成本和cpu成本

      • 2.成本计算需要两个数据,数据占用页面数和数据行数,mysql有一系列统计数据,show table like ‘order_exp’\G,其中Rows就是行数,不过是一个估计值,Data_length是聚簇索引的字节数,1589248,除以16除以1024,等于97个页面

        • 97 x 1.0 + 1.1 = 98.1

        • 10350x 0.2 + 1.0 = 2071

        • TIPS:我们前边说过表中的记录其实都存储在聚簇索引对应B+树的叶子节点

          中,所以只要我们通过根节点获得了最左边的叶子节点,就可以沿着叶子节点组

          成的双向链表把所有记录都查看一遍。也就是说全表扫描这个过程其实有的B+树非叶子节点是不需要访问的,但是 MySQL在计算全表扫描成本时直接使用聚簇索引占用的页面数作为计算 I/O 成本的依据,是不区分非叶子节点和叶子节点的。

    • 3.分开计算两个索引的成本,最后计算一下合并索引的成本

      • expire_time,使用成本,对应的范围区间就是: (‘2021-03-22 18:28:28’ , ‘2021-03-22 18:35:09’ ),使用 idx_expire_time 搜索会使用用二级索引 + 回表方式的查询,MySQL 计算这种查询的成本依赖两个方面的数据,

        • 1、范围区间数量

          不论某个范围区间的二级索引到底占用了多少页面,查询优化器认为读取索引的一个范围区间的 I/O 成本和读取一个页面是相同的。本例中使用 idx_expire_time 的范围区间只有一个,所以相当于访问这个范围区间的二级索引付出的 I/O 成本就是:1 x 1.0 = 1.0

        • 2、需要回表的记录数

          优化器需要计算二级索引的某个范围区间到底包含多少条记录,对于本例来说就是要计算 idx_expire_time 在(‘2021-03-22 18:28:28’ ,‘2021-03-22 18:35:09’) 这个范围区间中包含多少二级索引记录

          • 如果两个区间端点所在的页面,相差不超过10个页面,在mysql5.7里面会做一个精确统计,超过10个页面,会统计前10个页面,每个页面相隔数据量的平均值,然后求取总数

          • explain select * from order_exp where expire_time> ‘2021-03-22 18:28:28’ AND expire_time<= ‘2021-03-22 18:35:09’

            • 一共要扫描39行
          • 读取的cpu成本,39*0.2+0.01 = 7.81

          • 回表的io成本,39*1.0 = 39

          • 过滤检查是否符合,回表的cpu成本,39*0.2=7.8

          • 总成本 1.0+7.81+39+7.8 = 55.61

      • order_no

        • explain SELECT * FROM order_exp WHERE order_no IN (‘DD00_6S’, ‘DD00_9S’,

          ‘DD00_10S’);

        • 由于使用 idx_expire_time 时有 3 个单点区间,所以每个单点区间都需要查找

          一遍对应的二级索引记录数,三个单点区间总共需要回表的记录数是 58。扫描区间的io成本是 3*1.0 = 3.0

        • 读取这些二级索引记录的 CPU 成本就是:58 x 0.2 + 0.01 = 11.61

        • 根据这些记录里的主键值到聚簇索引中做回表操作,所需的 I/O 成本就是:

          58 x 1.0 = 58.0

        • 回表操作后得到的完整用户记录,然后再比较其他搜索条件是否成立

          此步骤对应的 CPU 成本就是: 58 x 0.2 = 11.6

        • 总成本 3.0+11.61+58+11.6=84.21

    • 4.执行计划

      • 全表:2169.1

      • expire_time:55.61

      • order_no:82.21

      • 所以最终选择用expire_time的索引

      • 实际比较成本时,索引计算成本时不会算上最后一次的回表成本,idx_expire_time为47.81(55.61-7.8),idx_order_no为72.61(84.21-11.6)。

    • explain SELECT * FROM order_exp WHERE order_no IN (‘DD00_6S’, ‘DD00_9S’, ‘DD00_10S’) AND expire_time> ‘2021-03-22 18:28:28’ AND expire_time<= ‘2021-03-22 18:35:09’ AND insert_time> expire_time AND order_note LIKE ‘%7排1%’ AND order_status = 0;

      • 可能的索引是expire_time和order_no,最终使用的索引是order_no

基于索引统计数据的成本计算

index dive
  • SELECT * FROM order_exp WHERE order_no IN (‘aa1’, ‘aa2’, ‘aa3’, … , ‘zzz’);

  • MySQL 把这种通过直接访问索引对应的 B+树来计 算某个范围区间对应的索引记录条数的方式称之为 index dive。

  • 有零星几个单点区间的话,使用 index dive 的方式去计算这些单点区间对应的记录数也不是什么问题,如果 IN 语句里 20000 个参数怎么办?

    • 这就意味着 MySQL 的查询优化器为了计算这些单点区间对应的索引记录条数,要进行 20000 次 index dive 操作,这性能损耗就很大,搞不好计算这些单点 区间对应的索引记录条数的成本比直接全表扫描的成本都大了。MySQL 考虑到了 这种情况,所以提供了一个系统变量 eq_range_index_dive_limit,我们看一下在 MySQL 5.7.21 中这个系统变量的默认值:200
    • show variables like ‘%dive%’;
  • 如果我们的 IN 语句中的参数个数大于或等于 200 个的话,就不能使用 index dive 了,要使用所谓的索引统计数据来进行估算

    • show index from order_exp;

      • Non_unique :索引列的值是否是唯一的
      • Seq_in_index :索引列在索引中的位置
      • Collation:索引列中的值是按照何种排序方式存放的,值为 A 时代表升序存放,为 NULL 时代表降序存放。
      • Cardinality:索引列中不重复值的数量,这是估计值,不是精确值
        • id:10345
        • insert_time:990
        • order_status:990
        • expire_time(联合索引中的第三列):10350
        • order_no:10220
        • expire_time(索引idx_expire_time中的列):9802
    • SHOW TABLE STATUS 展示出的 Rows 值,也就是一个表中有多少条记录。之前统计出来是10350

    • 以 order_exp 表的 idx_order_no 索引为例,它的 Rows 值是 10350,它对应 的 Cardinality 值是 10220,所以我们可以计算 order_no 列平均单个值的重复次数 就是:

      • 10350÷ 10220≈ 1.012(条)

      • 假设 IN 语句中有 20000 个参数的话,就直接使用统计数据来估算这些参数 需要单点区间对应的记录条数了,每个参数大约对应 1.012 条记录,所以总共需 要回表的记录数就是:

        20000 x 1.012= 21,730

  • 当你的查询中使用到了 IN 查询,但是却实际没有 用到索引,就应该考虑一下是不是由于 eq_range_index_dive_limit 值太小导致的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值