震惊!阿里的程序员竟被一个简单的 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。
考虑新表的时候, 忽略和原库字符集的比较. 其实, 发现库里面的不同表可能都有不同的字符集, 不同人建的时候可能都依据个人喜好去选择了不同的字符集. 由此可见, 开发规范有多重要.
虽然知道索引列不能参与计算, 但这个场景下都是相同的类型,
varchar(64)
最终查询过程中仍然发生了类型转换. 因此需要把字段字符集不一致等同于字段类型不一致.如果这个 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
区块链必读“上链”哲学:“胖链下”与“瘦链上”
在商业中,如何与人工智能建立共生关系?
真香,朕在看了!
震惊!阿里的程序员竟被一个简单的 SQL 查询难住了!相关推荐
- 【Java程序员来写一个简单的HTML前端——映纷创意官网】
官网页面:INFINI | 映纷创意 (infinistudio.cn) 布局分析: 映纷创意.css *{margin: 0;padding: 0; } body{background-color: ...
- 阿里某程序员爆料:面试一个重庆小伙子,却被对方微信调戏!
面试中总是充满了各种未知因素,前几天我们写了一篇<某百度程序员中午面试一个阿里程序员,晚上去阿里面试,面试官竟是中午那个人!>,讲述了面试官和候选人之间神秘的缘分,让围观群众吃足了瓜.今天 ...
- 阿里某程序员吐槽:年终奖被金融行业的老婆完爆!自己奖金15万,老婆奖金66万!...
都说互联网的收入比其他行业高,在众多互联网大小厂中,阿里作为行业翘楚,其员工的工资收入也算遥遥领先,但一个阿里程序员却发帖抱怨自己年终奖被老婆完爆. 这位阿里程序员说,自己的年终奖是5个月工资,15万 ...
- 年薪 170 万阿里 P8 程序员征婚上热搜,程序员婚恋观大曝光!
整理 | 伍杏玲 出品 | 程序人生(ID:coder_life) 上个月,民政部公布,2018年中国单身成年人口已经超过2亿, 独居成年人口超过7700万. 前两天有一位阿里P8程序员决定告别这个& ...
- 阿里p7程序员:生活压力大,有房贷不敢离职,离职股票就没了
(阿里p7程序员:生活压力大,有房贷不敢离职,离职股票就没了) 作为一名程序员,虽然社会经验不多,但是,好多生活经验告诉我们,人并不是薪水越高压力就越小,这中间没有必然的联系,有人虽然开着轿车,百万年 ...
- 39岁的阿里P9程序员被裁了,存下了1.5亿...
前段时间,知乎上有个问题很火, "40 岁因为财务自由决定不上班的人,个人资产总和到底有多少" . 而其中一位来自阿里P9程序员的匿名回答,却让许多知友都狠狠酸了一把. 这位刚刚被 ...
- 程序员绝对是一个容易发胖的职业!程序员的自我修养:控制体重多运动
程序员「是不是容易秃顶 」,依然有待商榷,但程序员绝对是一个容易发胖的职业. 程序员不能跑着写代码,工作量一多,就要久坐.遇到难写的代码,大脑就要消耗很多能量,为了维持大脑运转,就需要摄入含糖量高的甜 ...
- 程序员必读:一个码农在硅谷的悲惨故事
这是一个悲惨的故事.一个硅谷码农被创业公司炒了鱿鱼,因为他开发的应用有太多Bug. 还能比这更惨一点吗?能的.这个码农不但丢了工作,还被公司在苹果应用商店公开指责.人生已经如此的艰难,有些事情为什么还 ...
- 凡客诚品-工作经历 程序员你有一个感恩的心吗?
<凡客诚品-工作经历 程序员你有一个感恩的心吗?>,作者:dz45693,原文链接:http://www.cnblogs.com/majiang/archive/2012/11/27/27 ...
最新文章
- 复数3+4i在python中的表达方式是_i(x+yi)=3+4i xy属于R,则复数x+yi的模是为什么xi-y=3+4......
- spark运行NLP
- 使用VS2008开发及部署Excel AddIn 心得
- 中国第二代身份证验证js代码
- localhost使用ipv4_使用 blackbox exporter 实现域名证书过期监控
- JQ 全选后获取选中的值_Filecoin如何创建账户钱包并获取FIL测试币
- 【jvm】jvm jstack使用 Java线程Dump分析
- 淘宝用html还url,html取出指定div的内容(不怕嵌套)
- 2019年第五届计蒜之道复赛总结
- 【组成原理-处理器】数据通路
- 墨墨背单词mysql_GitHub - FunStuff/WeChat-applets: 微信小程序小鸡单词
- 搜集百度关键词的相关网站、生成词云
- 个人 OKR 案例,帮助你变得更好
- 从测试流程角度,对产品质量的一些总结思考
- K-mer频率分布图代码实现
- 人工智能带来的岗位减少更多是重复性、机械性、门槛低的岗位
- go语言基础(二)函数,数组,指针,结构体
- 计算机架构设计的8个伟大思想
- eclipse里调用接口库时出现了错误 Undefined reference to
- OWASP TOP10-A1:注入
热门文章
- 双路服务器cpu必须型号相同,双路主板存在使用不同型号的cpu之说吗?还是必须使用一模一样相同的cpu型号?...
- 3测试图片显示置信度_云上的移动性能测试平台
- leetcode 112 --- 二叉树根节点到叶子节点和为指定值的路径
- mysql命令4类_【Mysql】mysql数据库的一些常用命令
- redis重启命令_请收下这份redis持久化详解
- 如何用notepad写php,notepad新手怎么使用
- 清华大学博士,就12年前抄袭一事公开道歉
- 32个机械动图,揭秘生活中制造原理
- André Weil | 数学史:为什么,怎么看
- 知识即战斗力!数学家华罗庚投入特殊抗战,一夜译破日军密码