MySQL优化器索引选择迷思。

高鹏(八怪)对本文亦有贡献。

1. 问题描述

群友提出问题,表里有两个列c1、c2,分别为INT、VARCHAR类型,且分别创建了unique key。

SQL查询的条件是 WHERE c1 = ? AND c2 = ?,用EXPLAIN查看执行计划,发现优化器优先选择了VARCHAR类型的c2列索引。

他表示很不理解,难道不应该选择看起来代价更小的INT类型的c1列吗?

2. 问题复现

创建测试表t1:

[root@yejr.run]> CREATE TABLE `t1` (`c1` int NOT NULL AUTO_INCREMENT,`c2` int unsigned NOT NULL,`c3` varchar(20) NOT NULL,`c4` varchar(20) NOT NULL,PRIMARY KEY (`c1`),UNIQUE KEY `k3` (`c3`),UNIQUE KEY `k2` (`c2`)
) ENGINE=InnoDB;

利用 mysql_random_data_load 写入一万行数据:

mysql_random_data_load -h127.0.0.1 -uX -pX yejr t1 10000

查看执行计划:

[root@yejr.run]> EXPLAIN SELECT * FROM t1 WHEREc2 = 1755950419 AND c3 = 'MichaelaAnderson'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: t1partitions: NULLtype: const
possible_keys: k3,k2key: k3key_len: 82ref: constrows: 1filtered: 100.00Extra: NULL

可以看到优化器的确选择了 k3 索引,而非"预期"的 k2 索引,这是为什么呢?

3. 问题分析

其实原因很简单粗暴:优化器认为这两个索引选择的代价都是一样的,只是优先选中排在前面的那个索引而已

再建一个相同的表 t2,只不过把 k2、k3 的索引创建顺序对调下:

[root@yejr.run]> CREATE TABLE `t2` (`c1` int NOT NULL AUTO_INCREMENT,`c2` int unsigned NOT NULL,`c3` varchar(20) NOT NULL,`c4` varchar(20) NOT NULL,PRIMARY KEY (`c1`),UNIQUE KEY `k2` (`c2`),UNIQUE KEY `k3` (`c3`)
) ENGINE=InnoDB;

再查看执行计划:

[root@yejr.run]> EXPLAIN SELECT * FROM t2 WHEREc2 = 1755950419 AND c3 = 'MichaelaAnderson'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: t1partitions: NULLtype: const
possible_keys: k2,k3key: k2key_len: 4ref: constrows: 1filtered: 100.00Extra: NULL

我们利用 EXPLAIN ANALYZE 来查看下两次执行计划的代价对比:

-- 查看t1表执行计划代价
[root@yejr.run]> EXPLAIN ANALYZE SELECT * FROM t1 WHEREc2 = 1755950419 AND c3 = 'MichaelaAnderson'\G
*************************** 1. row ***************************
EXPLAIN: -> Rows fetched before execution  (cost=0.00..0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)-- 查看t2表执行计划代价
[root@yejr.run]> EXPLAIN ANALYZE SELECT * FROM t2 WHERE  c2 = 1755950419 AND c3 = 'MichaelaAnderson'\G
*************************** 1. row ***************************
EXPLAIN: -> Rows fetched before execution  (cost=0.00..0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)

可以看到,很明显代价都是一样的。

再利用 OPTIMIZE_TRACE 查看执行计划,也能看到两个SQL的代价是一样的:

...{"rows_estimation": [{"table": "`t1`","rows": 1,"cost": 1,"table_type": "const","empty": false}]},
...

所以,优化器认为选择哪个索引都是一样的,就看哪个索引排序更靠前。

从执行SELECT时的debug trace结果也能佐证:

-- 1、 T1表,k3索引在前面PRIMARY KEY (`c1`),UNIQUE KEY `k3` (`c3`),UNIQUE KEY `k2` (`c2`)T@2: | | | | | | | | opt: (null): starting struct
T@2: | | | | | | | | opt: table: "`t1`"
T@2: | | | | | | | | opt: field: "c3"   (C3在前面,因此最后使用k3)
T@2: | | | | | | | | >convert_string
T@2: | | | | | | | | | >alloc_root
T@2: | | | | | | | | | | enter: root: 0x40a8068
T@2: | | | | | | | | | | exit: ptr: 0x4b41ab0
T@2: | | | | | | | | | <alloc_root 304
T@2: | | | | | | | | <convert_string 2610
T@2: | | | | | | | | opt: equals: "'Louise Garrett'"
T@2: | | | | | | | | opt: null_rejecting: 0
T@2: | | | | | | | | opt: (null): ending struct
T@2: | | | | | | | | opt: Key_use: optimize= 0 used_tables=0x0 ref_table_rows= 18446744073709551615 keypart_map= 1
T@2: | | | | | | | | opt: (null): starting struct
T@2: | | | | | | | | opt: table: "`t1`"
T@2: | | | | | | | | opt: field: "c2"
T@2: | | | | | | | | opt: equals: "22896242"
T@2: | | | | | | | | opt: null_rejecting: 0
T@2: | | | | | | | | opt: null_rejecting: 0
T@2: | | | | | | | | opt: (null): ending struct
T@2: | | | | | | | | opt: Key_use: optimize= 0 used_tables=0x0 ref_table_rows= 18446744073709551615 keypart_map= 1
T@2: | | | | | | | | opt: (null): starting struct
T@2: | | | | | | | | opt: table: "`t1`"
T@2: | | | | | | | | opt: field: "c2"
T@2: | | | | | | | | opt: equals: "22896242"
T@2: | | | | | | | | opt: null_rejecting: 0
T@2: | | | | | | | | opt: (null): ending struct
T@2: | | | | | | | | opt: ref_optimizer_key_uses: ending struct
T@2: | | | | | | | | opt: (null): ending struct-- 2、 T2表,k2索引在前面PRIMARY KEY (`c1`),UNIQUE KEY `k2` (`c2`),UNIQUE KEY `k3` (`c3`)T@2: | | | | | | | | opt: (null): starting struct
T@2: | | | | | | | | opt: table: "`t2`"
T@2: | | | | | | | | opt: field: "c2" (C2在前面因此使用k2索引)
T@2: | | | | | | | | opt: equals: "22896242"
T@2: | | | | | | | | opt: null_rejecting: 0
T@2: | | | | | | | | opt: (null): ending struct
T@2: | | | | | | | | opt: Key_use: optimize= 0 used_tables=0x0 ref_table_rows= 18446744073709551615 keypart_map= 1
T@2: | | | | | | | | opt: (null): starting struct
T@2: | | | | | | | | opt: table: "`t2`"
T@2: | | | | | | | | opt: field: "c3"
T@2: | | | | | | | | >convert_string
T@2: | | | | | | | | | >alloc_root
T@2: | | | | | | | | | | enter: root: 0x40a8068
T@2: | | | | | | | | | | exit: ptr: 0x4b41ab0
T@2: | | | | | | | | | <alloc_root 304
T@2: | | | | | | | | <convert_string 2610
T@2: | | | | | | | | opt: equals: "'Louise Garrett'"
T@2: | | | | | | | | opt: null_rejecting: 0
T@2: | | | | | | | | opt: (null): ending struct
T@2: | | | | | | | | opt: ref_optimizer_key_uses: ending struct
T@2: | | | | | | | | opt: (null): ending struct

4. 问题延伸

到这里,我们不禁有疑问,这两个索引的代价真的是一样吗?

就让我们用 mysqlslap 来做个简单对比测试吧:

-- 测试1:对c2列随机point select
mysqlslap -hlocalhost -uroot -Smysql.sock --no-drop --create-schema X -i 3 --number-of-queries 1000000 -q "set @xid = cast(round(rand()*2147265929) as unsigned); select * from t1 where c2 = @xid" -c 8
...Average number of seconds to run all queries: 9.483 seconds
...-- 测试2:对c3列随机point select
mysqlslap -hlocalhost -uroot -Smysql.sock --no-drop --create-schema X -i 3 --number-of-queries 1000000 -q "set @xid = concat('u',cast(round(rand()*2147265929) as unsigned)); select * from t1 where c3 = @xid" -c 8
...Average number of seconds to run all queries: 10.360 seconds
...

可以看到,如果是走 c3 列索引,耗时会比走 c2 列索引多出来约 7% ~ 9%(在我的环境下测试的结果,不同环境、不同数据量可能也不同)。

看来,MySQL优化器还是有必要进一步提高的哟 :)

测试使用版本:GreatSQL 8.0.25(MySQL 5.6.39结果亦是如此)。

Enjoy MySQL :)

《实战MGR》视频课程

戳此小程序即可直达B站

或复制链接在浏览器中打开

  • https://space.bilibili.com/1363850082


文章推荐:

  • 面向金融级应用的GreatSQL正式开源

  • GreatSQL重磅特性,InnoDB并行查询优化测试

  • 技术分享|在Ubuntu下编译安装GreatSQL

  • 《叶问》37期,三节点的MGR集群关掉两个节点后还能继续读写吗

  • MySQL SQL 优化参数 引发的悲剧

  • SQL案例分析之部分查询和全部查询

  • 一周碎碎念,2021.11.7,两个MGR集群间还可以构建传统的主从复制通道吗

  • Percona XtraBackup 8.0.26实战大全

  • 技术分享 | Update更新慢、死锁等问题的排查思路分享

  • 在Linux下源码编译安装GreatSQL/MySQL


点击文末“阅读原文”直达老叶专栏

MySQL为什么错误选择代价更大的索引相关推荐

  1. 想突破现状,就得付出更大的努力!

    1.最难突破的就是父母 父母是每个人的起点,也是绝大部分人的天花板.中国最近这代人大部分都比父母混的强,是因为父母被耽搁了,再往后几代,大家就能看出来这句话的威力了,美国英国德国那边的成熟型社会这一点 ...

  2. 【MySQL高级篇】第06章_索引的数据结构

    第06章_索引的数据结构 1. 为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构,就好比一本教科书的目录部分,通过目录中找到对应文章的页码,便可快速定位到需要的文章.MySQL中也是一 ...

  3. MySQL的order by该如何避免“未命中索引“

      不少同学私信我说,用Explain查看Order By语句执行计划时经常发现用不上索引,难道花好多时间和资源创建的联合索引都摆烂了?今天我把几个同学遇到的情况整理出来,做一个Order By使用索 ...

  4. MySQL的order by该如何避免未命中索引

    在使用Explain查看Order By语句执行计划时经常发现用不上索引,难道花好多时间和资源创建的联合索引都摆烂了?今天我把遇到的情况整理出来,做一个Order By使用索引的坑点分享.希望对你有用 ...

  5. mysql负载均衡分区_分区和负载均衡让MySQL更大更好

    通常,当我们的MySQL数据库逐渐变慢时,我们就希望通过一切努力使它变得更快.更强.更大.更好!那么都有哪些方法呢?别着急,我会一个一个给大家介 绍如何才能实现这些美好的愿望.阅读本系列文章将有助于扩 ...

  6. 任正非:为什么华为选择与西工大合作,而没选清华北大,mysql连接查询原理

    西工大同清北等13所高校一样,在今年的5月22日被美国商务部列入实体名单,同时被限制使用美国的相关正版软件,而且很多之前和美国的合作也终止,不能用一些软件,同时也不能购买许多的美国重要零部件. 导致西 ...

  7. 计算机科学与技术专业歧视女生吗,考研选择这3大专业,女生就业不比男生差,反而女生更容易被看中...

    原标题:考研选择这3大专业,女生就业不比男生差,反而女生更容易被看中 学有方法,考有技巧,优学优考策略致力于学与考的最优结合,助力学子考入理想大学! 一年一度的高考填报志愿又来临了.在这惊心动魄,议论 ...

  8. 巨杉数据库 CTO 王涛:区块链+数据库,底层技术融合是否带来更大爆发?

    巨杉数据库 CTO 王涛:区块链+数据库,底层技术融合是否带来更大爆发? 4 月 22 日,在掘金技术社区主办的沙龙上,巨杉数据库 CTO 王涛以<区块链技术 VS 数据库技术:颠覆还是融合&g ...

  9. 在MySQL中当有多个索引时 你知道MySQL是如何选择索引的吗 ???

    在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不 ...

最新文章

  1. go map的定义和使用 键值对存储
  2. 加载场景不销毁的实现
  3. 深入理解this和call、bind、apply对this的影响及用法
  4. Alpha 通道的概念与功能
  5. pip show pip可以查看pip的版本以及升级pip到最新版本
  6. 【APIO2015】完跪记
  7. 批处理-删除环境变量
  8. NET开发资源站点和部分优秀.NET开源项目
  9. 20200122每日一句
  10. 在UITextView显示HTML,以及NSAttributedString乱码问题解决 swift
  11. 服务器系统如何校验md5值,什么是md5校验工具,md5校验工具怎么用?
  12. Python入门经典. 以解决计算问题为导向的Python编程实践
  13. python 工具变量_工具变量读书笔记
  14. 用路由器打印机显示服务器不存在,打印机连路由器怎么搜索不到
  15. centos离线安装docker-ce 18.03.0-ce
  16. Java第一天笔记01——jdk8的安装与环境变量的配置
  17. 知乎 | 给博士一年级新生的建议!
  18. 计算机课学生评价用语,关于学生上课的评语及评课用语
  19. 【项目篇-项目选题与类型】创赛项目来源、项目选题建议;四种常见类型项目各自内容的侧重点与区别
  20. [日推荐]『youhui优惠券』领点优惠券再去买买买!

热门文章

  1. 手写ngIf,使用视图容器引用ViewContainerRef和模板引用TemplateRef
  2. jqGrid 设置单元格行高的方法
  3. 在Word中插入x拔字符
  4. 大连python培训费用-大连Python培训
  5. Figures now render in the Plots pane by default. To make them also appear inline in the Console, unc
  6. TR-069协议介绍
  7. 基于STM32和GPS-NEO-6M模块实现GPS导航定位的
  8. 云台和华为p30pro_p30pro和mate30pro对比
  9. 中国石油大学《马克思主义基本原理概论#》第一阶段在线作业
  10. oracle vsm5,傲里拔尊--宝马M5 VS.奔驰CLS55AMG