Mysql为什么选择走全表/索引:内核查询成本计算规则

trace工具的介绍

使用方式

‐‐ 开启trace
SET optimizer_trace="enabled=on";
-- 查询数据
select * from file_info where file_name > 'a' order by create_time;
-- 查看trace信息
SELECT * FROM information_schema.OPTIMIZER_TRACE;

常见的trace字段信息

  • "join_preparation": ‐‐第一阶段:SQL准备阶段,格式化sql
  • "join_optimization": ‐‐第二阶段:SQL优化阶段
  • "condition_processing": ‐‐条件处理
  • "table_dependencies": ‐‐表依赖详情
  • "rows_estimation": ‐‐预估表的访问成本
  • "table_scan": ‐‐全表扫描情况
  • "potential_range_indexes": ‐‐查询可能使用的索引
  • "index": "PRIMARY", ‐‐主键索引
  • "index": "idx_name_age_position", ‐‐辅助索引
  • "analyzing_range_alternatives": ‐‐分析各个索引使用成本
  • "rowid_ordered": false, ‐‐使用该索引获取的记录是否按照主键排序
  • "index_only": false, ‐‐是否使用覆盖索引
  • "rows": 5061, ‐‐索引扫描行数
  • "cost": 6074.2, ‐‐索引使用成本
  • "chosen": false, ‐‐是否选择该索引
  • "best_access_path": ‐‐最优访问路径
  • "considered_access_paths": ‐‐最终选择的访问路径
  • "access_type": "scan", ‐‐访问类型:为scan,全表扫描
  • "chosen": true, ‐‐确定选择
  • "join_execution": ‐‐第三阶段:SQL执行阶段

最终选择方式

  • 从索引,全表扫描中选择cost值最小的一种进行执行

一条sql会产生什么成本

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

单表查询的成本

  • 在一条单表查询语句真正执行之前,MySQL的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案,这个成本最低的方案就是所谓的执行计划,之后才会调用存储引擎提供的接口真正的执行查询。

单表查询的索引选择过程

  • 根据搜索条件,找出所有可能使用的索引:possible keys
  • 计算全表扫描的代价:I/O成本(扫描的页数)* 1.0 + 1.1(固定值) + CPU成本(行数) * 0.2 + 1.0(固定值)
  • 计算使用不同索引执行查询的代价
  • 对比各种执行方案的代价,找出成本最低的那一个

全表扫描的记录行数的值从哪里来?

  • MySQL为每个表维护了一系列的统计信息!

  • 使用 SHOW TABLE STATUS 语句来查看表的统计信息
    统计信息.png

  • 得到的Rows列信息:对于使用MyISAM存储引擎的表来说,该值是准确的,对于使用InnoDB存储引擎的表来说,该值是一个估计值。

  • Data_length:表示表占用的存储空间字节数。

  • MyISAM存储引擎的表来说,该值就是数据文件的大小。
  • 对于使用InnoDB存储引擎的表来说,该值就相当于聚簇索引占用的存储空间大小,也就是说可以这样计算该值的大小:
    Data_length = 聚簇索引的页面数量 x 每个页面的大小
    对于我刚才的结果:49152 ÷ 16 ÷ 1024 = 3页
  • 全表扫描成本:87*0.2 + 1.1 + 3*1 + 1.0=90

计算使用不同索引执行查询的代价

  • 范围区间数量:一个连续的范围IO成本就是1。
  • in的数量:in中的每一个字符都是一个区间。也就是说in的每一条记录都是一次回表。
  • 需要回表的记录数:估算这个这个范围内需要回表的次数。mysql5.7版本涉及到的页面小于10页,精确计算;如果涉及到的页面超过10页数据,进行估算(取前10页的记录行数进行估算,有几个页面可以找他的B+书的上一级)!每行回表都是一次记录!MySQL评估回表操作的I/O成本依旧很简单粗暴,他们认为每次回表操作都相当于访问一个页面;简单说就是有多少记录,回表几次。
  • 回表操作后得到的完整用户记录,然后再检测其他搜索条件是否成立。cpu匹配。

in参数过多的处理

  • 查看in参数精确匹配的个数值(MySQL 5.7.21中默认200): show variables like '%dive%';
  • in的参数个数超过这个eq_range_index_dive_limit的值:估算一个值的重复次数 ≈ Rows(全表扫描估计的行数) ÷ Cardinality(show index from 查询出来的当前索引Cardinality的和)。然后用估算后的值乘上当前的in的格式,就是实际计算的值。

连接查询的成本

  • 连接查询总成本 = 单次访问驱动表的成本 + 驱动表扇出数 x 单次访问被驱动表的成本
  • 扇出:对驱动表进行查询后得到的记录条数
  • 计算方式的细节与单表相同!
posted @ 2021-12-22 22:43  程序java圈  阅读(136)  评论(0编辑  收藏  举报