主键中指定的排序顺序,但排序是在 SELECT 上执行的

m__*_*m__ 15 sql-server primary-key

我将传感器数据存储在表SensorValues 中。表和主键如下:

CREATE TABLE [dbo].[SensorValues](
  [DeviceId] [int] NOT NULL,
  [SensorId] [int] NOT NULL,
  [SensorValue] [int] NOT NULL,
  [Date] [int] NOT NULL,
CONSTRAINT [PK_SensorValues] PRIMARY KEY CLUSTERED 
(
  [DeviceId] ASC,
  [SensorId] ASC,
  [Date] DESC
) WITH (
    FILLFACTOR=75,
    DATA_COMPRESSION = PAGE,
    PAD_INDEX = OFF,
    STATISTICS_NORECOMPUTE = OFF,
    SORT_IN_TEMPDB = OFF,
    IGNORE_DUP_KEY = OFF,
    ONLINE = OFF,
    ALLOW_ROW_LOCKS = ON,
    ALLOW_PAGE_LOCKS = ON)
  ON [MyPartitioningScheme]([Date])
Run Code Online (Sandbox Code Playgroud)

然而,当我选择在特定时间有效的传感器值时,执行计划告诉我它正在执行排序。这是为什么?

我原以为,由于我存储了按日期列排序的值,因此不会发生排序。还是因为索引不仅仅按日期列排序,即它不能假设结果集已排序?

SELECT TOP 1 SensorValue
  FROM SensorValues
  WHERE SensorId = 53
    AND DeviceId = 3819
    AND Date < 1339225010
  ORDER BY Date DESC
Run Code Online (Sandbox Code Playgroud)

执行计划

编辑:我可以这样做吗?

由于表按DeviceId、SensorId、Date进行排序并且我执行SELECT仅指定一个DeviceId和一个SensorId,因此输出集应该已经按Date DESC排序。所以我想知道以下问题是否会在所有情况下产生相同的结果?

SELECT TOP 1 SensorValue
  FROM SensorValues
  WHERE SensorId = 53
    AND DeviceId = 3819
    AND Date < 1339225010
Run Code Online (Sandbox Code Playgroud)

根据下面的@Catcall,排序顺序与存储顺序不同。即我们不能假设返回的值已经在一个排序的顺序。

编辑:我试过这个 CROSS APPLY 解决方案,没有运气

@Martin Smith 建议我尝试将结果应用于分区。我找到了一篇博客文章(分区表上的对齐非聚集索引)描述了这个类似的问题,并尝试了与史密斯建议的有些相似的解决方案。但是,这里没有运气,执行时间与我原来的解决方案相当。

WITH Boundaries(boundary_id)
AS
(
  SELECT boundary_id
  FROM sys.partition_functions pf
  JOIN sys.partition_range_values prf ON pf.function_id = prf.function_id
  WHERE pf.name = 'PF'
  AND prf.value <= 1339225010
  UNION ALL
  SELECT max(boundary_id) + 1
  FROM sys.partition_functions pf
  JOIN sys.partition_range_values prf ON pf.function_id = prf.function_id
  WHERE pf.name = 'PF'
  AND prf.value <= 1339225010
),
Top1(SensorValue)
AS
(
  SELECT TOP 1 d.SensorValue
  FROM Boundaries b
  CROSS APPLY
  (
    SELECT TOP 1 SensorValue
      FROM SensorValues
      WHERE  SensorId = 53
        AND DeviceId = 3819
        AND "Date" < 1339225010
        AND $Partition.PF(Date) = b.boundary_id
        ORDER BY Date DESC
  ) d
  ORDER BY d.Date DESC
)
SELECT SensorValue
FROM Top1
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 13

对于非分区表,我得到以下计划

方案一

上有一个单一的搜索谓词Seek Keys[1]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010

这意味着 SQL Server 可以对前两列执行相等查找,然后开始范围查找,从 at1339225010和order 开始FORWARD(因为索引是用 定义的[Date] DESC

TOP发出第一行后,操作员将停止从搜索中请求更多行。

当我创建分区方案和功能时

CREATE PARTITION FUNCTION PF (int)
AS RANGE LEFT FOR VALUES (1000, 1339225009 ,1339225010 , 1339225011);
GO
CREATE PARTITION SCHEME [MyPartitioningScheme]
AS PARTITION PF
ALL TO ([PRIMARY] );
Run Code Online (Sandbox Code Playgroud)

并用以下数据填充表格

INSERT INTO [dbo].[SensorValues]    
/*500 rows matching date and SensorId, DeviceId predicate*/
SELECT TOP (500) 3819,53,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0))           
FROM master..spt_values
UNION ALL
/*700 rows matching date but not SensorId, DeviceId predicate*/
SELECT TOP (700) 3819,52,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0))           
FROM master..spt_values
UNION ALL 
/*1100 rows matching SensorId, DeviceId predicate but not date */
SELECT TOP (1100) 3819,53,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) + 1339225011      
FROM master..spt_values
Run Code Online (Sandbox Code Playgroud)

SQL Server 2008 上的计划如下所示。

方案二

从搜索发出的实际行数是500。该计划显示寻求谓词

Seek Keys[1]: Start: PtnId1000 <= 2, End: PtnId1000 >= 1, 
Seek Keys[2]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010
Run Code Online (Sandbox Code Playgroud)

表明它正在使用此处描述跳过扫描方法

查询优化器得到扩展,以便可以在 PartitionID(作为逻辑前导列)和可能的其他索引键列上使用一个条件执行查找或扫描操作,然后可以使用不同条件执行第二级查找在一个或多个附加列上,对于满足第一级查找操作的资格的每个不同值。

该计划是一个串行计划,因此对于您的特定查询,似乎如果 SQL Server 确保它date按照原始计划的降序处理分区,TOP并且仍然可以工作,并且它可以在第一个匹配行之后停止处理找到而不是继续并输出剩余的 499 个匹配项。

事实上,2005 年的计划看起来确实采用了这种方法

2005年计划

我不知道这是否是直截了当地得到同样的计划在2008年或也许会需要一个OUTER APPLYsys.partition_range_values进行模拟。


Mik*_*ll' 9

很多人认为聚集索引可以保证输出的排序顺序。但这不是它的作用。它保证了磁盘上的存储顺序。

例如,请参阅此博客文章后续文章此较长的讨论


gbn*_*gbn 5

我推测由于并行计划需要 SORT。我基于一些模糊和遥远的博客文章:但我在 MSDN上发现了这个,这可能会也可能不会证明这一点

所以,尝试使用 MAXDOP 1 看看会发生什么......

我认为在@sql kiwi 的“Exchange Operator”下的Simple Talk 博客文章中也暗示了这一点。和“DOP依赖”在这里