前言
刚到公司接手了一个项目,那个项目中有一个奇葩的 Bug 让我记忆犹新。今天写一篇博客记录下这块的知识点和坑。这个 Bug 是这样的:一条数据在数据库中的存储顺序是 A->B->C
。但是当输出到页面上的时候就莫名其妙变成了 B->A->C
。当时碰到这个 Bug,我首先去阅读了前人写的代码逻辑,发现单看逻辑代码怎么都没有问题。知道我追踪到 mapper 层自己调试了下 SQL 语句后才真正找到了 IN 子句隐藏的坑!
"IN" 查询简介
"IN" 查询是 MySQL 中一种非常重要的查询方式。它允许我们查询某个列或者字段中是否包含了多个值。
例如,下面这个查询可以返回”OrderNumber”列中包含了值为1、3、5的所有行:
SELECT * FROM Orders WHERE OrderNumber IN (1, 3, 5);
使用”IN”查询可以非常方便地进行数据过滤和查找。但是!真正得到的数据顺序可能并不能如我们所愿。例如:我们查询的顺序是 1->3->5
,但在实际测试的过程中返回结果的 ID 顺序是 3->1->5
。下面,我来讲解下为什么会出现这种情况?
为什么 "IN" 查询的结果顺序不稳定?
在MySQL中,查询的结果是存储在一个缓冲区中的,而这个缓冲区的大小是有限制的。如果查询结果中的行数超出了缓冲区的大小,MySQL就会使用临时表来存储剩余的数据。这个临时表的排序方式可能与原表不同,从而导致查询结果的顺序不稳定。为了解决这个问题,我们可以使用 "ORDER BY FIELD" 排序方式来保持查询结果的顺序。
解决方式(2种)
1. 使用 FIELD() 函数来实现根据给定的值在查询结果中的出现顺序来排序查询结果。
2. 建立临时表或派生表,在临时表中设置正确的索引顺序。
以上两种方式各有各的使用场景。下面,我将给大家详细讲解这两种思路的具体实现逻辑以及实现步骤。
1. Mysql 中 FIELD() 函数
1.1. FIELD() 函数定义
函数名 |
作用 |
解释 |
FIELD(str, str1, str2, str3, ...) |
对SQL中的查询结果集进行指定顺序排序 str 与 str1,str2,str3,...比较,其中 str 指的是字段名字 |
字段 str 按照字符串 str1,str2,str3,str4 的顺序返回查询到的结果集。如果表中 str 字段值不存在于 str1,str2,str3,str4 中的记录,则放在结果集最前面返回。 |
说明:
- 新建一张数据库表,表数据如下:
root@localhost|iris>select * from ta;
+----+--------+------+-------+
| id | name | age | class |
+----+--------+------+-------+
| 1 | iris | 11 | a1 |
| 2 | iris | 22 | a2 |
| 3 | seiki | 33 | a3 |
| 4 | seiki | 44 | a4 |
| 5 | xuding | 55 | a5 |
| 6 | xut | 66 | a6 |
| 7 | iris | 12 | a2 |
| 8 | iris | 24 | a4 |
| 9 | seiki | 36 | a6 |
| 10 | seiki | 48 | a8 |
| 11 | xuding | 50 | a0 |
| 12 | xut | 77 | a7 |
+----+--------+------+-------+
12 rows in set (0.00 sec)
- 希望能够按照
seiki、iris、xut
的顺序降序输出,SQL 语句及结果如下:
root@localhost|iris> SELECT * FROM ta ORDER BY FIELD(name, 'seiki', 'iris', 'xut');
+----+--------+------+-------+
| id | name | age | class |
+----+--------+------+-------+ -- 不在str1,str2,str3中的内容,放在最前面返回;str值相同按照主键的顺序
| 5 | xuding | 55 | a5 |
| 11 | xuding | 50 | a0 |
| 3 | seiki | 33 | a3 |
| 4 | seiki | 44 | a4 |
| 9 | seiki | 36 | a6 |
| 10 | seiki | 48 | a8 |
| 1 | iris | 11 | a1 |
| 2 | iris | 22 | a2 |
| 7 | iris | 12 | a2 |
| 8 | iris | 24 | a4 |
| 6 | xut | 66 | a6 |
| 12 | xut | 77 | a7 |
+----+--------+------+-------+
12 rows in set (0.00 sec)
- 希望按照 'seiki','iris' 的顺序升序输出,SQL 语句及结果如下:
root@localhost|iris> SELECT * FROM ta ORDER BY field(name,'seiki','iris') DESC;
+----+--------+------+-------+
| id | name | age | class |
+----+--------+------+-------+
| 1 | iris | 11 | a1 |
| 2 | iris | 22 | a2 |
| 7 | iris | 12 | a2 |
| 8 | iris | 24 | a4 |
| 3 | seiki | 33 | a3 |
| 4 | seiki | 44 | a4 |
| 9 | seiki | 36 | a6 |
| 10 | seiki | 48 | a8 |#倒序时,按照str3,str2,str1的顺序逆向排序,
| 5 | xuding | 55 | a5 |#不在str1,str2,str3中的记录放到最后;str值相同按主键顺序排列
| 6 | xut | 66 | a6 |
| 11 | xuding | 50 | a0 |
| 12 | xut | 77 | a7 |
+----+--------+------+-------+
1.2. 实际项目中使用 FIELD() 函数
在实际项目中,我们会通过 Mybatis-plus 或 Mybatis 来操作数据库,那么我们在 mapper 层的条件大部分来说是由上层传递下来的,以下是我在 mapper 层使用 FIELD() 函数的查询语句:
<select id="exportUserDataByUseridList" resultMap="ResultMapWithUsers" parameterType="java.lang.String">
SELECT t.* FROM t_user t WHERE t.id IN
<foreach collection="list" index="index" item="userid" open="(" separator="," close=")">
${userid}
</foreach>
order by field(t.id,
<foreach collection="list" index="index" item="userid" separator=",">
${userid}
</foreach>
);
</select>
小结:
在MySQL中,”IN”查询是非常常用的查询方式,它可以帮助我们快速地查询多个值,但是它的查询结果可能会有顺序问题。为了保证查询结果的顺序,我们可以使用”ORDER BY FIELD”排序方式来对查询结果进行排序,在保证查询结果正确的同时还能保证查询结果的顺序。
下篇文章,我将给大家讲解如何使用派生表来实现 IN 查询顺序的固定。