震惊!阿里的程序员竟被一个简单的 SQL 查询难住了!

作者 | 唐磊

责编 | Carol

来源 | 程序猿石头

封图 | CSDN 付费下载于视觉中国

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

问题大概是,我有两个表 TableA,TableB,其中 TableA 表大概百万行级别(存量业务数据),TableB 表几行(新业务场景, 数据还未膨胀起来),语义上  TableA.columnA = TableB.columnA,其中 columnA 上建立了索引,但查询的时候确巨慢无比, 基本上到5-6 秒,明显跟预期不符合。

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

场景重现

  • user_info 表, 为了场景尽量简单, 我只 mock 了其中的三列数据。

  • user_score 表,其中 uid 和 user_info.uid 语义一致。

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

  • 索引情况是

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

请忽略其中的数据,我刚开始 mock 了 100W,然后又重复导入了两遍, 因此数据有一些重复。300W 数据, 最后查询出来也是 1.18 秒,按道理应该更快的。老规矩 explain 看看啥情况?

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

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

(分割线, 花 10 秒想想?)


我当时也是”一顿操作猛如虎”,然并卵? 尝试了什么多种 sql 写法来完成这个操作,比如更换Join表的顺序(驱动表/被驱动表), 再比如用子查询。最终,还是没有结果。但直接单表查询写 SQL 确能用上索引。

问题解决

尝试更换检索条件,比如更换 uid 直接关联查询,索引仍然用不上, 差点放弃了都。在准备求助 DBA 前, 看了下表的建表语句。

完全有理由怀疑因为字符集不一致的问题导致索引失效的问题了。
于是修改了小表(真实线上环境可别乱操作)的字符集与大表一致, 再测试下。

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。

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

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

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

说明: 本文测试场景基于 MySQL 5.6, 另外, 本文案例只是为了说明问题, 其中的 SQL 并不规范(例如尽量别用 select * 之类的), 请勿模仿(模仿了我也不负责).  为了写本文, 可花了不少时间, 建 DB, mock数据, 包括排版公众号(啊,公众号后台对代码格式还是不友好, markdown 转来代码格式还是有问题)等等, 如果觉得有用, 还望你帮忙"在看", "转发". 最后留一个思考题供讨论, 欢迎留言说出你的看法。

留一道思考题

你能解释如下情况吗? 查询结果表现为何不一致?  注意一下 SQL 的执行顺序, 查询优化器工作流程,以及其中的 Using join buffer (Block Nested Loop), 可以多看看 [MySQL 官方手册](https://dev.mysql.com/doc/refman/5.6/en/) 深入了解背后的过程和原理。

作者简介

唐磊,码农@阿里云,硕士毕业于清华大学,曾工作于大疆,宜信大数据创新中心,Tencent和友盟。欢迎关注,多多交流多多指教????

 

推荐阅读

  • 手把手教你配置VS Code 远程开发工具,工作效率提升N倍

  • 用大白话彻底搞懂 HBase RowKey 详细设计

  • 后端程序员必备:书写高质量SQL的30条建议

  • Go 远超 Python,机器学习人才极度稀缺,全球 16,655 位程序员告诉你这些真相!

  • 任正非谈“狼文化”:华为没有 996,更没有 007

  • 区块链必读“上链”哲学:“胖链下”与“瘦链上”

  • 在商业中,如何与人工智能建立共生关系?

真香,朕在看了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/517926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ETL异构数据源Datax_datax-web安装部署_10

文章目录1. 解压2. 一键安装3. 修改执行器py地址4. 一键启动5. 查看启动日志6. 访问地址登录7. 操作记录1. 解压 tar -zxvf datax-web-2.1.2.tar.gz2. 一键安装 # 执行一键安装脚本,系统中需要有mysql环境,会自动执行sql文件初始化bin/install.sh cd datax-web-2.1.2/ bin/in…

3D 真的很难吗,瞧瞧支付宝怎么做?

阿里妹导读:图像作为人类感知世界的视觉基础,是我们在这个信息化时代获取信息、表达信息及传递信息的重要手段,而生成图像最高效准确的方式就是由计算机生成、显示、绘制,这些技术又统称计算机图形技术。计算机图形技术已经是许多…

OpenStack发布Ussuri版本 实现智能开源基础设施的自动化

从2010年到2020年,OpenStack项目整整走过了十个春夏秋冬。10年来,关于OpenStack的争议持续不断,但不可否认的是,在不同技术路线的争执中,OpenStack所展现的蓬勃生命力仍无可比拟。据451研究机构2019年9月发布的市场监测…

ETL异构数据源Datax_图形化数据同步_11

数据同步全量增量知识SQL和执行频次不一样,其他的都一样 文章目录一、Oracle同步Mysql(全量)1. 添加项目2. 添加数据源3. 添加任务4. 构建json5. 任务执行6. 查看日志7. 同步数量对比二、Mysql同步Oracle(全量)2.1. 添加构建reader2.2. 添加构建writer2.3. 配置同步…

RocketMQ消息轨迹-设计篇

RocketMQ 消息轨迹主要包含两篇文章:设计篇与源码分析篇,本节将详细介绍RocketMQ消息轨迹-设计相关。 RocketMQ消息轨迹,主要跟踪消息发送、消息消费的轨迹,即详细记录消息各个处理环节的日志,从设计上至少需要解决如…

红帽加速开放混合云创新,助力企业成功迈向开源应用时代

2020年的开局让人有些意想不到,一场新冠疫情给人们的生活带来了前所未有的改变。对于企业更是有着数不尽的困难与挑战。在过去的三个月当中,全世界都面临着有关于“虚拟会议”所带来的便捷与隐患问题,原因在于虚拟会议中对于网络体验与安全的…

ETL异构数据源Datax_自增ID增量同步_12

文章目录增量同步方法 1.先同步存量数据 2.再同步增量数据(根据自增ID、日期条件) 前提条件: 1.只针对数据增长,如果老数据被update/delete则无法使用增量同步方式。 基于主键自增ID增量同步 Oracle同步Mysql演示 idno采用序列自增 添加项目 AutoIncr…

K8S从懵圈到熟练 - 节点下线姊妹篇

之前分享过一例集群节点NotReady的问题。在那个问题中,我们的排查路劲,从K8S集群到容器运行时,再到sdbus和systemd,不可谓不复杂。那个问题目前已经在systemd中做了修复,所以基本上能看到那个问题的几率是越来越低了。…

PLSQL 查询结果只显示年月日不显示时分秒的解决方法

PLSQL 14查询结果只显示年月日不显示时分秒的解决方法 如果plsql里面日期显示出现混乱,并且按照一般的调整首选项里日期格式调整不了,可以修改环境变量,新建环境变量,变量名:nls_date_format 变量值:YYYY-…

阿里云“网红“运维工程师白金:做一个平凡的圆梦人

他是阿里云的一位 P8 运维专家,却很有野心得给自己取花名“辟拾(P10)”; 他没有华丽的履历,仅凭着 26 年的热爱与坚持,一步一个脚印踏出了属于自己的技术逆袭之路; 他爱好清奇,练就了…

巧用 Trie 树,实现搜索引擎关键词提示功能

来源 | 码海责编 | Carol封图 | CSDN 付费下载于视觉中国我们几乎每天都在用搜索引擎搜索信息,相信大家肯定有注意过这样一个细节:当输入某个字符的时候,搜索引框底下会出现多个推荐词,如下,输入「python」后,底下会出…

这一团糟的代码,真的是我写的?!

阿里妹导读:你有没有遇到过这种情况:过几周或者几个月之后,再看到自己写的代码,感觉一团糟,不禁怀疑人生?我们每天都与代码打交道,但当被问道什么是好的代码时,很多人可能会先愣一下…

迅雷下载Linux Oracle11gR2和Oracle12c

很多朋友分享的都是百度云盘链接,那个下载速度对于贫民来说,真的是等到花都谢了 而Oracle官网现在只能下载Oracle19C了,特此分享11和12版本的迅雷下载链接 11g下载链接: https://download.oracle.com/otn/linux/oracle11g/R2/lin…

除了吃月饼,中秋节还能干啥?

明天 八月十五,团圆夜 花好月圆之际 除了吃月饼,还能干啥? 阿里妹带来双重好礼,陪你过中秋~ (往下看,送云栖大会三日通票哦) 1重礼 — 阿里技术热门精选 — 《Java 开发手册》发布&#…

恭喜了!5 月逼自己学下这项技能,年薪 35 万起

你觉得 15 年内自己所在的 IT 行业很安全吗?No!据麦肯锡全球研究院发布的一份就业报告中显示,到 2030 年,中国预计将有 1200 万~ 1.02 亿人面临重新就业的局面。越来越多的人学习编程不再只是为了当程序员,…

Linux7/Redhat7/Centos7 安装Oracle 12C_系统安装_01

文章目录一、安装虚拟机1. 新建虚拟机2. 稍后安装3. 选择linux版本4. 安装位置5. 处理器配置6. 内存配置7. 网络配置8. 创建新磁盘9. 设置磁盘大小10. 选择镜像,完成二、虚拟机配置2.1. 重新启动2.2. 选择语言2.3. 设置时区2.4. 选择安装模式2.5. 自定义分区2.6. 接…

源码分析RocketMQ ACL实现机制

有关RocketMQ ACL的使用请查看上一篇《RocketMQ ACL使用指南》,本文从源码的角度,分析一下RocketMQ ACL的实现原理。 备注:RocketMQ在4.4.0时引入了ACL机制,本文代码基于RocketMQ4.5.0版本。 根据RocketMQ ACL使用手册&#xff0c…

Linux7/Redhat7/Centos7 安装Oracle 12C_配置IP、系统参数_02

文章目录一、基础配置1. 重新启动2. 安装VMware Tools3. 修改主机名4. 网络配置5. 重新加载网络二、配置系统参数2.1. 检查硬件环境2.2. 查看共享内存大小2.3. 创建用户和组2.4. 配置内核参数2.5. 配置资源限制2.6. 创建目录赋予权限2.7. 设置环境变量2.8. 映射配置三、基础关闭…

华为:跨过时艰,向未来

[中国,深圳,2020年5月18日] 华为公司第十七届全球分析师大会18日在深圳开幕。华为与来自全球的2000多名行业分析师,金融分析师,通讯、互联网、金融等行业意见领袖和媒体一起,以现场在线的方式,共同探讨在当…

阿里巴巴飞天大数据架构体系与Hadoop生态系统

很多人问阿里的飞天大数据平台、云梯2、MaxCompute、实时计算到底是什么,和自建Hadoop平台有什么区别。 先说Hadoop 什么是Hadoop? Hadoop是一个开源、高可靠、可扩展的分布式大数据计算框架系统,主要用来解决海量数据的存储、分析、分布式…