目录

一、索引成本计算

1、IO成本

2、CPU成本

二、cout(*)索引---选成本最小的辅助索引

总结


一、索引成本计算

在有多个索引的情况下, 在查询数据前,MySQL 会选择成本最小原则来选择使用对应的索引,这里的成本主要包含两个方面。

1、IO成本

即从磁盘把数据加载到内存的成本,默认情况下,读取数据页的 IO 成本是 1,MySQL 是以页的形式读取数据的,即当用到某个数据时,并不会只读取这个数据,而会把这个数据相邻的数据也一起读到内存中,这就是有名的程序局部性原理,所以 (2)MySQL 每次会读取一整页,一页的成本就是 1。所以 IO 的成本主要和页的大小有关

2、CPU成本

将数据读入内存后,还要检测数据是否满足条件和排序等 CPU 操作的成本,显然它与行数有关,默认情况下,检测记录的成本是 0.2。

二、cout(*)索引---选成本最小的辅助索引

如下sql会导致慢查询,造成全表扫描:
SELECT COUNT(*) FROM SomeTable
SELECT COUNT(1) FROM SomeTable
这种说法是有问题的,实际上针对无where条件的COUNT(*),MySQL是有优化的,优化器会选择成本最小的辅助索引查询计数。

首先去生产上找了一个千万级别的表使用 EXPLAIN 来查询了一下执行计划

EXPLAIN SELECT COUNT(*) FROM SomeTable

结果如下

image.png

如图所示: 发现确实此条语句在此例中用到的并不是主键索引,而是辅助索引,实际上在此例中我试验了,不管是 COUNT(1),还是 COUNT(*),MySQL 都会用成本最小的辅助索引查询方式来计数,也就是使用 COUNT(*) 由于 MySQL 的优化已经保证了它的查询性能是最好的!随带提一句,COUNT(*)是 SQL92 定义的标准统计行数的语法,并且效率高,所以请直接使用COUNT(*)查询表的行数!不过是在 MySQL 5.6 之后的版本中才有这种优化。

那么这个成本最小该怎么定义呢,有时候在 WHERE 中指定了多个条件,为啥最终 MySQL 执行的时候却选择了另一个索引,甚至不选索引?这里成本计算见一(1)(2)

为了根据以上两个成本来算出使用索引的最终成本,我们先准备一个表(以下操作基于 MySQL 5.7.18)

CREATE TABLE `person` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(255) NOT NULL,`score` int(11) NOT NULL,`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `name_score` (`name`(191),`score`),KEY `create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这个表除了主键索引之外,还有另外两个索引, name_score 及 create_time。然后我们在此表中插入 10 w 行数据,只要写一个存储过程调用即可,如下:

CREATE PROCEDURE insert_person()
begindeclare c_id integer default 1;while c_id<=100000 doinsert into person values(c_id, concat('name',c_id), c_id+100, date_sub(NOW(), interval c_id second));set c_id=c_id+1;end while;
end

插入之后我们现在使用 EXPLAIN 来计算下统计总行数到底使用的是哪个索引

EXPLAIN SELECT COUNT(*) FROM person

从结果上看它选择了 create_time 辅助索引,显然 MySQL 认为使用此索引进行查询成本最小,这也是符合我们的预期,使用辅助索引来查询确实是性能最高的!

我们再来看以下 SQL 会使用哪个索引

SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'

用了全表扫描!理论上应该用 name_score 或者 create_time 索引才对,从 WHERE 的查询条件来看确实都能命中索引,那是否是使用 **SELECT *** 造成的回表代价太大所致呢,我们改成覆盖索引的形式试一下

SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18'

结果 MySQL 依然选择了全表扫描!这就比较有意思了,理论上采用了覆盖索引的方式进行查找性能肯定是比全表扫描更好的,为啥 MySQL 选择了全表扫描呢,既然它认为全表扫描比使用覆盖索引的形式性能更好,那我们分别用这两者执行来比较下查询时间吧

-- 全表扫描执行时间: 4.0 ms
SELECT create_time FROM person WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18' -- 使用覆盖索引执行时间: 2.0 ms
SELECT create_time FROM person force index(create_time) WHERE NAME >'name84059' AND create_time>'2020-05-23 14:39:18'

从实际执行的效果看使用覆盖索引查询比使用全表扫描执行的时间快了一倍!说明 MySQL 在查询前做的成本估算不准!我们先来看看 MySQL 做全表扫描的成本有多少。

前面我们说了成本主要 IO 成本和 CPU 成本有关,对于全表扫描来说也就是分别和聚簇索引占用的页面数和表中的记录数。执行以下命令

SHOW TABLE STATUS LIKE 'person'

可以发现

  1. 行数是 100264,我们不是插入了 10 w 行的数据了吗,怎么算出的数据反而多了,其实这里的计算是估算,也有可能这里的行数统计出来比 10 w 少了,估算方式有兴趣大家去网上查找,这里不是本文重点,就不展开了。得知行数,那我们知道 CPU 成本是 100264 * 0.2 = 20052.8。

  2. 数据长度是 5783552,InnoDB 每个页面的大小是 16 KB(2^14=16384),可以算出页面数量是 5783552 / 16384 = 353。

也就是说全表扫描的成本是 20052.8 + 353 = 20406。

这个结果对不对呢,我们可以用一个工具验证一下。在 MySQL 5.6 及之后的版本中,我们可以用 optimizer trace 功能来查看优化器生成计划的整个过程 ,它列出了选择每个索引的执行计划成本以及最终的选择结果,我们可以依赖这些信息来进一步优化我们的 SQL。

optimizer_trace 功能使用如下

SET optimizer_trace="enabled=on";
SELECT create_time FROM person WHERE NAME >'name84059' AND create_time > '2020-05-23 14:39:18';
SELECT * FROM information_schema.OPTIMIZER_TRACE;
SET optimizer_trace="enabled=off";

执行之后我们主要观察使用 name_score,create_time 索引及全表扫描的成本。

先来看下使用 name_score 索引执行的的预估执行成本:

{"index": "name_score","ranges": ["name84059 <= name"],"index_dives_for_eq_ranges": true,"rows": 25372,"cost": 30447
}

可以看到执行成本为 30447,高于我们之前算出来的全表扫描成本:20406。所以没选择此索引执行

注意:这里的 30447 是查询二级索引的 IO 成本和 CPU 成本之和,再加上回表查询聚簇索引的 IO 成本和 CPU 成本之和。

再来看下使用 create_time 索引执行的的预估执行成本:

{"index": "create_time","ranges": ["0x5ec8c516 < create_time"],"index_dives_for_eq_ranges": true,"rows": 50132,"cost": 60159,"cause": "cost"
}

可以看到成本是 60159,远大于全表扫描成本 20406,自然也没选择此索引。

再来看计算出的全表扫描成本:

{"considered_execution_plans": [{"plan_prefix": [],"table": "`person`","best_access_path": {"considered_access_paths": [{"rows_to_scan": 100264,"access_type": "scan","resulting_rows": 100264,"cost": 20406,"chosen": true}]},"condition_filtering_pct": 100,"rows_for_plan": 100264,"cost_for_plan": 20406,"chosen": true}]
}

注意看 cost:20406,与我们之前算出来的完全一样!这个值在以上三者算出的执行成本中最小,所以最终 MySQL 选择了用全表扫描的方式来执行此 SQL。

实际上 optimizer trace 详细列出了覆盖索引,回表的成本统计情况,有兴趣的可以去研究一下。

从以上分析可以看出, MySQL选择的执行计划未必是最佳的,原因有挺多,就比如上文说的行数统计信息不准,再比如 MySQL认为的最优跟我们认为不一样,我们可以认为执行时间短的是最优的,但 MySQL 认为的成本小未必意味着执行时间短。

总结

本文通过一个例子深入剖析了 MySQL 的执行计划是如何选择的,以及为什么它的选择未必是我们认为的最优的,这也提醒我们,在生产中如果有多个索引的情况,使用 WHERE 进行过滤未必会选中你认为的索引,我们可以提前使用 EXPLAIN, optimizer trace 来优化我们的查询语句。

mysql count(*)使用索引和成本计算相关推荐

  1. mysql count 优化索引_如何通过使用索引在InnoDB上优化COUNT(*)性能

    我有一个小而狭窄的InnoDB表,大约有900万条记录.在桌子上count(*)或count(id)桌子上做的速度非常慢(超过6秒): DROP TABLE IF EXISTS `perf2`; CR ...

  2. mysql count 不走索引_sql中不走索引情况

    where 用到的字段没创建索引,就不走索引. 下面这三种情况也会走索引. where field1 like '%abc%' --like % where substr(field1,1)='a' ...

  3. mysql 查询优化 非索引_mysql 查询优化和索引使用心得

    -- sql优化案例 -- 1.not in 用left join on 替换 -- 2.like '%XXX%' 用 like 'XXX%' 替换 -- 3.limit 优化 实用,在分页中 EXP ...

  4. 面试mysql中怎么创建索引_阿里面试:MySQL如何设计索引更高效?

    有情怀,有干货,微信搜索[三太子敖丙]关注这个不一样的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系列文章. ...

  5. mysql的最佳索引攻略

    Explain优化查询检测 所谓索引就是为特定的mysql字段进行一些特定的算法排序,比如二叉树的算法和哈希算法,哈希算法是通过建立特征值,然后根据特征值来快速查找,而用的最多,并且是mysql默认的 ...

  6. mysql字段简索引_Mysql索引优化攻略(全)

    所谓索引就是为特定的mysql字段进行一些特定的算法排序,比如二叉树的算法和哈希算法,哈希算法是通过建立特征值,然后根据特征值来快速查找.而用的最多,并且是mysql默认的就是二叉树算法 BTREE, ...

  7. 阿里P8架构师谈:MySQL数据库的索引原理、与慢SQL优化的5大原则

    MySQL凭借着出色的性能.低廉的成本.丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库.虽然性能出色,但所谓"好马配好鞍",如何能够更好的使用它,已经成为开发工程师的必修 ...

  8. MySQL高级 —— 高性能索引

    引言 最近一直在抱着<高性能MySQL(第三版)>研究MySQL相关热点问题,诸如索引.查询优化等,这阶段的学习是前一段时间MySQL基础与官方的"阅读理解"的进一步延 ...

  9. mysql删除unionkey_MySQL索引如何优化?二十条铁则送给你

    前言 索引的相信大家都听说过,但是真正会用的又有几人?平时工作中写SQL真的会考虑到这条SQL如何能够用上索引,如何能够提升执行效率? 此篇文章详细的讲述了索引优化的几个原则,只要在工作中能够随时应用 ...

最新文章

  1. 某大厂女程序员哀叹:还不到三十岁,父母竟让自己和一个离异有娃的男人相亲!
  2. redis中的quicklist
  3. java 多项式拟合最多的项数_牛顿插值法、曲线拟合、多项式拟合
  4. 《网络空间欺骗:构筑欺骗防御的科学基石》一1.1 主动网络空间防御中网络空间抵赖与欺骗的视图...
  5. Linux上的.NET框架Mono 2.0发布
  6. HTML-JS-CSS基础
  7. nutch batchid
  8. java下载 安卓版_java教程手机版-java宝典安卓版下载2.0 官方版-西西软件下载
  9. Google play谷歌应用商店 APP上包上架的一些策略和技巧
  10. kindeditor上传图片php,kindeditor图片上传功能
  11. 湖南科技大学计算机考研难吗,湖南科技大学考研难吗
  12. loj10099 矿场搭建
  13. iOS iPhone、iPad、Mac等禁止系统摄像头功能
  14. Percona监控数据库解决方案
  15. msf获取靶机shell进行远控
  16. Discuz!开发之DB、CT类解析
  17. 趣图 | 这该死的自信
  18. 总框架 计算机网络和协议
  19. 【ML】EM(期望最大)算法
  20. CAD软件中怎么画水箱?

热门文章

  1. Element UI 多选表格【翻页多选】全能版(含翻页多选数据反显、toggleRowSelection失效的原因解析和解决方案)
  2. 转 -- Zynga:从Amazon公共云到zCloud私有云
  3. echart 插件做任务关系图时 添加照片
  4. 拼多多面试问了数据库基础知识,今天分享出来
  5. 九个角度分析对比 Android、iOS开发区别
  6. 谁在“接盘”造车新势力?
  7. 为什么说 HashMap 是无序的
  8. c语言字节的高地位互换,c语言面试题目100及最佳答案(51页)-原创力文档
  9. jmeter查看平均响应时间_Jmeter查看QPS和响应时间随着时间的变化曲线
  10. 【面试篇】牛客网面试总结