MySQL 优化器是 CBO,即一种基于成本的优化器。

在关系型数据库中,B+ 树索引只是存储的一种数据结构,具体怎么使用,还要依赖数据库的优化器,优化器决定了具体某一索引的选择,也就是常说的执行计划。

而优化器的选择是基于成本(cost),哪个索引的成本越低,优先使用哪个索引。

对orders表创建3个索引

 CREATE TABLE `orders` (`O_ORDERKEY` int NOT NULL,`O_CUSTKEY` int NOT NULL,`O_ORDERSTATUS` char(1) NOT NULL,`O_TOTALPRICE` decimal(15,2) NOT NULL,`O_ORDERDATE` date NOT NULL,`O_ORDERPRIORITY` char(15) NOT NULL,`O_CLERK` char(15) NOT NULL,`O_SHIPPRIORITY` int NOT NULL,`O_COMMENT` varchar(79) NOT NULL,PRIMARY KEY (`O_ORDERKEY`),KEY `idx_custkey_orderdate` (`O_CUSTKEY`,`O_ORDERDATE`),KEY `ORDERS_FK1` (`O_CUSTKEY`),KEY `idx_custkey_orderdate_totalprice` (`O_CUSTKEY`,`O_ORDERDATE`,`O_TOTALPRICE`),CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`O_CUSTKEY`) REFERENCES `customer` (`C_CUSTKEY`)) ENGINE=InnoDB

而在 MySQL中,一条 SQL 的计算成本计算如下所示:

Cost  = Server Cost + Engine Cost= CPU Cost + IO Cost

其中,CPU Cost 表示计算的开销,比如索引键值的比较、记录值的比较、结果集的排序……这些操作都在 Server 层完成;

IO Cost 表示引擎层 IO 的开销,MySQL 8.0 可以通过区分一张表的数据是否在内存中,分别计算读取内存 IO 开销以及读取磁盘 IO 的开销。

未能使用创建的索引

SELECT * FROM ordersWHERE o_orderdate > '1994-01-01' and o_orderdate < '1994-12-31';SELECT * FROM orders WHERE o_orderdate > '1994-02-01' and o_orderdate < '1994-12-31';

上面这两条 SQL 都是通过索引字段 o_orderdate 进行查询,然而第一条 SQL 语句的执行计划并未使用索引 idx_orderdate,而是使用了如下的执行计划:

EXPLAIN SELECT * FROM orders
WHERE o_orderdate > '1994-01-01'
AND o_orderdate < '1994-12-31'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: orderspartitions: NULLtype: ALL
possible_keys: idx_orderdatekey: NULLkey_len: NULLref: NULLrows: 5799601filtered: 32.35Extra: Using where

从上述执行计划中可以发现,优化器已经通过 possible_keys 识别出可以使用索引idx_orderdate,但最终却使用全表扫描的方式取出结果。 最为根本的原因在于:优化器认为使用通过主键进行全表扫描的成本比通过二级索引 idx_orderdate 的成本要低,可以通过FORMAT=tree 观察得到:

EXPLAIN FORMAT=tree
SELECT * FROM orders
WHERE o_orderdate > '1994-01-01'
AND o_orderdate < '1994-12-31'\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: ((orders.O_ORDERDATE > DATE'1994-01-01') and (orders.O_ORDERDATE < DATE'1994-12-31'))  (cost=592267.11 rows=1876082)-> Table scan on orders  (cost=592267.11 rows=5799601)
EXPLAIN FORMAT=tree
SELECT * FROM orders FORCE INDEX(idx_orderdate)
WHERE o_orderdate > '1994-01-01'
AND o_orderdate < '1994-12-31'\G
*************************** 1. row ***************************
EXPLAIN: -> Index range scan on orders using idx_orderdate, with index condition: ((orders.O_ORDERDATE > DATE'1994-01-01') and (orders.O_ORDERDATE < DATE'1994-12-31'))  (cost=844351.87 rows=1876082)

可以看到,MySQL 认为全表扫描,然后再通过 WHERE 条件过滤的成本为 592267.11,对比强制使用二级索引 idx_orderdate 的成本为 844351.87。

成本上看,全表扫描低于使用二级索引。故,MySQL 优化器没有使用二级索引 idx_orderdate。

为什么全表扫描比二级索引查询快呢? 因为二级索引需要回表,当回表的记录数非常大时,成本就会比直接扫描要快,因此这取决于回表的记录数。

所以,第二条 SQL 语句,只是时间范围发生了变化,但是 MySQL 优化器就会自动使用二级索引 idx_orderdate了,这时我们再观察执行计划:

EXPLAIN SELECT * FROM orders
WHERE o_orderdate > '1994-02-01'
AND o_orderdate < '1994-12-31'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: orderspartitions: NULLtype: range
possible_keys: idx_orderdatekey: idx_orderdatekey_len: 3ref: NULLrows: 1633884filtered: 100.00Extra: Using index condition

再次强调,并不是 MySQL 选择索引出错,而是 MySQL 会根据成本计算得到最优的执行计划, 根据不同条件选择最优执行计划,而不是同一类型一成不变的执行过程,这才是优秀的优化器该有的样子。

索引创建在有限状态

  • 一般只对高选择度的字段和字段组合创建索引,低选择度的字段如性别,不创建索引;
  • 低选择性,但是数据存在倾斜,通过索引找出少部分数据,可以考虑创建索引;
  • 若数据存在倾斜,可以创建直方图,让优化器知道索引中数据的分布,进一步校准执行计划;

B+ 树索引通常要建立在高选择性的字段或字段组合上,如订单 ID、日期等,因为这样每个字段值大多并不相同。

但是对于性别这样的字段,其值只有男和女两种,哪怕记录数再多,也只有两种值,这是低选择性的字段,因此无须在性别字段上创建索引。

但在有些低选择性的列上,是有必要创建索引的。比如电商的核心业务表 orders,其有字段 o_orderstatus,表示当前的状态。

在电商业务中会有一个这样的逻辑:即会定期扫描字段 o_orderstatus 为支付中的订单,然后强制让其关闭,从而释放库存,给其他有需求的买家进行购买。

但字段 o_orderstatus 的状态是有限的,一般仅为已完成、支付中、超时已关闭这几种。

通常订单状态绝大部分都是已完成,只有绝少部分因为系统故障原因,会在 15 分钟后还没有完成订单,因此订单状态是存在数据倾斜的。

这时,虽然订单状态是低选择性的,但是由于其有数据倾斜,且我们只是从索引查询少量数据,因此可以对订单状态创建索引:

创建索引

ALTER TABLE orders ADD INDEX idx_orderstatus(o_orderstatus)
EXPLAIN SELECT * FROM orders WHERE o_orderstatus = 'P'\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: orderspartitions: NULLtype: ALLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 5799601filtered: 50.00Extra: Using where

由于字段 o_orderstatus 仅有三个值,分别为 ‘O’、‘P’、‘F’。但 MySQL 并不知道这三个列的分布情况,认为这三个值是平均分布的,但其实是这三个值存在严重倾斜:

SELECT o_orderstatus,count(1) FROM orders GROUP BY o_orderstatus;+---------------+----------+| o_orderstatus | count(1) |+---------------+----------+| F             |  2923619 || O             |  2923597 || P             |   152784 |+---------------+----------+

因此,优化器会认为订单状态为 P 的订单占用 1/3 的数据,使用全表扫描,避免二级索引回表的效率会更高。

然而,由于数据倾斜,订单状态为 P 的数据非常少,根据索引 idx_orderstatus 查询的效率会更高。这种情况下,我们可以利用 MySQL 8.0 的直方图功能,创建一个直方图,让优化器知道数据的分布,从而更好地选择执行计划。直方图的创建命令如下所示:

ANALYZE TABLE orders UPDATE HISTOGRAM ON o_orderstatus;

收集o_orderstatus的数值分布

SELECT v value, CONCAT(round((c - LAG(c, 1, 0) over()) * 100,1), '%') ratio   FROM information_schema.column_statistics, JSON_TABLE(histogram->'$.buckets','$[*]' COLUMNS(v VARCHAR(60) PATH '$[0]', c double PATH '$[1]')) hist  WHERE column_name = 'o_orderstatus';+-------+-------+| value | ratio |+-------+-------+| F     | 49%   || O     | 48.5% || P     | 2.5%  |+-------+-------+

可以看到,现在 MySQL 知道状态为 P 的订单只占 2.5%,因此再去查询状态为 P 的订单时,就会使用到索引 idx_orderstatus了,如:

EXPLAIN SELECT * FROM orders WHERE o_orderstatus = 'P'\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: orderspartitions: NULLtype: refpossible_keys: idx_orderstatuskey: idx_orderstatuskey_len: 4ref: constrows: 306212filtered: 100.00Extra: Using index condition

知识点来自学习-姜承尧老师拉钩网教导内容。

Mysql学习-第二章(CBO工作原理)相关推荐

  1. Mysql学习-第二章(组合索引)

    组合索引是指多个列所组成的B+树索引,既可以是主键索引,也可以是二级索引组合,下图为一个索引图 组合索引(a,b),(b,a)完全不同 示例,组合索引(a,b),对列ab进行排序 SELECT * F ...

  2. 快速学习一门新技术的工作原理(十步学习法来自软技能)

    快速学习一门新技术的工作原理 ●如何开始--要想开始使用自己所学的,我需要掌握哪些基本知识? ●学科范围--我现在学的东西有多宏大?我应该怎么做?在开始阶段,我不需要了解每个细节,但是如果我能对该学科 ...

  3. 深度学习 - 第二章 - 机器学习基础

    深度学习 - 第二章 - 机器学习基础 第二章 机器学习基础 2.1 各种常见算法图示 2.2 监督学习.非监督学习.半监督学习.弱监督学习? 2.3 监督学习有哪些步骤 2.4 多实例学习? 2.5 ...

  4. Python爬虫学习第二章-1-requests模块简介

    Python爬虫学习第二章-1-requests模块简介   这一章主要是介绍requests模块的相关知识以及使用 1.requests模块简介: 概述:是python中原生的一款基于网络请求的模块 ...

  5. 计算机原理简明教程第二章,《计算机原理简明教程》习题答案[参考].doc

    <计算机原理简明教程>习题参考答案 第一章习题答案 1.1 答:是1946年在美国宾夕法尼亚大学诞生,称为ENIAC. 特点是由1800个电子管和1500个继电器组成,重30吨:功耗150 ...

  6. Mastering KVM Virtualization:第二章 KVM内部原理

    在本章中,我们将讨论libvirt.QEMU和KVM的重要数据结构和内部实现.然后,我们将深入了解KVM下vCPU的执行流程. 在这一章,我们将讨论: libvirt.QEMU和KVM的内部运作方式. ...

  7. ocsng mysql connection problem_OCSNG 介绍及其工作原理

    OCSNG部署:http://wowking.blog.51cto.com/1638252/994441 OCSNG 是什么呢? OCSNG就是Open Computer and Software I ...

  8. 图解Http学习第二章

    Http通信必须存在客户端和服务端 请求从客户端发出,服务器端接收后响应请求.(所以不难理解:首先是从客户端开始建立通信的) 发送请求报文示例: GET /index.htm  HTTP/1.1 Ho ...

  9. 第二章 Javac编译原理

    注:本文主要记录自<深入分析java web技术内幕>"第四章 javac编译原理" 1.javac作用 将*.java源代码文件转化为*.class文件 2.编译流程 ...

最新文章

  1. WKWebView get/set cookie小结
  2. java解决策略膨胀_折腾Java设计模式之策略模式
  3. SpringBoot继承TkMapper通用Mapper
  4. 定时线程的使用 java_Java线程Timer定时器用法详细总结
  5. 使用jquery判断及改变checkbox选中状态
  6. C++ 常量类型 const 详解
  7. mac android 调试快捷键,Mac Android Studio快捷键整理_IOS_脚本之家
  8. bind 完成正确安装
  9. 值得收藏几个Web木马后门查杀扫描工具
  10. 开始报名丨CCF C³-13@奇安信:透视俄乌网络战 —— 网络空间基础设施面临的安全对抗与制裁博弈...
  11. 蚂蚱跳跃问题 【字节笔试】题目说 ”字节“跳动
  12. 拼多多2020届数据分析面试题合集
  13. oracle序列号的使用
  14. matlab基于傅立叶变换的时域或频域算法计算多普勒频移,展示代码
  15. 拓展编辑器(十八)_源生自定义菜单
  16. 网上招标系统的分析与实现
  17. html5 清除cookies,react怎么清除cookie?
  18. java使用 Batik svg代码转换成png图片 JPEGTranscoder/PNGTranscoder 转图片 linux 中文乱码
  19. 关于Raptor的简单使用
  20. The Standalone Programmer: Tips from the trenches(1)

热门文章

  1. 增强现实技术漫谈(续)——研究内容全面解析
  2. 回顾“90后”——MISRA的25年岁月
  3. (步骤详细)MATLAB/SIMULINK全局变量设置
  4. layDate显示默认时间
  5. 切换window窗口
  6. vue实现echarts图表下载(含多张图表),导出图片格式
  7. windows虚拟机中如何创建MBR分区表
  8. 更新时被锁定SVN: Working copy '' locked
  9. oracle 表建模工具,Oracle数据库建模工具(ModelRight for Oracle)下载 V4.0 官方版 - 比克尔下载...
  10. php导出excel2007表格