浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因

简介: 原文:浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因   本文出处:http://www.cnblogs.com/wy123/p/6189100.html        标题有点拗口,来源于一个开发人员遇到的实际问题  先抛出问题:一个查询没有明确指定排序方式,那么,第二次执行这个同样的查询的时候,查询结果会不会与第一次的查询结果排序方式完全一样?  答案是不确定的,两个完全一样的查询,结果也完全一样,两次(多次)查询结果的排序方式有可能一致,有可能不一致。
原文: 浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因

  

本文出处:http://www.cnblogs.com/wy123/p/6189100.html 

 

 

  标题有点拗口,来源于一个开发人员遇到的实际问题
  先抛出问题:一个查询没有明确指定排序方式,那么,第二次执行这个同样的查询的时候,查询结果会不会与第一次的查询结果排序方式完全一样?
  答案是不确定的,两个完全一样的查询,结果也完全一样,两次(多次)查询结果的排序方式有可能一致,有可能不一致。
  如果不一致,又是什么原因导致同样的查询默认排序方式不一致?
  以下简单分析几种情况,说明为什么查询同样的查询会出现默认排序结果不一样的情况。当然对于该问题,包含但不限于以下几种情况。

 

场景1:并行查询导致默认结果集的排序是随机的

按照惯例,先造一个表供测试

create table TestDefaultOrder1
(
    id int identity(1,1) primary key,
    col2 varchar(50),
    col3 varchar(50),
    col4 varchar(50),
    col5 varchar(50),
    col6 varchar(50),
    col7 varchar(50),
    col8 varchar(50),
    CreateDate Datetime
)
go

declare @i int =0 
begin tran
    while @i<500000
    begin
        insert into TestDefaultOrder1 values (NEWID(),NEWID(),NEWID(),NEWID(),NEWID(),NEWID(),NEWID(),GETDATE()-RAND()*500)
        set @i=@i+1
    end
commit

 

测试场景:

  这里先不考虑索引之类的性能问题,
  如图是一个测试结果的示例,可以看到,两个查询的条件是完全一样的,都没有显式指定排序列,默认结果的排序是完全不一样的

    

  甚至可以用同样的条件做三次查询(可以更多次),结果依然都是完全不一致的

  

 

 

原因分析:

  为什么一样的查询,每次查询结果的排序都不一样,正如上面所说,这种情况下是并行查询导致的。
  查询引擎采用什么样的执行计划是基于代价考虑的,如果一旦发现一个查询的执行代价超过一定的阈值,就有可能采用并行的方式来处理,
  如果采用了并行查询的方式,就会采用多个线程来分解整个查询任务,而每一个线程分配的任务量是无法固定的,同时,合并每个线程的结果顺序也是不固定的
  这就导致了最终的查询结果的顺序是不固定的。
  截图即为并行查询的每个线程分配的任务量示例。

  如图,当前这个查询,第一个线程返回的行数是2,但是无法保证第二次查询的第一个线程返回的行数也是2,
  即便是第二次返回的行数是2,也无法保证返回的2行与第一次返回的两行数据一样的
  同时,在合并各个线程的结果集的时候,依据线程返回的时间来的,理论上讲也是不确定的,多个不确定因素在一起,就造成了最终的结果集排序(可以认为)是随机的。

  

  

  有人说,只要执行计划一样,查询默认排序就一样,其实也是不对的,因为即便是执行计划一样,只要SQLServer开启了并行查询,默认排序都是无法保证一直的   

 

 

场景2:物理存储导致默认结果的随机性

  同样,先造一个测试数据的case,如下,创建一个堆表,

create table TestDefaultOrder2
(
    id int identity(1,1),
    col2 char(5000)
)
go

declare @i int =0 
begin tran
    while @i<50
    begin
        insert into TestDefaultOrder2 values (NEWID())
        set @i=@i+1
    end
commit

 

测试场景:

  这个场景排除了上述并行查询的影响,因为只有50条数据,根本不会启用并行查询
  如截图,两次查询之间执行了一次表的重建动作,同样是数据本身没有发生任何变化,两次查询的默认顺序完全不一样

 

甚至在重建一次,查询结果仍然与上面两次还是都不一样的。

 

 原因分析:

  堆表的特点决定了堆内的数据行和数据页没有任何固定的顺序,整个堆内的数据在物理存储发生了变化之后,
  在对查询(对堆表扫描)的过程中得不到一个与物理存储变化之前完全一样的顺序。
  除了上述的重建表会导致查询的默认顺序不一致,其他影响物理空间的操作,都会影响堆表数据页面的物理存储位置,

  比如这里再执行一次数据库的收缩,收缩之后的查询与收缩之前的查询顺序依旧是不一样的,我可没有动你表和你表中的任何一条数据,但你不能阻止我正常的数据库维护操作。
  总之,一旦影响到物理存储位置,堆表的默认扫描结果顺序都有可能不一样。

  

 

  以上仅仅通过单表查询来说明,如果未显式指定排序方式,即便是同样的查询条件,查询结果的顺序是无法保证每次都一致的,
  如果是多表关联,或者是考虑到索引,数据库维护等操作,情况将变得更加复杂,比如这个也比较有意思:http://www.cnblogs.com/wy123/p/5425946.html
  比较特殊的是:没有显式指定排序方式,
    1,某段一个时间段内,查询结果可能是按照预期结果排序的,某个时间段内就不是了(物理存储改变的影响);
    2,某些查询条件下是按照预期结果排序的,改变一下查询条件,排序结果就变得面目全非了(执行计划改变的影响)。
  总之一句话:没有显式执行排序方式,不要期待查询结果每次都是预期的排序方式,甚至每次都不一样。

 

 总结:

   本文通过两个简单的示例,
  从执行计划和物理存储两个方面,说明了“如果查询SQL没有显式指定排序方式,查询结果的顺序是无法保证总是按照你的预期来的”。
  当然也不能局限于这两种情况,极有可能还有很多原因是我没有想到的。
  然而话不能说死,某些条件下没有显式指定排序方式,一定条件下(多次查询)可能会得到预期的排序结果,但是这种期待往往是不可靠的。
  

  “昨天系统查询结果的排序还是好好的,今天怎么变了?”
  “为啥我用A条件查询是按照时间排序的,按照B条件查询就不是了?”
  如果没有显式指定排序方式,不要问我数据库是不是有问题(或者说SQL Server这个数据库“不行”,或者说DBA说是内部原因是忽悠人的)。


  所以同学,如果期望查询结果排序,不管默认是不是你预期的排序方式,都请显式指定排序方式。

 

 

 

 

目录
相关文章
|
2月前
|
SQL 安全 数据库
如何在Django中正确使用参数化查询或ORM来避免SQL注入漏洞?
如何在Django中正确使用参数化查询或ORM来避免SQL注入漏洞?
155 77
|
11天前
|
SQL 关系型数据库 分布式数据库
利用 PolarDB PG 版向量化引擎,加速复杂 SQL 查询!完成任务领发财新年抱枕!
利用 PolarDB PG 版向量化引擎,加速复杂 SQL 查询!完成任务领发财新年抱枕!
|
2天前
|
SQL 关系型数据库 OLAP
云原生数据仓库AnalyticDB PostgreSQL同一个SQL可以实现向量索引、全文索引GIN、普通索引BTREE混合查询,简化业务实现逻辑、提升查询性能
本文档介绍了如何在AnalyticDB for PostgreSQL中创建表、向量索引及混合检索的实现步骤。主要内容包括:创建`articles`表并设置向量存储格式,创建ANN向量索引,为表增加`username`和`time`列,建立BTREE索引和GIN全文检索索引,并展示了查询结果。参考文档提供了详细的SQL语句和配置说明。
18 1
|
2月前
|
SQL NoSQL Java
Java使用sql查询mongodb
通过MongoDB Atlas Data Lake或Apache Drill,可以在Java中使用SQL语法查询MongoDB数据。这两种方法都需要适当的配置和依赖库的支持。希望本文提供的示例和说明能够帮助开发者实现这一目标。
54 17
|
2月前
|
SQL Oracle 关系型数据库
如何在 Oracle 中配置和使用 SQL Profiles 来优化查询性能?
在 Oracle 数据库中,SQL Profiles 是优化查询性能的工具,通过提供额外统计信息帮助生成更有效的执行计划。配置和使用步骤包括:1. 启用自动 SQL 调优;2. 手动创建 SQL Profile,涉及收集、执行调优任务、查看报告及应用建议;3. 验证效果;4. 使用 `DBA_SQL_PROFILES` 视图管理 Profile。
|
10天前
|
SQL 数据可视化 IDE
SQL做数据分析的困境,查询语言无法回答的真相
SQL 在简单数据分析任务中表现良好,但面对复杂需求时显得力不从心。例如,统计新用户第二天的留存率或连续活跃用户的计算,SQL 需要嵌套子查询和复杂关联,代码冗长难懂。Python 虽更灵活,但仍需变通思路,复杂度较高。相比之下,SPL(Structured Process Language)语法简洁、支持有序计算和分组子集保留,具备强大的交互性和调试功能,适合处理复杂的深度数据分析任务。SPL 已开源免费,是数据分析师的更好选择。
|
2月前
|
SQL Java 数据库连接
【潜意识Java】MyBatis中的动态SQL灵活、高效的数据库查询以及深度总结
本文详细介绍了MyBatis中的动态SQL功能,涵盖其背景、应用场景及实现方式。
115 6
|
6月前
|
关系型数据库 MySQL 网络安全
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
|
8月前
|
SQL 存储 监控
SQL Server的并行实施如何优化?
【7月更文挑战第23天】SQL Server的并行实施如何优化?
192 13
|
8月前
|
SQL
解锁 SQL Server 2022的时间序列数据功能
【7月更文挑战第14天】要解锁SQL Server 2022的时间序列数据功能,可使用`generate_series`函数生成整数序列,例如:`SELECT value FROM generate_series(1, 10)。此外,`date_bucket`函数能按指定间隔(如周)对日期时间值分组,这些工具结合窗口函数和其他时间日期函数,能高效处理和分析时间序列数据。更多信息请参考官方文档和技术资料。
115 9

热门文章

最新文章