作者 | 唐磊

责编 | 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

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

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

真香,朕在看了!

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

  1. 【Java程序员来写一个简单的HTML前端——映纷创意官网】

    官网页面:INFINI | 映纷创意 (infinistudio.cn) 布局分析: 映纷创意.css *{margin: 0;padding: 0; } body{background-color: ...

  2. 阿里某程序员爆料:面试一个重庆小伙子,却被对方微信调戏!

    面试中总是充满了各种未知因素,前几天我们写了一篇<某百度程序员中午面试一个阿里程序员,晚上去阿里面试,面试官竟是中午那个人!>,讲述了面试官和候选人之间神秘的缘分,让围观群众吃足了瓜.今天 ...

  3. 阿里某程序员吐槽:年终奖被金融行业的老婆完爆!自己奖金15万,老婆奖金66万!...

    都说互联网的收入比其他行业高,在众多互联网大小厂中,阿里作为行业翘楚,其员工的工资收入也算遥遥领先,但一个阿里程序员却发帖抱怨自己年终奖被老婆完爆. 这位阿里程序员说,自己的年终奖是5个月工资,15万 ...

  4. 年薪 170 万阿里 P8 程序员征婚上热搜,程序员婚恋观大曝光!

    整理 | 伍杏玲 出品 | 程序人生(ID:coder_life) 上个月,民政部公布,2018年中国单身成年人口已经超过2亿, 独居成年人口超过7700万. 前两天有一位阿里P8程序员决定告别这个& ...

  5. 阿里p7程序员:生活压力大,有房贷不敢离职,离职股票就没了

    (阿里p7程序员:生活压力大,有房贷不敢离职,离职股票就没了) 作为一名程序员,虽然社会经验不多,但是,好多生活经验告诉我们,人并不是薪水越高压力就越小,这中间没有必然的联系,有人虽然开着轿车,百万年 ...

  6. 39岁的阿里P9程序员被裁了,存下了1.5亿...

    前段时间,知乎上有个问题很火, "40 岁因为财务自由决定不上班的人,个人资产总和到底有多少" . 而其中一位来自阿里P9程序员的匿名回答,却让许多知友都狠狠酸了一把. 这位刚刚被 ...

  7. 程序员绝对是一个容易发胖的职业!程序员的自我修养:控制体重多运动

    程序员「是不是容易秃顶 」,依然有待商榷,但程序员绝对是一个容易发胖的职业. 程序员不能跑着写代码,工作量一多,就要久坐.遇到难写的代码,大脑就要消耗很多能量,为了维持大脑运转,就需要摄入含糖量高的甜 ...

  8. 程序员必读:一个码农在硅谷的悲惨故事

    这是一个悲惨的故事.一个硅谷码农被创业公司炒了鱿鱼,因为他开发的应用有太多Bug. 还能比这更惨一点吗?能的.这个码农不但丢了工作,还被公司在苹果应用商店公开指责.人生已经如此的艰难,有些事情为什么还 ...

  9. 凡客诚品-工作经历 程序员你有一个感恩的心吗?

    <凡客诚品-工作经历 程序员你有一个感恩的心吗?>,作者:dz45693,原文链接:http://www.cnblogs.com/majiang/archive/2012/11/27/27 ...

最新文章

  1. 复数3+4i在python中的表达方式是_i(x+yi)=3+4i xy属于R,则复数x+yi的模是为什么xi-y=3+4......
  2. spark运行NLP
  3. 使用VS2008开发及部署Excel AddIn 心得
  4. 中国第二代身份证验证js代码
  5. localhost使用ipv4_使用 blackbox exporter 实现域名证书过期监控
  6. JQ 全选后获取选中的值_Filecoin如何创建账户钱包并获取FIL测试币
  7. 【jvm】jvm jstack使用 Java线程Dump分析
  8. 淘宝用html还url,html取出指定div的内容(不怕嵌套)
  9. 2019年第五届计蒜之道复赛总结
  10. 【组成原理-处理器】数据通路
  11. 墨墨背单词mysql_GitHub - FunStuff/WeChat-applets: 微信小程序小鸡单词
  12. 搜集百度关键词的相关网站、生成词云
  13. 个人 OKR 案例,帮助你变得更好
  14. 从测试流程角度,对产品质量的一些总结思考
  15. K-mer频率分布图代码实现
  16. 人工智能带来的岗位减少更多是重复性、机械性、门槛低的岗位
  17. go语言基础(二)函数,数组,指针,结构体
  18. 计算机架构设计的8个伟大思想
  19. eclipse里调用接口库时出现了错误 Undefined reference to
  20. OWASP TOP10-A1:注入

热门文章

  1. 双路服务器cpu必须型号相同,双路主板存在使用不同型号的cpu之说吗?还是必须使用一模一样相同的cpu型号?...
  2. 3测试图片显示置信度_云上的移动性能测试平台
  3. leetcode 112 --- 二叉树根节点到叶子节点和为指定值的路径
  4. mysql命令4类_【Mysql】mysql数据库的一些常用命令
  5. redis重启命令_请收下这份redis持久化详解
  6. 如何用notepad写php,notepad新手怎么使用
  7. 清华大学博士,就12年前抄袭一事公开道歉
  8. 32个机械动图,揭秘生活中制造原理
  9. André Weil | 数学史:为什么,怎么看
  10. 知识即战斗力!数学家华罗庚投入特殊抗战,一夜译破日军密码