MySQL IN子句:数据顺序与条件顺序不一致情况探究(一)

发布于:2024-10-15 ⋅ 阅读:(106) ⋅ 点赞:(0)

前言

刚到公司接手了一个项目,那个项目中有一个奇葩的 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 中的记录,则放在结果集最前面返回。

说明:

  1. 新建一张数据库表,表数据如下:
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)
  1. 希望能够按照 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)
  1. 希望按照 '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 查询顺序的固定。