阅读 211

有趣的MySQL(二):“order by”引发的乱序

人生苦短,不如养狗

一、背景

  MySQL可以说是一门比较容易上手但是也很容易出错的数据库语言。当你自信满满敲下一行SQL准备不看结果直接英雄式的转身时,不符合预期的执行结果会将你的脸打的 pia~ pia~ 响。

  前段时间闲鱼在写bug时遇到了这样一个需求:按照权重升序查询一组数据,新增的数据必须按照时间出现的列表的后面。看到这个需求,闲鱼想都没想,直接一个 order by ,然后自信回头,不带走一个bug。没想到过两天,产品告诉执行结果不符合预期。本着“你一定是在骗我,看我花式打你的脸”的心态,我执行了一下SQL:

SELECT * FROM member_tool_config WHERE isv_id IS NULL and user_id IS NULL ORDER BY weight ASC;
复制代码

  咦?一定是今天的风有些喧嚣,影响了SQL执行的结果......算了,还是老老实实查bug。

二、“order by”引发的乱序

  经过一番排查,发现罪魁祸首其实是 order by 。当使用 order by 的字段存在多行相同值时,就会导致上面出现的乱序问题,这里我们来看下官方的解释[1]

If multiple rows have identical values in the ORDER BY columns, the server is free to return those rows in any order, and may do so differently depending on the overall execution plan. In other words, the sort order of those rows is nondeterministic with respect to the nonordered columns.

  注意这里的用词 is free to return those rows in any order 。当出现多行相同值时,MySQL会 自由奔放 的以 任何顺序 返回结果集。当然也不会那么奔放,官方也在后面说了,可能会根据执行计划不同最终的执行情况也会不同,也就是说最终结果是不稳定的。

三、如何解决

  既然官方文档也说了,执行的结果很大程度受执行计划的影响,那么就意味着,在使用 order by我们需要明确查询的范围,细化查询条件,让MySQL在执行时更加了解我们的需求。比如上面的例子,除了使用权重,还可以增加创建时间作为排序的条件之一。具体SQL如下:

SELECT * FROM member_tool_config WHERE isv_id IS NULL and user_id IS NULL ORDER BY weight,gmt_create ASC;
复制代码

四、总结

  虽然问题得到了解决,也查看了官方的文档,但是总觉得好像有点缺憾。如果哪位大佬有更好的解释可以一起交流一下。最后感谢产品经理,让闲鱼在写bug之余也感受到了MySQL的“有趣”。

Reference

[1]

LIMIT Query Optimization: https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html

本文使用 mdnice 排版