Sql-Server

強制流不同

  • August 2, 2017

我有一張這樣的桌子:

CREATE TABLE Updates
(
   UpdateId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
   ObjectId INT NOT NULL
)

本質上是跟踪 ID 不斷增加的對象的更新。

此表的使用者將選擇 100 個不同的對象 ID 的塊,按UpdateId特定的UpdateId. 本質上,跟踪它停止的位置,然後查詢任何更新。

我發現這是一個有趣的優化問題,因為我只能通過編寫查詢來生成一個最大優化的查詢計劃,這些查詢由於索引而恰好執行我想要的操作,但不能保證我想要的:

SELECT DISTINCT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId

@fromUpdateId儲存過程參數在哪裡。

有以下計劃:

SELECT <- TOP <- Hash match (flow distinct, 100 rows touched) <- Index seek

由於UpdateId正在使用的索引上的搜尋,結果已經很好,並且像我想要的那樣從最低到最高更新 ID 排序。這會生成一個流程不同的計劃,這就是我想要的。但是排序顯然不能保證行為,所以我不想使用它。

這個技巧也會產生相同的查詢計劃(儘管有一個冗餘的 TOP):

WITH ids AS
(
   SELECT ObjectId
   FROM Updates
   WHERE UpdateId > @fromUpdateId
   ORDER BY UpdateId OFFSET 0 ROWS
)
SELECT DISTINCT TOP 100 ObjectId FROM ids

不過,我不確定(也不懷疑)這是否真的能保證訂購。

我希望 SQL Server 足夠智能以簡化的一個查詢是這樣的,但它最終會生成一個非常糟糕的查詢計劃:

SELECT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId
GROUP BY ObjectId
ORDER BY MIN(UpdateId)

有以下計劃:

SELECT <- Top N Sort <- Hash Match aggregate (50,000+ rows touched) <- Index Seek

我正在嘗試找到一種方法來生成具有索引搜尋的最佳計劃UpdateId和不同的流程以刪除重複ObjectId的 s。有任何想法嗎?

如果需要,請提供樣本數據。對像很少會有一個以上的更新,並且在一組 100 行中幾乎不應該有一個以上的更新,這就是為什麼我追求一個流 distinct,除非有更好的東西我不知道?但是,不能保證單個ObjectId表中的行數不會超過 100 行。該表有超過 1,000,000 行,預計會快速增長。

假設 this 的使用者有另一種方法可以找到合適的 next @fromUpdateId。無需在此查詢中返回它。

SQL Server 優化器無法在您需要的保證下生成您所追求的執行計劃,因為Hash Match Flow Distinct運算符不保持順序。

不過,我不確定(也不懷疑)這是否真的能保證訂購。

在許多情況下,您可能會觀察到訂單保留,但這是一個實現細節;沒有保證,所以你不能依賴它。與往常一樣,呈現順序只能由頂級ORDER BY子句保證。

例子

下面的腳本顯示 Hash Match Flow Distinct 不保留順序。它在兩列中設置了匹配數字 1-50,000 的表:

IF OBJECT_ID(N'dbo.Updates', N'U') IS NOT NULL
   DROP TABLE dbo.Updates;
GO
CREATE TABLE Updates
(
   UpdateId INT NOT NULL IDENTITY(1,1),
   ObjectId INT NOT NULL,

   CONSTRAINT PK_Updates_UpdateId PRIMARY KEY (UpdateId)
);
GO
INSERT dbo.Updates (ObjectId)
SELECT TOP (50000)
   ObjectId =
       ROW_NUMBER() OVER (
           ORDER BY C1.[object_id]) 
FROM sys.columns AS C1
CROSS JOIN sys.columns AS C2
ORDER BY
   ObjectId;

測試查詢是:

DECLARE @Rows bigint = 50000;

-- Optimized for 1 row, but will be 50,000 when executed
SELECT DISTINCT TOP (@Rows)
   U.ObjectId 
FROM dbo.Updates AS U
WHERE 
   U.UpdateId > 0
OPTION (OPTIMIZE FOR (@Rows = 1));

估計的計劃顯示索引查找和流不同:

預計計劃

輸出當然似乎從以下命令開始:

結果開始

…但進一步下降的值開始“失去”:

模式崩潰

…最終:

混亂爆發

在這種特殊情況下的解釋是散列運算符溢出:

執行後計劃

一旦分區溢出,散列到同一分區的所有行也會溢出。溢出的分區稍後處理,打破了遇到的不同值將按照接收到的順序立即發出的期望。


有很多方法可以編寫高效的查詢來生成您想要的有序結果,例如遞歸或使用游標。但是,它不能使用Hash Match Flow Distinct來完成。

引用自:https://dba.stackexchange.com/questions/182410