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

基于JDBC的全量/增量数据迁移与同步原理 基于JDBC的全量/增量数据迁移与同步原理一、关系库基于JDBC的全量数据迁移原理1.1 where条件切片方式1.1.1 原理1.1.2 优缺点1.1.3 影响性能的因素:1.2 limit/offset分页切片方式1.2.1 原理1.2.2 优缺点1.2.3 影响性能的因素1.2.4 oracle基于分页的切片方式二、基于JDBC的增量数据迁移/同步原理三、关系库的逻辑结构3.1 Mysql3.2 Oracle3.2.1 oracle中的其它概念:3.3 PostgreSQL3.3.1 PostgreS 阅读详情

欢迎来到 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: | | | | | | | | | <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 GreatSQL :)

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

MySQL索引优化:从回表、覆盖索引到优化器代价模型一次讲透 MySQL索引是提升查询性能的基石,但索引并不是建得越多越快。其底层B+树结构决定了二级索引命中后往往需要回表,当回表行数占比过高时,优化器可能放弃索引而选择全表扫描,这正是许多慢SQL的根源。理解回表代价模型、覆盖索引、索引下推与联合索引的最左前缀规则,才能解释为什么同一个查询在不同数据分布下性能差距巨大。实际业务中,无论是order by、group by还是深分页,索引设计都直接影响执行计划与Filesort开销;定期analyze table刷新统计信息、善用optimizer trace和直方图, 阅读详情

相关推荐

排队论在食堂窗口规划中的应用:M/M/S模型优化案例

本文通过食堂窗口规划的真实案例,详细阐述了如何运用排队论中的M/M/S模型量化服务效率。文章从问题拆解、数据采集、模型计算到成本效益分析,提供了完整的数学建模实战指南,帮助读者掌握利用最优化方法解决资源配置问题的核心流程与工具。

docker7nomad的博客 1084

MySQL EXPLAIN 完全指南:从入门到实战优化

在日常开发中,我们经常会遇到 SQL 执行缓慢的问题。此时,MySQL EXPLAIN 便是排查性能瓶颈的“利器”——它能清晰展示 SQL 的执行计划,帮助我们判断索引是否生效、是否存在全表扫描、是否需要优化排序等。本文将从测试环境搭建开始,逐步拆解 EXPLAIN 的核心字段含义,并通过实战案例讲解如何利用它优化 SQL 性能,最后介绍 MySQL 8.0 带来的执行计划新特性。

专注Java和MySQL领域知识分享 1584

文石NoteX3国行版安装 Google Play经验分享

文石NoteX3国行版安装 Google Play经验分享。

2301_80693261的博客 7603

第36期:MySQL 原生水平拆表

引言 上一章节我们探讨过数据垂直拆分,今天我们来继续讨论数据拆分:水平拆分! 水平拆分和垂直拆分有些不一样,垂直拆分最小单元是字段,与业务有很强的关联性,具体业务对应具体的拆分数据;而水平拆分最小单元是数据行,与具体业务关系不大,业务关联可以是拆分后的单张表数据,也可以是拆分前的全局数据。简单来说,水平拆分对应用透明,应用逻辑在数据拆分后不需要大动。 正文 一般在关系数据库中,水平拆分具体对应两个方面: 第一是水平拆表。 水平拆表是基于一张表的某个字段,以一定的拆分方法拆分为多张表的数据拆分,拆分完后需要.

ActionTech的博客 803

版图学习汇总十一(DRC、LVS注意事项)

esd。

weixin_56071389的博客 2262

电力工程师必看:GOOSE/SV/MMS三协议在智能变电站中的分工与联动实例

本文深入解析了智能变电站中GOOSE、SV、MMS三大核心通信协议的分工与协同机制。GOOSE协议作为过程层的“闪电信使”,负责毫秒级传输跳闸命令等事件信息;SV协议则传输精确同步的电流电压采样值流;而MMS协议作为站控层的“管理语言”,实现设备监控与数据管理。三者基于IEC 61850标准,在过程层、间隔层和站控层之间高效联动,共同构建了智能变电站稳定可靠的通信骨架。

jj890的博客 366

新闻 | MySQL 9.2.0 有哪些功能新增、弃用和删除?

2025 年 1 月 21 日,MySQL 9.2.0 版本正式发布!作者:MySQL 官方文档本文约 800 字,预计阅读需要 3 分钟。根据 MySQL 版本发布计划,MySQL 9.2.0 是一个创新版,那么我们来看一下有哪些功能新增,弃用和删除。

ActionTech的博客 644

智能绿植管家:基于 ESP32 的室内植物自动补光与环境调控系统

本文设计的基于 ESP32 的室内植物自动补光与生长环境调控系统,实现了植物生长环境的自动化、智能化管理。硬件选型合理、接线简单,软件逻辑清晰、易于调试,能够有效解决室内植物生长环境不适的问题。系统具备良好的实用性和扩展性,可为园艺爱好者和小型培育场所提供稳定、高效的植物养护方案。

weixin_42300449的博客 147

MYSQL因IN的范围太大导致索引失效问题

背景 最近发现有个用于统计的门店串码激活数量的SQL特别慢,将其摘出来大致如下 SELECT a.sku_id as skuId,a.store_id as storeId, count(*) as saleQty FROM all_imei_info a where a.activated_time >= 1675530000000 and a.activated_ti...

翎野君的博客 3112

LVGL 拨盘菜单(Dial Menu)

h。

摸鱼专家 191

Win11此电脑怎么放在桌面上?Win11此电脑如何显示在电脑桌面上?

相信用习惯Win10的用户都喜欢电脑电脑桌面上的此电脑,但是很多用户更新完Win11正式版以后,发现桌面只有回收站和Microsoft Edge图标,没有此电脑。那么,Win11此电脑要如何显示呢?下面小编为大家简单介绍一下操作方法!5、桌面图标设置窗口,点击勾选计算机,然后点击确定,桌面就可以显示此电脑图标了。1、桌面空白处,点击右键,在打开的菜单项中,选择显示设置;4、个性化 - 主题,相关设置下,点击桌面图标设置;3、右侧点击主题(安装、创建、管理);2、设置窗口,左侧点击个性化;

shengyin714959的博客 13万+

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

MySQL优化器索引选择迷思。高鹏(八怪)对本文亦有贡献。1. 问题描述群友提出问题,表里有两个列c1、c2,分别为INT、VARCHAR类型,且分别创建了unique key。SQL查询...

老叶茶馆 850

多准则决策方法体系(AHP、熵权法、随机森林、加权线性法、TOPSIS、模糊综合评价)

多准则决策方法体系的基础,是由三大赋权方法和三大评价框架构成的 “3+3” 核心模块。赋权方法聚焦于准则重要性的量化,评价框架则专注于多准则信息的综合集成,二者共同覆盖了从 “确定权重” 到 “输出结果” 的全流程决策需求。

```On Earth, Be Water, By Coding``` 522

mySQL—索引

索引优缺点,哈希索引,BTree索引

且将&的博客 851

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

关于mysql索引说法错误的_下列对于数据库索引的说法一定是错误的()

说白了,索引问题就是一个查找问题。。。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数...

weixin_35974217的博客 4019

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

【MySQL】索引(多角度深入讲解索引)

索引是什么? 索引分为几种? 索引的理解 InnoDB、MyISAM 对Page的理解 索引的操作 创建索引、删除索引

Liyuyue的博客 421

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

MySQL索引

Mysql索引

xyh93936942的博客 206

MySQL执行计划误选索引及修改方案

查看该语句,where条件中a需要扫描1000行,b要扫描50000行,由于两者是and条件连接,所以我们认为使用索引a可以扫描更少的行数,因此,在查询时优化器会使用到a索引,但是使用explain执行时,可以看到explain命中了b索引,扫描了50128行。当某个字段需要排序的时候,那么同等条件下,会优先考虑使用排序的那个字段索引,因为直接使用排序字段做索引,查询的结果就是已经排好序的,无需再次排序。,由于索引有排序的功能,优化器认为使用b索引可以避免再次排序,所以使用了索引b。MySQL中可以使用。

FirstMrRight的博客 1288

日志:MySQL 百万级数据表使用了错误索引的解决方法

开门见山 今天偶然发现线上一SQL运行缓慢 druid 监控 最慢的时候达到了 30.8 秒。 SQL代码段: SELECT id, injection_machine_id, value_code, `value`, sample_time FROM `表` -- FORCE INDEX ( idx_machine_time ) WHERE injection_machine_id = 26100 ORDER BY sample_time DESC LIMIT 1

picc_lu的博客 754

MySQL中用到了索引还很慢的一个SQL场景

技术社群的这篇文章《技术分享 | MySQL 优化:为什么 SQL 走索引还那么慢?》讲了一个很常见,但经常会犯错误的场景,值得学习借鉴。背景2019-01-11 9:00-10:00 一个 MySQL 数据库把 CPU 打满了。硬件配置:256G 内存,56 core分析过程接手这个问题时现场已经不在了,信息有限,所以我们先从监控系统中查看一下当时的状态。从 PMM 监控来看,这个 MySQL ...

bisal的专栏 190

MYSQL数据库为何使用B+树,而不使用红黑树,二叉树作为索引

B+树是一种多路平衡查找树,每个节点可以存储多个关键字和数据,在B+树中,非叶子节点只存储关键字,而不存储数据,因此节点数比二叉树和红黑树更少,查询效率更高。因此,虽然红黑树也可以作为数据库的索引结构,但在处理大量数据时,B+树更适合作为索引结构,因为它可以更好地应对大规模数据的存储和查询需求,同时减少磁盘I/O操作,提高索引查询的效率。B+树可以减少索引更新的代价。由于B+树的非叶子节点只存储关键字,而不存储数据,因此在更新索引时,只需要更新叶子节点,而不需要更新非叶子节点,这可以减少索引更新的代价。

Jerry 1739

MySQL EXPLAIN 怎么用:不要一味创建索引,而要根据查询增加和删除索引

MySQL索引优化应基于查询分析而非盲目添加索引。通过EXPLAIN和EXPLAIN ANALYZE工具,需重点关注查询路径(type)、索引选择(key)、扫描行数(rows)及Extra信息中的排序/临时表等关键指标。索引设计应围绕高频查询模式而非孤立字段,同时需要定期清理冗余索引。优化流程应遵循:先识别关键查询→分析执行计划→合理调整SQL或索引→验证实际效果。记住,索引是以写入和存储成本换取查询收益,需要根据业务变化动态维护。

qq_43655834的博客 252

SQL 慢就 force index?先问优化器为什么不听你的

force index 应作为慢 SQL 优化的“最后一招”,而非首选。使用前需通过 explain 分析原因:统计信息过期、索引选择性差、SQL 写法导致索引失效或设计不合理,均应优先修复。强制走索引可能因扫描更大数据量反而更慢,且带来三大代价:索引名写死易出错、数据分布变化后性能下降、维护困难。建议优先改 SQL、建索引或更新统计信息,必要时用 USE INDEX 或 IGNORE INDEX 等更灵活提示。大表 count 慢本质是引擎特性,宜用近似值、汇总表或缓存解决。

鲜衣怒马楼兰月 2343

MySQL 索引失效的典型场景与优化方案(附详细代码)

本篇文章将系统梳理 MySQL 索引失效的典型场景,并为每个场景给出示例、失效原因、解决方案。同时,文章还会补充许多容易忽视的边角场景,并介绍如何通过 EXPLAIN 工具定位问题。

往者之不谏 来者犹可追 1431

MySQL回表与覆盖索引:如何权衡查询性能与索引维护成本

在MySQL数据库优化中,索引设计直接决定查询性能。InnoDB引擎采用聚簇索引与二级索引的B+树结构,当二级索引无法覆盖查询所需列时,就需要回表访问聚簇索引获取完整行,这往往成为慢查询的关键因素。覆盖索引通过将查询列纳入索引树,能够有效避免回表,提升读取效率,尤其适用于高频点查、统计查询和分页查询等场景。然而,覆盖索引并非万能,盲目堆砌索引列会导致写入变慢、索引膨胀,甚至优化器弃用索引。理解回表原理、EXPLAIN执行计划中的Using index标记,以及索引维护成本与过滤选择性的权衡,才是SQL性能优

weixin_33898876的博客 380

Mysql索引工作原理以及种类

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。 为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引...

m0_37564591的博客 777
上一篇: 技术分享 | 在MySQL对于批量更新操作的一种优化方式
下一篇: 故障案例 | 主从复制环境中tokudb引擎报错排查过程
GreatSQL社区
博客等级 码龄5年 1210粉丝 390原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值