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

图片来自 Pexels

语义上 TableA.columnA=TableB.columnA,其中 columnA 上建立了索引,但查询的时候确巨慢无比,基本上到 5-6 秒,明显跟预期不符合。
下面我以一个具体的例子来说明,模拟其中的 SQL 查询场景。
场景重现



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

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

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

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

问题解决
在准备求助 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)
挖掘根因
看下效果:(啊,我还得把字符集改回去)
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)
①考虑新表的时候,忽略和原库字符集的比较。其实,发现库里面的不同表可能都有不同的字符集,不同人建的时候可能都依据个人喜好去选择了不同的字符集。由此可见,开发规范有多重要。
②虽然知道索引列不能参与计算,但这个场景下都是相同的类型,varchar(64) 最终查询过程中仍然发生了类型转换。因此需要把字段字符集不一致等同于字段类型不一致。
③如果这个 case,利用 fail-fast 的理念的话,发现不一致,直接不让 join 会不会更好?(就像 char v.s varchar 不能 join 一样)
可以多看看 MySQL 官方手册深入了解背后的过程和原理:
https://dev.mysql.com/doc/refman/5.6/en/

作者:唐磊
简介:清华学渣,目前就职阿里云,曾就职于大疆,宜信,Tencent,友盟。
编辑:陶家龙
出处:转载自公众号程序猿石头(ID:tangleithu)

精彩文章推荐:





