一个简单的Mysql查询

2023-11-05

最近工作上遇到一个“神奇”的问题,或许对大家有帮助,因此形成本文。

问题大概是,我有两个表 TableA,TableB,其中 TableA 表大概百万行级别(存量业务数据),TableB 表几行(新业务场景,数据还未膨胀起来)。

image

语义上 TableA.columnA=TableB.columnA,其中 columnA 上建立了索引,但查询的时候确巨慢无比,基本上到 5-6 秒,明显跟预期不符合。

下面我以一个具体的例子来说明,模拟其中的 SQL 查询场景。

场景重现
user_info 表,为了场景尽量简单,我只 mock 了其中的三列数据。user_score 表,其中 uid 和 user_info.uid 语义一致。

 

image.png

其中数据情况如下, 都是很常见的场景:

 

image.png

索引情况如下图:

 

image.png

查询业务场景:已知 user_score.id,需要关联查询对应 user_info 的信息,(大家先忽略这个具体业务场景是否合理哈)。

那么对应的 SQL 很自然的如下:

 

image.png

请忽略其中的数据,我刚开始 mock 了 100W,然后又重复导入了两遍,因此数据有一些重复。

300W 数据,最后查询出来也是 1.18 秒,按道理应该更快的,老规矩 explain 看看啥情况?

 

image.png

发现 user_info 表没用上索引,全表扫描近 300W 数据?现象是这样,为什么呢?

你不妨思考一下,如果你遇到这种场景,应该怎么去排查?

image.png

我当时也是“一顿操作猛如虎”,然并卵?尝试了什么多种 SQL 写法来完成这个操作。

比如更换 Join 表的顺序(驱动表/被驱动表),再比如用子查询。最终,还是没有结果。但直接单表查询写 SQL 确能用上索引。

 

image.png

问题解决
尝试更换检索条件,比如更换 uid 直接关联查询,索引仍然用不上,差点放弃了都。

 

 

在准备求助 DBA 前,我看了下表的建表语句:

image.png

完全有理由怀疑因为字符集不一致的问题导致索引失效的问题。

于是修改了小表(真实线上环境可别乱操作)的字符集与大表一致,再测试下:

 

mysql> select * from user_score us
    -> inner join user_info ui on us.uid = ui.uid
    -> where us.id = 5;
+----+-----------+-------+---------+-----------+---------+
| id | uid       | score | id      | uid       | name    |
+----+-----------+-------+---------+-----------+---------+
|  5 | 111111111 |   100 |       1 | 111111111 | tanglei |
|  5 | 111111111 |   100 | 3685399 | 111111111 | tanglei |
|  5 | 111111111 |   100 | 3685400 | 111111111 | tanglei |
|  5 | 111111111 |   100 | 3685401 | 111111111 | tanglei |
|  5 | 111111111 |   100 | 3685402 | 111111111 | tanglei |
|  5 | 111111111 |   100 | 3685403 | 111111111 | tanglei |
+----+-----------+-------+---------+-----------+---------+
6 rows in set (0.00 sec)

mysql> explain
    -> select * from user_score us
    -> inner join user_info ui on us.uid = ui.uid
    -> where us.id = 5;
+----+-------------+-------+-------+-------------------+-----------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys     | key       | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+-------------------+-----------+---------+-------+------+-------+
|  1 | SIMPLE      | us    | const | PRIMARY,index_uid | PRIMARY   | 4       | const |    1 | NULL  |
|  1 | SIMPLE      | ui    | ref   | index_uid         | index_uid | 194     | const |    6 | NULL  |
+----+-------------+-------+-------+-------------------+-----------+---------+-------+------+-------+
2 rows in set (0.00 sec)

果然 Work 了。

挖掘根因
其实深究原因,就是网上各种 MySQL 军规/规约所提到的, “索引列不要参与计算”。

这次这个 case,如果知道 explain extended+show warnings 这个工具的话,(以前都不知道 explain 后面还能加 extended 参数),可能就尽早“恍然大悟”了。(最新的 MySQL 8.0 版本貌似不需要另外加这个关键字)

看下效果:(啊,我还得把字符集改回去)

 

mysql> explain extended select * from user_score us  inner join user_info ui on us.uid = ui.uid where us.id = 5;
+----+-------------+-------+-------+-------------------+---------+---------+-------+---------+----------+-------------+
| id | select_type | table | type  | possible_keys     | key     | key_len | ref   | rows    | filtered | Extra       |
+----+-------------+-------+-------+-------------------+---------+---------+-------+---------+----------+-------------+
|  1 | SIMPLE      | us    | const | PRIMARY,index_uid | PRIMARY | 4       | const |       1 |   100.00 | NULL        |
|  1 | SIMPLE      | ui    | ALL   | NULL              | NULL    | NULL    | NULL  | 2989934 |   100.00 | Using where |
+----+-------------+-------+-------+-------------------+---------+---------+-------+---------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                              |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select '5' AS `id`,'111111111' AS `uid`,'100' AS `score`,`test`.`ui`.`id` AS `id`,`test`.`ui`.`uid` AS `uid`,`test`.`ui`.`name` AS `name` from `test`.`user_score` `us` join `test`.`user_info` `ui` where (('111111111' = convert(`test`.`ui`.`uid` using utf8mb4))) |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

索引列参与计算了,每次都要根据字符集去转换,全表扫描,你说能快得起来么?

至于这个问题为什么会发生?综合来看,就是因为历史原因,老业务场景中的原表是假 utf8,新业务新表采用了真 utf8mb4。

①考虑新表的时候,忽略和原库字符集的比较。其实,发现库里面的不同表可能都有不同的字符集,不同人建的时候可能都依据个人喜好去选择了不同的字符集。由此可见,开发规范有多重要。

②虽然知道索引列不能参与计算,但这个场景下都是相同的类型,varchar(64) 最终查询过程中仍然发生了类型转换。因此需要把字段字符集不一致等同于字段类型不一致。

③如果这个 case,利用 fail-fast 的理念的话,发现不一致,直接不让 join 会不会更好?(就像 char v.s varchar 不能 join 一样)

说明:本文测试场景基于 MySQL 5.6,另外,本文案例只是为了说明问题,其中的 SQL 并不规范(例如尽量别用 select * 之类的),请勿模仿(模仿了我也不负责)。

最后留一个思考题供讨论,欢迎留言说出你的看法。

你能解释如下情况吗?查询结果表现为何不一致?注意一下 SQL 的执行顺序,查询优化器工作流程,以及其中的 Using join buffer(Block Nested Loop)。

可以多看看 MySQL 官方手册深入了解背后的过程和原理:

 

https://dev.mysql.com/doc/refman/5.6/en/

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一个简单的Mysql查询 的相关文章

  • 删除、截断或删除以清理 MySQL 中的表

    我正在尝试清理表格 但没有摆脱表格的实际结构 我有一个id自动递增的列 我不需要保留ID号 但我确实需要它来保持其自动递增的特性 我发现了删除和截断 但我担心其中之一会完全删除整个表 从而使未来的插入命令变得无用 如何从表中删除所有记录以便
  • Errno 121,写入或更新时出现重复密钥?

    SET OLD UNIQUE CHECKS UNIQUE CHECKS UNIQUE CHECKS 0 SET OLD FOREIGN KEY CHECKS FOREIGN KEY CHECKS FOREIGN KEY CHECKS 0 S
  • MySQL 中布尔值的 TINYINT 与 ENUM(0, 1)

    MyISAM 表和 MySQL 5 1 中具有 0 和 1 值的 Tinyint 或 ENUM 0 1 哪个更好 您可以使用BIT 1 如中提到的MySQL 5 1 参考 http dev mysql com doc refman 5 1
  • ODBC 链接表中突然开始出现写入冲突消息

    我有一个 mySQL 数据库 用于跟踪我们的项目并驱动我们的网站显示其信息 为了方便更新数据库 我设置了一个使用 ODBC 连接 MySQL ODBC 5 1 来编辑数据的访问数据库 过去几个月一直运行良好 没有出现任何问题 然而 昨晚用户
  • Innodb页面大小设置

    在innodb中 页面大小默认为16kb 如何将页面大小设置为 8kb 是否有在源编译步骤中设置的选项 您不需要在源编译步骤中指定页面大小 MySQL 5 6 及更高版本支持不同的页面大小 无需重新编译 但是 您必须在初始化 InnoDB
  • 在 Django 查询中使用 .extra(select={...}) 引入的值上使用 .aggregate() ?

    我正在尝试计算玩家每周玩游戏的次数 如下所示 player game objects extra select week WEEK games game date aggregate count Count week 但姜戈抱怨说 Fiel
  • 如何在 bash 上运行 MySQL 命令?

    以下代码在命令行上运行 mysql user myusername password mypassword database mydatabase execute DROP DATABASE myusername CREATE DATABA
  • 如何在 Laravel 5 中使用 Orchestral/Tenanti 构建具有多个数据库的多租户应用程序?

    我正在尝试使用 Laravel 5 构建和应用程序 它应该是使用多个数据库的多租户数据库架构 我的雇主出于安全目的要求这样做 我尝试手动管理主数据库迁移和租户迁移 但失败了 所以我决定借助 Laravel 特定包的帮助 这应该是我所需要的
  • 从 CSV 到 MySQL 的换行问题

    我正在将 csv 文件导入 MySQL 除了文件中的换行符之外 一切正常 我的 csv 行之一如下所示 42 E A R Classic Earplugs ear images ear classic jpg 5 Proven size s
  • 用教义 2 DBAL 连接子查询

    我正在重构 Zend 框架2应用程序使用学说 2 5 DBAL 而不是 Zend DB ZF1 我有以下 Zend Db 查询 subSelect db gt select gt from user survey status entrie
  • 连接两个表而不返回不需要的行

    我的表结构如下所示 tbl users tbl issues userid real name issueid assignedid creatorid 1 test 1 1 1 1 2 test 2 2 1
  • MySQL 5:我的 GROUP BY 字段的顺序重要吗?

    Peeps 我的 MySQL 查询中有一些聚合 计算字段 我的 GROUP BY 子句是动态生成的 具体取决于用户在 Web 表单中选择的选项 很好奇 GROUP BY 子句中列出的字段顺序是否会对计算产生任何影响 例如 SUM AVERA
  • 使用 Sequelize (NodeJS) 代替 * 指定特定字段

    好吧 我在 NodeJS 中有一个项目 我正在其中使用 Sequelize 来实现 MySQL ORM 这件事工作得非常好 但是我试图弄清楚是否有一种方法可以指定在查询的基础上返回哪些字段 或者是否有一种方法可以在某处执行 query 例如
  • 错误代码:1062。重复条目“PRIMARY”

    因此 我的教授给了我表格将其插入数据库 但是当我执行他的代码时 MySQL 不断给出错误代码 1062 这是冲突表和插入 TABLES CREATE TABLE FABRICANTES COD FABRICANTE integer NOT
  • 在内连接中重用 mysql 子查询

    我正在尝试优化查询 试图避免重复用 指示的查询 复杂查询 使用两次 结果相同 原始查询 SELECT news FROM news INNER JOIN SELECT myposter FROM SELECT COMPLEX QUERY U
  • Mysql 将 --secure-file-priv 选项设置为 NULL

    我在 Ubuntu 中运行 MySQL 我在运行特定的查询集时收到此错误 MySQL 服务器正在使用 secure file priv 选项运行 因此无法执行此语句 当我这样做的时候SELECT secure file priv 在我的 m
  • 内连接 3 个表

    我正在使用 PHP 和 PDO 我需要重新收集连接 3 个表的信息 photos albums 相册照片 该表具有以下结构 photos photo id int path varchar nick varchar date timesta
  • Laravel 读写连接不同步

    我在 Laravel 5 2 应用程序中使用读写 MySQL 连接设置 mysql gt write gt host gt env DB HOST WRITE localhost read gt host gt env DB HOST RE
  • 如何删除MySQL中的所有事件

    如果我想删除某个事件 我需要查询类似的内容 DROP EVENT IF EXISTS eventname 但我找不到一次性删除所有事件的命令 必须一项一项地删除 有没有一次性删除所有事件的SQL DROP EVENT IF EXISTS S
  • MySQL 两种日期格式之间的转换

    用户将以这种格式输入日期 2017 年 2 月 17 日 存储在 mysql 数据库中的日期格式如下 2015 02 17 00 00 00 我想做的是 SELECT FROM insurance where DATE FORMAT in

随机推荐

  • grafana改用https登录

    grafana添加HTTPS证书 安装 openssl yum install y openssl 创建 certificates openssl req x509 out server crt keyout server key newk
  • mysql--排序

    在项目开发时 为了使查询的数据结果满足用户的要求 通常会对查询出的数据进行上升或下降的排序 MySQL针对不同的开发需求提供两种排序的方式 分别为单字段排序和多字段排序 接下来将对这两种排序方式的语法及使用进行详细讲解 1 单字段排序 单字
  • Android 7.0新特性总结

    2016年8月22日 谷歌正式推送Android 7 0 Nougat 牛轧糖 正式版 他们还会三个月一次推送开发版 而曝光的消息看 第一个开发版就是Android 7 1 Android N主要新增了以下的新特性和优化 一 新的Notif
  • LeetCode_433. 最小基因变化

    题目链接 力扣 这道题是一道经典的BFS题型 我觉得可能会踩坑导致不能一次AC的地方有两处 一是bankSize可能为0 那么我们开辟一个记录数组的时候会报错 二是题目所说的 起始基因序列 start 默认是有效的 但是它并不一定会出现在基
  • java 多线程 总结三

    本文转载至 http blog csdn net vking wang article details 9952063 1 synchronized 把代码块声明为 synchronized 有两个重要后果 通常是指该代码具有 原子性 at
  • MYSQL--基础--06--delete删除数据,磁盘空间未释放的解决办法

    MYSQL 基础 06 delete删除数据 磁盘空间未释放的解决办法 1 原因 使用delete删除数据 不会把数据文件删除 而是将数据文件的标识位删除 因此会留下数据碎片 当有新数据写入的时候 mysql会利用这些已删除的空间再写入 如
  • MIPI D-PHY TX 一致性测试实例解析 Part 02

    如果测过1 3 x 并对这组测试的细节已经熟悉的前提下 1 4 x的测试将会变得轻松 因为几乎相同的测试项被用于时钟通道的数据测量 因此 接下来 仅着重对其中的不同点进行讨论 由于小编的工程应用中 不需要时钟工作于LP模式 因此 本章节中部
  • 点云文件的格式转换:ply转pcd,pcd转pth,查看pth格式文件

    1 ply格式转pcd格式 import open3d as o3d def convert ply to pcd ply file pcd file 读取PLY文件 point cloud o3d io read point cloud
  • jdk-jmap命令

    jmap命令详解 jmap是JVM自带的堆内存转储 heap dump 生成工具 可以用来分析某JVM进程的堆内存占用 以及所有对象的概况 其用法说明如下所示 heap 打印堆配置信息和使用概况 在Heap Configuration一节
  • 努比亚Z11系统服务器选择,努比亚Z11系统升级,赶紧来感受一下脱胎换骨的流畅感...

    原标题 努比亚Z11系统升级 赶紧来感受一下脱胎换骨的流畅感 苹果正式发布了新系统后 可当满心欢喜的果粉们在升级系统后却发现新系统中存在各式花样漏洞与BUG 而且最无法忍受的是老旧型在更新系统之后手机续航能力急速下滑 相信每个人都收到系统推
  • Kconfig内容(详细)总结附示例快速掌握

    目录 一 简介 二 内容解析 2 1 menuconfig 2 2 choice endchoice 2 3 comment 2 4 menu endmunu 2 5 if endif 2 6 source 2 7 mainmenu 2 8
  • shell 中进行算术计算的各种方法

    shell中 无法直接进行算术运算 如果直接进行算术运算会出现如下情况 1 shell中进行算术运算的各种方法 默认情况下 shell不会直接进行算术运算 而是把 算术符号 当做 字符串 与两个变量的值连接在了一起 形成了一个新的字符串 那
  • 病虫害模型算法_AI识别病虫害 在线诊断“疑难杂症”

    来源 经济日报 中国经济网 我们的产品 识农 将人工智能技术应用于农作物病虫害的诊断 识别率能达到90 以上 近日 深圳市识农智能科技有限公司CEO谢秋发在接受经济日报 中国经济网采访时表示 谢秋发介绍说 目前我们主要是聚焦于柑橘 葡萄 苹
  • [Ubuntu] [Qt] Ubuntu安装并配置Qt5.15.2环境

    1 通过清华源下载qt镜像包 官网太慢了 新版本都只能在线安装 https mirrors tuna tsinghua edu cn qt official releases online installers qt unified lin
  • mysql5.7.24-win32安装及配置

    一 Mysql安装 安装包mysql 5 7 24 win32 zip 解压该安装包 将解压后的文件夹mysql 5 7 24 win32放到C盘根目录下 置mysql环境变量 系统变量 新建 变量名为MYSQL HOME 变量值为C my
  • java无重复字符的最长子串

    给定一个字符串 s 请你找出其中不含有重复字符的 最长子串 的长度 示例 1 输入 s abcabcbb 输出 3 解释 因为无重复字符的最长子串是 abc 所以其长度为 3 示例 2 输入 s bbbbb 输出 1 解释 因为无重复字符的
  • 如何用java实现增删改查

    使用Java实现增删改查操作需要连接数据库 例如使用JDBC或者其他ORM框架 下面是一个简单的例子 展示如何使用Java和JDBC进行增删改查操作 导入JDBC驱动程序 在Java项目中 需要将相应的JDBC驱动程序添加到项目的依赖中 建
  • qt-两个界面传值交互

    一 说明 A 子界面 B 主界面 实现A往B传值 B显示 二 利用emit和slot实现 2 1 对A h 添加声明 signals void sendData QString 用来传递数据的信号 2 2在A cpp中适当位置将数据进行发射
  • JDK1.8和JDK8是同一个版本吗?

    是的 JDK1 8和JDK8是同一个版本 最开始 命名为 JDK1 JDK2 后来就 命名为 JDK1 7 JDK1 8 Java Development Kit JDK 是Sun公司 已被Oracle收购 针对Java开发员的软件开发工具
  • 一个简单的Mysql查询

    最近工作上遇到一个 神奇 的问题 或许对大家有帮助 因此形成本文 问题大概是 我有两个表 TableA TableB 其中 TableA 表大概百万行级别 存量业务数据 TableB 表几行 新业务场景 数据还未膨胀起来 image 语义上