关于排序后查询结果集不稳定的问题

为提高效率,请提供以下信息,问题描述清晰能够更快得到解决:
【 TiDB 使用环境】
测试环境

【概述】 场景 + 问题概述
有以下表结构和测试数据:

DROP TABLE IF EXISTS `test1`;
CREATE TABLE `test1`  (
  `a` int(11) NULL DEFAULT NULL,
  `b` int(11) NULL DEFAULT NULL,
  `c` int(6) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin;
INSERT INTO `test1` VALUES (1, 1, 0);
INSERT INTO `test1` VALUES (2, 2, NULL);
INSERT INTO `test1` VALUES (3, 3, 0);
INSERT INTO `test1` VALUES (4, 4, 0);
INSERT INTO `test1` VALUES (5, 5, 0);
INSERT INTO `test1` VALUES (6, 6, 0);

DROP TABLE IF EXISTS `test2`;
CREATE TABLE `test2`  (
  `a` bigint(20) NULL DEFAULT NULL,
  `b` int(11) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin;
INSERT INTO `test2` VALUES (34, 3);
INSERT INTO `test2` VALUES (53, 5);
INSERT INTO `test2` VALUES (38, 39);
INSERT INTO `test2` VALUES (22, 2);
INSERT INTO `test2` VALUES (87, 51);
INSERT INTO `test2` VALUES (45, 67);
INSERT INTO `test2` VALUES (88, 4);
INSERT INTO `test2` VALUES (567, 786);
INSERT INTO `test2` VALUES (67, 678);
INSERT INTO `test2` VALUES (234563, 65);
INSERT INTO `test2` VALUES (546, 8);

查询SQL(结果顺序不稳定):

SELECT * FROM (
select test1.*
 ,(select max(test2.a) from test2 where test2.b=test1.a) as p
from test1 
) t
order by c

根据test1的c字段(有重复值和null值)做排序查询,并且包含一个test2的子查询,结果集顺序不固定。

但是去掉子查询的话结果集顺序是固定,进一步排查发现,去掉子查询中的max函数也能固定顺序。

以上脚本能复现这个问题。

【问题】
1、为什么子查询用了函数会导致结果集不稳定
2、对于单表查询的话,如果排序字段有值重复了,最终的输出结果是按什么规则确定顺序的

【业务影响】
查询结果不稳定

【TiDB 版本】
5.2.2


若提问为性能优化、故障排查类问题,请下载脚本运行。终端输出的打印结果,请务必全选并复制粘贴上传。

v5.4.0下测试,排序是稳定的

木有发现,每次查询结果没有变化

但不同版本的结果顺序是不一致的

你多执行一些次数,我在5.4版本执行了20多次后出现了顺序不一样的情况:

目前测试过522、531、541、600这几个版本,都不能保证固定顺序

嗯,是有变化,但结果也是正确的,跟mysql是有区别,还也可以通过增加排序字段解决

是不是升级到5.4的最新版本就可以了

5.4也不行,测试了6.0也不行

我测试了一下 6.1 也有些问题,我先确认下再回复。

1 个赞

c列除了null,都是0,order by对相同值的排序本身是存在不确定性的,mysql应该也是有这样的情况发生。order by后面在多加一个字段就行了。

当遇到相同的 order by 值时,排序结果不稳定。为减少随机性,应当尽可能保持 order by 值的唯一性。不能保证唯一的继续加,保证 order by 的字段组合是唯一时,结果才能唯一。

:+1:

  1. 为什么子查询用了函数会导致结果集不稳定

这个问题不对。不是子查询用了函数导致的结果集不稳定,而是这个查询的结果本身是不保证稳定的。
这条 SQL 只一能给的保证是,结果是按 order by C 有序的,每次的结果集内容是一致的。
其它都是未定义行为,依赖于实现细节。
子查询里面不用 max 函数的时候,走的是一个 Apply 算子,更低效的实现,它恰好给你了一个有序的结果。而用 max 的时候,Apply 算子被优化成 HashJoin 了,是更高效的并发实现,高效并发的副产品是返回结果也不稳定了

  1. 对于单表查询的话,如果排序字段有值重复了,最终的输出结果是按什么规则确定顺序的

未定义行为。如果期望在排序字段重复的情况下,得到一个有序的结果,那么应该用多列组合去排序。
这样写出来的 SQL 才是跨实现通用的。

1 个赞

领教。


该主题在最后一个回复创建后60天后自动关闭。不再允许新的回复。