Sql-Server

停止 SQL Server 排序結果?

  • September 25, 2017

我正在教一門課,我想向我的學生證明,如果沒有明確的ORDER BY. 我觀察到即使ORDER BY查詢中不存在 SQL Server 似乎也會返回有序結果。這是一個問題,因為我擔心我的學生會在這一點上感到困惑。

觀察到的行為範例:

  • 預設情況下,數據似乎按主鍵順序排列,即使主鍵是非聚集的。
  • 如果我使用GROUP BY,結果集似乎按最後一列的順序排列。

例如GROUP BY this, that,以數據的順序結束,這很奇怪,特別是如果上面沒有索引。

我沒有在 MySQL、PostgreSQL 和 SQLite 等其他數據庫中觀察到這種行為。SQL Server 有什麼根本不同的地方,即使沒有ORDER BY?

現在在我看來,在不被詢問時訂購數據是一個額外的不必要的步驟,它浪費了處理時間。真的嗎?

是否有某種全域設置可以禁用此行為?有沒有辦法讓 SQL Server 停止排序數據?

這也使得向學生證明數據不是自動排序的變得更加困難。

誰能給我一個簡單的展示腳本,顯示從執行到執行的結果順序?

讓我們假設你有電話簿的白頁——記住,爺爺放在冰箱裡的那個東西,這樣他就可以給戰爭中的朋友打電話。它是按姓氏、名字組織的。

如果我讓你拿到電話簿並讀出名字:

SELECT FirstName, LastName FROM dbo.PhoneBook

通常會按姓氏順序把它們讀給我聽。您不必對它們進行分類——它們會以這種方式出現,因為它們已經是這樣儲存的。

這就是為什麼預設情況下 SQL Server 的結果似乎已排序的原因。

問題是,它不能保證,並且有幾種情況可以改變它。例如,假設您走到電話簿前為我閱讀姓氏,而其他人已經在閱讀白頁。您可以選擇跟隨他們,從電話簿的同一區域閱讀。你會從他們已經在的地方開始(比如 M),然後當那個人到達電話簿的末尾(Z)時,你會回到 A 並閱讀你錯過的部分。這就是所謂的旋轉木馬掃描,SQL Server 企業版預設會執行此操作,您無法將其關閉。你也無法預測什麼時候會得到它。

另一種情況 - 假設我只問你我們城市的名字,而不是姓氏:

SELECT FirstName FROM dbo.PhoneBook

請注意,我沒有要求對名稱進行排序。起初,當我們只有白頁時,您可能會使用那些白頁並向我喊出答案——但它們似乎沒有排序。(我們的電話簿白頁是按姓氏、名字排列的,所以如果你只是從那裡閱讀,名字就會到處都是。)

如果稍後有人僅在名字上創建索引,SQL Server 足夠聰明,可以將其用於我的僅限名字的查詢,因為它是可滿足我的查詢的最窄/最小的對象。突然,結果可能看起來是按名字排序的——但這只是巧合,因為有一個新的索引可以滿足我的查詢。

例如GROUP BY this,that,以 的順序結束數據that,這很奇怪,尤其是在沒有索引的情況下。

為了進行分組,您可能必須對數據進行排序。以我們的電話簿為例,如果我要求您按名字將每個人分組在一起,您必須按名字對他們進行排序才能完成該任務。請注意電話簿和索引之間的相似之處,而不是(除非表具有聚集索引,在這種情況下,表聚集索引,因此表/聚集索引具有固定的邏輯順序)。


**現在提供一些溫和的職業建議:**因為這些概念對你來說是新的,我會謙虛地建議,不要強迫 SQL Server 做你的競標,認為你會以某種方式調整它以使其更快,而是採取一些步驟背部。您在這裡提出了一個很好奇的問題,所以我建議觀看我的免費 SQL Server 培訓影片系列,如何像 SQL Server 引擎一樣思考。我使用 Stack Overflow 數據庫中的頁面來解釋 SQL Server 如何傳遞您的查詢結果。

我的建議可能看起來有點爭議——畢竟,我在這裡推銷我自己的培訓——但它是免費的,它甚至是開源的,在 MIT 許可下獲得許可,供人們重複使用。看我做完掌握了之後,就可以拿幻燈片來訓練你的隊友了。

社區維基回答

這也使得向學生證明數據不是自動排序的變得更加困難。

這是我喜歡的展示:

CREATE TABLE dbo.Example
(
   [data]  integer NOT NULL,
   padding character(8000) NOT NULL DEFAULT ''
);
GO
-- Add 50 rows with [data] numbered from 1 to 50
INSERT dbo.Example 
   ([data]) 
SELECT SV.number
FROM master.dbo.spt_values AS SV
WHERE SV.number BETWEEN 1 AND 50
AND SV.[type] = N'P';
GO
-- Add a nonclustered primary key on [data]
ALTER TABLE dbo.Example
ADD CONSTRAINT [PK dbo.Example data]
PRIMARY KEY NONCLUSTERED ([data]);

以下查詢以冷記憶體開始,並強制使用 PK:

-- Flush dirty pages to disk
CHECKPOINT;
GO
-- Drop clean buffers from memory
DBCC DROPCLEANBUFFERS;
GO
-- Query forced to use the PK ordered by [data]
SELECT TOP (15)
   E.[data],
   E.padding
FROM dbo.Example AS E 
   WITH (INDEX([PK dbo.Example data]))
WHERE
   E.[data] > 0;

執行計劃顯示了在查找中使用的索引:

計劃

我們可能期望行排序,[data]但輸出通常隨每次執行而變化:

輸出

原因是嵌套循環連接使用無序預取。


例如GROUP BY this,that,以 的順序結束數據that,這很奇怪,尤其是在沒有索引的情況下。

當您(或 DBMS)想要這樣做GROUP BY a,b時,您可以通過以下方式進行:

  • 排序(按a,b順序);或
  • 按順序排序b,a;或
  • 散列組合a,b

MySQL 只能以第一種方式進行,所以它總是會按a,b- 的順序產生結果,除非你添加不同的ORDER BY.

SQL Server 的優化器提供所有 3 個選項 - 並且可能更多 - 因此您可以看到不同的結果,具體取決於索引、表的大小、連接等。結果的(巧合)順序可能只是所需排序的剩餘部分進行分組。

如果您想向學生展示,請嘗試使用不同索引的相同查詢。或者同時嘗試GROUP BY a,bGROUP BY b,a。你會得到相同或不同排序的結果嗎?在執行查詢之前詢問您的學生!

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