QL Server 2008 改進了許多并行計劃的已分區表的查詢處理性能,更改了并行和串行計劃的表示方式,增強了編譯時和運行時執行計劃中所提供的分區信息。本主題將說明這些改進并提供有關如何解釋已分區表和索引的查詢執行計劃的指南,此外還將提供改進已分區對象的查詢性能的最佳方法。
? 注意 |
---|
只有 SQL Server Enterprise Edition、Developer Edition 和 Evaluation Edition 支持已分區表和已分區索引。 |
在 SQL Server 2008 中,已分區表的內部表示形式已發生變化,即已分區表將作為一個多列索引呈現給查詢處理器,其中? PartitionID ?是第一列。 PartitionID ?是一個隱藏的計算列,用于在內部表示包含特定行的分區的 ID。例如,假設一個定義為? T(a, b, c) ?的表? T ?在? a ?列進行了分區,并在? b ?列的聚集索引。在 SQL Server 2008 中,此分區表在內部被視為一個具有架構? T(PartitionID, a, b, c) ?的未分區表,并具有組合鍵 ( PartitionID, b ) 的聚集索引。這樣查詢優化器便可以基于? PartitionID ?對任何已分區表或索引執行查找操作。
現在,分區的排除任務已在此查找操作中完成。
此外,查詢優化器的功能也得以擴展,可以針對? PartitionID (作為邏輯首列)以及其他可能的索引鍵列執行某一條件下的查找或掃描操作,然后,對于符合第一級查找操作的條件的每個不同值,再針對一個或多個其他列執行不同條件下的二級查找。也就是說,這種稱為“跳躍掃描”的操作允許查詢優化器基于某一條件來執行查找或掃描操作以確定要訪問的分區,然后在該運算符內執行一個二級索引查找操作以返回這些分區中符合另一個不同條件的行。例如,請考慮以下查詢。
SELECT * FROM T WHERE a < 10 and b = 2;
對于本示例,假設定義為? T(a, b, c) ?的表? T ?對? a ?列進行了分區,并具有? b ?的聚集索引。表? T ?的分區邊界由以下分區函數定義:
CREATE PARTITION FUNCTION myRangePF1 (int) AS RANGE LEFT FOR VALUES (3, 7, 10);
為求解該查詢,查詢處理器將執行第一級查找操作以查找包含符合條件? T.a < 10 ?的行的每個分區。這將標識要訪問的分區。然后,在所標識的每個分區內,處理器將針對? b ?列的聚集索引執行一個二級查找以查找符合條件? T.b = 2 ?和? T.a < 10 ?的行。
下圖所示為跳躍掃描操作的邏輯表示形式,其中顯示了在? a ?列和? b ?列中包含數據的表? T 。分區編號為 1 到 4,分區邊界由垂直虛線表示。對分區執行的第一級查找操作(圖中未顯示)已確定分區 1、2 和 3 符合查找條件(由為該表定義的分區和? a ?列的謂詞指示),即? T.a < 10 。曲線指示了跳躍掃描操作的二級查找部分所遍歷的路徑。實際上,跳躍掃描操作將在這些分區的每個分區中查找符合條件? b = 2 ?的行。跳躍掃描操作的總開銷等于三個單獨索引查找之和。

若要檢查已分區表和索引的查詢執行計劃,可以使用 Transact-SQL SET 語句 SET SHOWPLAN_XML 或 SET STATISTICS XML,或者使用 SQL Server Management Studio 中的圖形執行計劃輸出。例如,單擊查詢編輯器工具欄上的 “顯示估計的執行計劃” 可以顯示編譯時執行計劃,單擊 “包括實際的執行計劃” 可以顯示運行時計劃。
使用這些工具,您可以確定以下信息:
-
訪問已分區表或索引的操作,例如掃描、查找、插入、更新、合并和刪除。
-
查詢訪問的分區。例如,運行時執行計劃中包含所訪問分區的總計數以及所訪問的連續分區的范圍。
-
何時在查找或掃描操作中使用跳躍掃描操作以便從一個或多個分區中檢索數據。
有關顯示執行計劃的詳細信息,請參閱 執行計劃操作指南幫助主題 。
增強的分區信息
SQL Server 2008 為編譯時執行計劃和運行時執行計劃都提供了增強的分區信息。現在,執行計劃可以提供以下信息:
-
可選的? Partitioned ?屬性,它指示對某已分區表執行的某個運算符,例如 seek、scan、insert、update、merge 或 delete。
-
新增的? SeekPredicateNew ?元素,它帶有? SeekKeys ?子元素,其中包含? PartitionID (作為第一個索引鍵列)和篩選條件(指定針對 PartitionID ?的查找范圍)。如果存在兩個? SeekKeys ?子元素,則表明對? PartitionID ?使用了跳躍掃描操作。
-
用于提供所訪問分區的總計的摘要信息。只有在運行時計劃中才有此信息。
為說明此信息在圖形執行計劃輸出和 XML 顯示計劃輸出中的顯示方式,請考慮對已分區表? fact _ sales ?的以下查詢。此查詢將更新兩個分區中的數據。
UPDATE fact_sales
SET quantity = quantity * 2
WHERE date_id BETWEEN 20080802 AND 20080902;
下圖顯示了此查詢的編譯時執行計劃中的? Clustered Index Seek ?運算符的屬性。若要查看? fact _ sales ?表的定義和分區定義,請參閱本主題中的“示例”部分。

Partitioned 屬性
對已分區表或索引執行某個運算符(例如? Index Seek )時, Partitioned ?屬性將出現在編譯時和運行時計劃中并設為? True ?(1)。設為? False ?(0) 時將不會顯示該屬性。
Partitioned ?屬性可以出現在以下物理和邏輯運算符中:
-
Table Scan
-
Index Scan
-
Index Seek
-
Insert
-
Update
-
Delete
-
Merge
如上圖所示,該屬性顯示在包含其定義的運算符的屬性中。在 XML 顯示計劃輸出中,該屬性在包含其定義的運算符的? RelOp ?節點中顯示為 Partitioned="1" 。
新增的 Seek 謂詞
在 XML 顯示計劃輸出中, SeekPredicateNew ?元素出現在包含其定義的運算符中。它最多可以包含兩個? SeekKeys ?子元素實例。第一個 SeekKeys ?實例項指定位于邏輯索引的分區 ID 級別的第一級查找操作。也就是說,該查找操作將確定為滿足查詢條件而必須訪問的分區。第二個? SeekKeys ?實例項指定在第一級查找中所標識的每個分區中進行的跳躍掃描操作的二級查找部分。
分區摘要信息
在運行時執行計劃中,分區摘要信息提供了所訪問分區的計數以及所訪問的實際分區的標識。您可以使用此信息來驗證查詢中所訪問的分區是否正確以及所有其他分區是否均排除在外。
所提供的信息包括以下內容:“實際分區計數”和“訪問的分區”。
“實際分區計數”是查詢所訪問的分區總數。
在 XML 顯示計劃輸出中,“訪問的分區”分區摘要信息顯示在新的? RuntimePartitionSummary ?元素中,此元素則位于包含該元素定義的運算符的? RelOp ?節點下。下面的示例顯示了? RuntimePartitionSummary ?元素的內容,它表明共訪問了兩個分區(分區 2 和 3)。
<RunTimePartitionSummary>
????<PartitionsAccessed PartitionCount="2">
????????<PartitionRange Start="2" End="3" />
????</PartitionsAccessed>
</RunTimePartitionSummary>
使用其他顯示計劃方法來顯示分區信息
顯示計劃方法 SHOWPLAN_ALL、SHOWPLAN_TEXT 和 STATISTICS PROFILE 并不報告本主題中所述的分區信息,但以下情況例外。作為? SEEK 謂詞的一部分,要訪問的分區由表示該分區 ID 的計算列的范圍謂詞標識。下面的示例顯示了? Clustered Index Seek ?運算符的? SEEK ?謂詞。訪問的分區是分區 2 和 3,并且該查找運算符將篩選符合條件? date_id BETWEEN 20080802 AND 20080902 ?的行。
|--Clustered Index Seek(OBJECT:([db_sales_test].[dbo].[fact_sales].[ci]),
????????SEEK:([PtnId1000] >= (2) AND [PtnId1000] <= (3)
????????????????AND [db_sales_test].[dbo].[fact_sales].[date_id] >= (20080802)
????????????????AND [db_sales_test].[dbo].[fact_sales].[date_id] <= (20080902))
????????????????ORDERED FORWARD)
在 SQL Server 2008 中,已分區堆被視為分區 ID 的邏輯索引。已分區堆的分區排除在執行計劃中表示為一個? Table Scan ?運算符,其中對分區 ID 使用了 SEEK 謂詞。下面的示例顯示了所提供的顯示計劃信息:
|-- Table Scan (OBJECT: ([db].[dbo].[T]), SEEK: ([PtnId1001]=[Expr1011]) ORDERED FORWARD)
使用相同或等效的分區函數對兩個表進行分區并且在查詢的聯接條件中指定了來自聯接兩側的分區依據列時就會發生聯接歸置。查詢優化器可以生成一個計劃,其中具有相等分區 ID 的每個表的分區將分別聯接在一起。歸置聯接可能比非歸置聯接的執行速度快,因為前者可以只需較少的內存和處理時間。優化器會基于成本估計來選擇非歸置計劃或歸置計劃。
在歸置計劃中, Nested Loops ?聯接從內側讀取一個或多個聯接表或索引分區。 Constant Scan ?運算符內的數字表示分區號。
為已分區表或索引生成歸置聯接的并行計劃時,在? Constant Scan ?和? Nested Loops ?聯接運算符之間會出現一個? Parallelism ?運算符。在此情況下,在聯接外側的多個線程會各自在不同的分區上進行讀取和操作。
下圖顯示了一個歸置聯接的并行查詢計劃。

查詢處理器對從已分區對象選擇的查詢使用查詢執行策略。作為執行策略的一部分,查詢處理器會確定查詢所需的表分區,以及要分配給每個分區的線程比例。在大多數情況下,查詢處理器會為每個分區分配數量相等或幾乎相等的線程,然后在這些分區中并行地執行查詢。以下幾段更詳細地介紹了線程分配情況。
如果線程數小于分區數,則查詢處理器會將每個線程分配給一個不同的分區,最初會有一個或多個分區沒有獲得分配的線程。當線程完成在一個分區上的執行時,查詢處理器會將它分配給下一個分區,直到每個分區都分配有一個線程。這是查詢處理器將線程重新分配給其他分區的唯一情況。

如果線程數與分區數相等,則查詢處理器會為每個分區分配一個線程。當線程完成時,不會重新分配給另一個分區。

如果線程數大于分區數,則查詢處理器會為每個分區分配相等數量的線程。如果線程數并非恰好是分區數的倍數,則查詢處理器會為某些分區額外分配一個線程,以使用所有可用線程。請注意,如果只有一個分區,則會將所有線程都分配給該分區。在下圖中,有四個分區和 14 個線程。每個分區都分配有 3 個線程,兩個分區具有一個額外的線程,總共分配了 14 個線程。當線程完成時,不會重新分配給另一個分區。

盡管以上示例指出了一種分配線程的簡單方式,但實際策略要復雜一些,并需要考慮在查詢執行過程中出現的其他變化因素。例如,如果表已分區,并在 A 列上有一個聚集索引,并且查詢有謂詞子句? WHERE A IN (13, 17, 25) ,則查詢處理器將為這三個查找值( A=13 、 A=17 ?和 A=25) )各分配一個或多個線程,而不是為每個表分區分配一個或多個線程。只需在包含這些值的分區中執行查詢,并且如果所有這些查找謂詞都恰好在同一個表分區中,則所有線程都將分配給同一個表分區。
為了舉出另一個示例,假定表在 A 列上有四個分區(邊界點為 (10, 20, 30)),在 B 列上有一個索引,并且查詢有一個謂詞子句? WHERE B IN (50, 100, 150) 。因為表分區是基于值 A,所以值 B 可以出現在任何表分區中。這樣,查詢處理器將分別在四個表分區中查找三個 B 值 (50, 100, 150) 中的每一個值。查詢處理器將按比例分配線程,以便它可以并行執行 12 個查詢掃描中的每一個掃描。
基于 A 列的表分區 |
在每個表分區中查找 B 列 |
---|---|
表分區 1:A < 10 |
B=50, B=100, B=150 |
表分區 2:A >= 10 AND A < 20 |
B=50, B=100, B=150 |
表分區 3:A >= 20 AND A < 30 |
B=50, B=100, B=150 |
表分區 4:A >= 30 |
B=50, B=100, B=150 |
為提高訪問來自大型已分區表和索引的大量數據的查詢性能,我們建議采用以下最佳方法:
-
跨越許多磁盤創建各個條帶化分區。
-
盡可能使用具有足夠主內存的服務器以便在內存中保留頻繁訪問的分區或所有分區,以減少 I/O 開銷。
-
如果內存容納不下所查詢的數據,請壓縮表和索引。這會減少 I/O 開銷。
-
使用具有快速處理器的服務器以及盡可能多的處理器核,以充分利用并行查詢處理能力。
-
確保服務器具有足夠的 I/O 控制器帶寬。
-
對每個大型已分區表創建聚集索引,以充分利用 B 樹掃描優化。
-
向已分區表進行大容量數據加載時,請遵循白皮書? Loading Bulk Data into a Partitioned Table(將大容量數據加載到已分區表中) 中的最佳方法建議。
下面的示例創建一個測試數據庫,其中包含一個帶有七個分區的表。執行本示例中的查詢時請使用前面所述的工具以查看編譯時計劃和運行時計劃的分區信息。
? 注意 |
---|
本示例要向表中插入超過 100 萬行數據。根據您的硬件情況,運行本示例可能需要幾分鐘時間。在執行本示例之前,請確保您有超過 1.5 GB 的可用磁盤空間。 |
USE master; GO IF DB_ID (N'db_sales_test') IS NOT NULL DROP DATABASE db_sales_test; GO CREATE DATABASE db_sales_test; GO USE db_sales_test; GO CREATE PARTITION FUNCTION [pf_range_fact](int) AS RANGE RIGHT FOR VALUES (20080801, 20080901, 20081001, 20081101, 20081201, 20090101); GO CREATE PARTITION SCHEME [ps_fact_sales] AS PARTITION [pf_range_fact] ALL TO ([PRIMARY]); GO CREATE TABLE fact_sales(date_id int, product_id int, store_id int, quantity int, unit_price numeric(7,2), other_data char(1000)) ON ps_fact_sales(date_id); GO CREATE CLUSTERED INDEX ci ON fact_sales(date_id); GO PRINT 'Loading...'; SET NOCOUNT ON; DECLARE @i int; SET @i = 1; WHILE (@i<1000000) BEGIN INSERT INTO fact_sales VALUES(20080800 + (@i%30) + 1, @i%10000, @i%200, RAND() * 25, (@i%3) + 1, ''); SET @i += 1; END; GO DECLARE @i int; SET @i = 1; WHILE (@i<10000) BEGIN INSERT INTO fact_sales VALUES(20080900 + (@i%30) + 1, @i%10000, @i%200, RAND() * 25, (@i%3) + 1, ''); SET @i += 1; END; PRINT 'Done.'; GO -- Two-partition query. SET STATISTICS XML ON; GO SELECT date_id, SUM(quantity*unit_price) AS total_price FROM fact_sales WHERE date_id BETWEEN 20080802 AND 20080902 GROUP BY date_id ; GO SET STATISTICS XML OFF; GO -- Single-partition query. SET STATISTICS XML ON; GO SELECT date_id, SUM(quantity*unit_price) AS total_price FROM fact_sales WHERE date_id BETWEEN 20080801 AND 20080831 GROUP BY date_id; GO SET STATISTICS XML OFF; GO
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
