7

几年来,我不时在几个数据库引擎中使用 SQL,但理论知识很少,所以我的问题对你们中的一些人来说可能非常“无趣”。但这对我来说变得很重要,所以我不得不问。

想象一下具有非唯一列的表 URL status。对于这个问题,假设我们有大量的行并且状态在每条记录中都具有相同的值。

想象一下我们执行了很多次查询:

SELECT * FROM Urls ORDER BY status
  1. 我们是否每次都得到相同的行顺序?如果我们添加一些新行会发生什么?它会更改顺序还是将新记录附加到结果的末尾?如果我们没有得到相同的订单 - 这个订单取决于什么条件?

  2. ROW_NUMBER() OVER (ORDER BY status)返回与上述查询相同的顺序还是基于不同的排序机制?

4

4 回答 4

10

这很简单。如果您想要一个可以依赖的排序,那么您需要在ORDER BY子句中包含足够的列,以便所有这些列的组合对于每一行都是唯一的。没有其他任何保证。

对于单个表,您通常可以通过列出“有趣”的列进行排序然后包括主键列来获得所需的内容。由于 PK 本身保证了唯一性,因此整个组合也保证唯一地定义排序,例如,如果Urls表具有主键,{Site, Page, Ordinal}则以下内容将为您提供可靠的结果:

SELECT * FROM Urls ORDER BY status, Site, Page, Ordinal
于 2013-09-04T12:07:19.563 回答
8

ORDER BY在 SQL Server 中不稳定(据我所知,在任何其他数据库中也不稳定)。稳定排序是一种以与在表中找到记录相同的顺序返回记录的排序。

高层原因很简单。表是集合。他们没有秩序。所以“稳定”的排序是没有意义的。

较低层次的原因可能更重要。数据库可能正在实现并行排序算法。默认情况下,此类算法不是稳定的。

如果你想要一个稳定的排序,那么在排序中包含一个键列。

文档中提到了这一点:

要在使用 OFFSET 和 FETCH 的查询请求之间获得稳定的结果,必须满足以下条件:

查询使用的基础数据不得更改。也就是说,查询涉及的行没有更新,或者查询中的所有页面请求都在使用快照或可序列化事务隔离的单个事务中执行。有关这些事务隔离级别的详细信息,请参阅 SET TRANSACTION ISOLATION LEVEL (Transact-SQL)。

ORDER BY 子句包含保证唯一的列或列组合。

于 2013-09-04T12:07:21.983 回答
0

我真的很喜欢这些类型的问题,因为您可以进行性能分析。

首先,让我们创建一个示例 [test] 数据库,其中包含一个包含一百万条随机记录的 [urls] 表。

请参阅下面的代码。

-- Switch databases
USE [master];
go

-- Create simple database
CREATE DATABASE [test];
go

-- Switch databases
USE [test];
go

-- Create simple table
CREATE TABLE [urls]
    (
      my_id INT IDENTITY(1, 1)
                PRIMARY KEY ,
      my_link VARCHAR(255) ,
      my_status VARCHAR(15)
    );
go

-- http://stackoverflow.com/questions/1393951/what-is-the-best-way-to-create-and-populate-a-numbers-table

-- Load table with 1M rows of data 
;
WITH    PASS0
          AS ( SELECT   1 AS C
               UNION ALL
               SELECT   1
             ),           --2 rows
        PASS1
          AS ( SELECT   1 AS C
               FROM     PASS0 AS A ,
                        PASS0 AS B
             ),  --4 rows
        PASS2
          AS ( SELECT   1 AS C
               FROM     PASS1 AS A ,
                        PASS1 AS B
             ),  --16 rows
        PASS3
          AS ( SELECT   1 AS C
               FROM     PASS2 AS A ,
                        PASS2 AS B
             ),  --256 rows
        PASS4
          AS ( SELECT   1 AS C
               FROM     PASS3 AS A ,
                        PASS3 AS B
             ),  --65536 rows
        PASS5
          AS ( SELECT   1 AS C
               FROM     PASS4 AS A ,
                        PASS4 AS B
             ),  --4,294,967,296 rows
        TALLY
          AS ( SELECT   ROW_NUMBER() OVER ( ORDER BY C ) AS Number
               FROM     PASS5
             )
    INSERT  INTO urls
            ( my_link ,
              my_status
            )
            SELECT 
      -- top 10 search engines + me
                    CASE ( Number % 11 )
                      WHEN 0 THEN 'www.ask.com'
                      WHEN 1 THEN 'www.bing.com'
                      WHEN 2 THEN 'www.duckduckgo.com'
                      WHEN 3 THEN 'www.dogpile.com'
                      WHEN 4 THEN 'www.webopedia.com'
                      WHEN 5 THEN 'www.clusty.com'
                      WHEN 6 THEN 'www.archive.org'
                      WHEN 7 THEN 'www.mahalo.com'
                      WHEN 8 THEN 'www.google.com'
                      WHEN 9 THEN 'www.yahoo.com'
                      ELSE 'www.craftydba.com'
                    END AS my_link ,

      -- ratings scale
                    CASE ( Number % 5 )
                      WHEN 0 THEN 'poor'
                      WHEN 1 THEN 'fair'
                      WHEN 2 THEN 'good'
                      WHEN 3 THEN 'very good'
                      ELSE 'excellent'
                    END AS my_status
            FROM    TALLY AS T
            WHERE   Number <= 1000000
go

其次,在我们的测试环境中进行性能分析时,我们总是希望清除缓冲区和缓存。另外,我们要打开统计 I/O 和时间来比较结果。

请参阅下面的代码。

-- Show time & i/o
SET STATISTICS TIME ON
SET STATISTICS IO ON
GO

-- Remove clean buffers & clear plan cache
CHECKPOINT 
DBCC DROPCLEANBUFFERS 
DBCC FREEPROCCACHE
GO

第三,我们要尝试第一个 TSQL 语句。查看执行计划并捕获统计信息。

-- Try 1
SELECT * FROM urls ORDER BY my_status

/*
Table 'urls'. Scan count 5, logical reads 4987, physical reads 1, read-ahead reads 4918, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 3166 ms,  elapsed time = 8130 ms.
*/

在此处输入图像描述

第四,我们想试试第二条 TSQL 语句。不要忘记清除查询计划缓存和缓冲区。如果不这样做,则查询将花费不到 1 秒的时间,因为大部分信息都在内存中。查看执行计划并捕获统计信息。

-- Try 2
SELECT ROW_NUMBER() OVER (ORDER BY my_status) as my_rownum, * FROM urls

/*
Table 'urls'. Scan count 5, logical reads 4987, physical reads 1, read-ahead reads 4918, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 3276 ms,  elapsed time = 8414 ms.
*/

在此处输入图像描述

最后但同样重要的是,这是有趣的部分,性能分析。

1 - 我们可以看到第二个计划是第一个计划的超集。所以这两个计划都会扫描聚集索引并对数据进行排序。并行性用于将结果放在一起。

2 - 第二个计划/查询需要计算行号。它分割数据并计算这个标量。因此,我们最终在计划中增加了两个运算符。

第一个计划在 8130 毫秒内运行,第二个计划在 8414 毫秒内运行,这并不奇怪。

始终查看查询计划。估计的和实际的。他们告诉您希望引擎计划做什么以及它实际做什么。

在这个例子中,两个不同的 TSQL 语句提出了几乎相同的计划。

真挚地

约翰

www.craftydba.com

于 2013-09-04T13:01:13.713 回答
0

对任何 sql 问题“此输出的顺序是什么”的一般答案是“无论服务器感觉如何,而且从查询到查询可能不一样”,除非您特别请求了订单。

甚至像“从 myTable 中选择前 1000 个 myColumn”这样简单的操作也可以以任何顺序返回任何行;例如,服务器可以使用并行线程,第一个线程开始返回结果,开始在表中间读取,或者使用了包含 myColumn 的索引,所以你得到了具有字母顺序第一个 productName 的行(这次;上次index 具有不同的统计信息,因此它选择了不同的索引并为您提供了 1000 个最旧的交易)...

理论上,服务器甚至可以说“我的内存缓存中有这 10 个页面与您的查询匹配,我会在等待磁盘返回其余页面时将这些页面传递给您......

于 2016-12-20T07:20:41.907 回答