热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MySQL为什么"错误"选择代价更大的索引

MySQL为什么错误选择代价更大的索引-欢迎来到GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答MySQL优化器索引

欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答

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 WHERE
 c2 = 1755950419 AND c3 = 'MichaelaAnderson'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: const
possible_keys: k3,k2
          key: k3
      key_len: 82
          ref: const
         rows: 1
     filtered: 100.00
        Extra: 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 WHERE
 c2 = 1755950419 AND c3 = 'MichaelaAnderson'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: const
possible_keys: k2,k3
          key: k2
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL

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

-- 查看t1表执行计划代价
[root@yejr.run]> EXPLAIN ANALYZE SELECT * FROM t1 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)

-- 查看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: | | | | | | | | | convert_string
T@2: | | | | | | | | | >alloc_root
T@2: | | | | | | | | | | enter: root: 0x40a8068
T@2: | | | | | | | | | | exit: ptr: 0x4b41ab0
T@2: | | | | | | | | | 
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 GreatSQL :)

本文由博客一文多发平台 OpenWrite 发布!

推荐阅读
  • Python操作MySQL(pymysql模块)详解及示例代码
    本文介绍了使用Python操作MySQL数据库的方法,详细讲解了pymysql模块的安装和连接MySQL数据库的步骤,并提供了示例代码。内容涵盖了创建表、插入数据、查询数据等操作,帮助读者快速掌握Python操作MySQL的技巧。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • Explain如何助力SQL语句的优化及其分析方法
    本文介绍了Explain如何助力SQL语句的优化以及分析方法。Explain是一个数据库SQL语句的模拟器,通过对SQL语句的模拟返回一个性能分析表,从而帮助工程师了解程序运行缓慢的原因。文章还介绍了Explain运行方法以及如何分析Explain表格中各个字段的含义。MySQL 5.5开始支持Explain功能,但仅限于select语句,而MySQL 5.7逐渐支持对update、delete和insert语句的模拟和分析。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Python使用Pillow包生成验证码图片的方法
    本文介绍了使用Python中的Pillow包生成验证码图片的方法。通过随机生成数字和符号,并添加干扰象素,生成一幅验证码图片。需要配置好Python环境,并安装Pillow库。代码实现包括导入Pillow包和随机模块,定义随机生成字母、数字和字体颜色的函数。 ... [详细]
  • 大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记
    本文介绍了大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记,包括outputFormat接口实现类、自定义outputFormat步骤和案例。案例中将包含nty的日志输出到nty.log文件,其他日志输出到other.log文件。同时提供了一些相关网址供参考。 ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
  • PHP连接MySQL的2种方法小结以及防止乱码【PHP】
    后端开发|php教程PHP,MySQL,乱码后端开发-php教程PHP的MySQL配置报错信息:ClassmysqlinotfoundinAnswer:1.在confphp.ini ... [详细]
author-avatar
芳方程_269
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有