在專用 SQL 集區中建立和更新數據表統計數據
本文提供在專用 SQL 集區中建立和更新數據表查詢優化統計數據的建議和範例。
為何使用統計資料
專用 SQL 集區越了解您的資料,執行查詢的速度就越快。 將數據載入專用 SQL 集區之後,收集數據的統計數據是您可以執行的最重要動作之一,以優化查詢。
專用 SQL 集區查詢最佳化工具是成本型最佳化工具。 它會比較各種查詢方案的成本,然後選擇成本最低的方案。 在大多數情況下,它會選擇執行最快的方案。
例如,如果優化器估計查詢篩選的日期會傳回一個數據列,則優化器會選取特定計劃。 如果優化器估計選取的日期傳回一百萬個數據列,則會選擇不同的方案。
自動建立統計資料
當資料庫 AUTO_CREATE_STATISTICS
選項開啟時,專用 SQL 集區會分析傳入的用戶查詢是否有遺漏的統計數據。
如果遺漏統計資料,查詢最佳化工具會在查詢述詞或聯結條件中建立個別資料行的統計資料,以改善查詢計劃的基數估計值。
注意
自動建立統計資料目前依預設開啟。
您可以執行下列 T-SQL 命令來檢查您的專用 SQL 集區 AUTO_CREATE_STATISTICS
是否已設定:
SELECT name, is_auto_create_stats_on
FROM sys.databases
如果您的專用 SQL 集區 AUTO_CREATE_STATISTICS
未設定,建議您執行下列命令來啟用此屬性。 將取代 <your-datawarehouse-name>
為專用 SQL 集區的名稱。
ALTER DATABASE <your-datawarehouse-name>
SET AUTO_CREATE_STATISTICS ON
這些語句會觸發統計數據的自動建立:
SELECT
-
INSERT
...SELECT
-
CREATE TABLE AS SELECT
(CTAS) UPDATE
DELETE
-
EXPLAIN
當偵測到包含聯結或述詞是否存在時
注意
自動建立統計數據不會在臨時表或外部數據表上執行。
自動建立統計資料是同步進行的,因此,如果您的資料行缺少統計資料,查詢效能可能會略為降低。 為單一資料行建立統計資料的時間,取決於資料表的大小。
若要避免測量效能降低,請在分析系統前,先執行基準測試的工作負載,以確定會先建立統計資料。
注意
統計數據的建立會記錄在不同的 用戶內容下sys.dm_pdw_exec_requests 。
建立自動統計數據時,會採用下列格式: _WA_Sys_<8 digit column id in Hex>_<8 digit table id in Hex>
。 您可以執行 DBCC SHOW_STATISTICS 命令來檢視已建立的統計資料:
DBCC SHOW_STATISTICS (<table_name>, <target>)
table_name
是數據表的名稱,其中包含要顯示的統計數據。 資料表不得為外部資料表。 目標是用來顯示統計資料資訊的目標索引、統計資料或資料行名稱。
更新統計資料
其中一個最佳做法,是隨著新增新的日期,每天在日期資料行上更新統計資料。 專用 SQL 集區每次載入新的資料列,就會加入新的載入日期或交易日期。 這些增加項目會造成資料散發變更,並使統計資料過時。
客戶資料表中國家/地區資料行的統計資料,可能永遠不需更新,因為值的散發通常不會變更。 假設客戶間的散發固定不變,將新資料列加入至資料表變化並不會改變資料散發情況。
但如果專用 SQL 集區只有一個國家/地區,您卻帶入新的國家/地區資料,導致集區儲存多個國家/地區的資料時,即須更新國家/地區資料行的統計資料。
以下是更新統計數據的建議:
統計資料屬性 | 建議 |
---|---|
統計資料更新的頻率 | 保守:載入或轉換資料後,每天 |
取樣 | 小於 10 億個資料列,使用預設取樣 (20%)。
大於 10 億個資料列,使用 2% 的取樣。 |
針對查詢進行疑難解答時要問的第一個問題之一是 「統計數據是否為最新狀態?
數據存留期無法回答這個問題。 如果基礎資料並沒有任何實質變更,最新的統計資料物件可能會是舊的。 當數據列數目大幅變更,或數據行值分佈有重大變更時, 是時候 更新統計數據了。
沒有動態管理檢視可判斷數據表中的數據自上次更新統計數據之後是否已變更。 下列兩個查詢有助判斷統計資料是否過時。
查詢 1:尋找統計數據的數據列計數與實際數據列計數 (
stats_row_count
actual_row_count
) 之間的差異。select objIdsWithStats.[object_id], actualRowCounts.[schema], actualRowCounts.logical_table_name, statsRowCounts.stats_row_count, actualRowCounts.actual_row_count, row_count_difference = CASE WHEN actualRowCounts.actual_row_count >= statsRowCounts.stats_row_count THEN actualRowCounts.actual_row_count - statsRowCounts.stats_row_count ELSE statsRowCounts.stats_row_count - actualRowCounts.actual_row_count END, percent_deviation_from_actual = CASE WHEN actualRowCounts.actual_row_count = 0 THEN statsRowCounts.stats_row_count WHEN statsRowCounts.stats_row_count = 0 THEN actualRowCounts.actual_row_count WHEN actualRowCounts.actual_row_count >= statsRowCounts.stats_row_count THEN CONVERT(NUMERIC(18, 0), CONVERT(NUMERIC(18, 2), (actualRowCounts.actual_row_count - statsRowCounts.stats_row_count)) / CONVERT(NUMERIC(18, 2), actualRowCounts.actual_row_count) * 100) ELSE CONVERT(NUMERIC(18, 0), CONVERT(NUMERIC(18, 2), (statsRowCounts.stats_row_count - actualRowCounts.actual_row_count)) / CONVERT(NUMERIC(18, 2), actualRowCounts.actual_row_count) * 100) END from ( select distinct object_id from sys.stats where stats_id > 1 ) objIdsWithStats left join ( select object_id, sum(rows) as stats_row_count from sys.partitions group by object_id ) statsRowCounts on objIdsWithStats.object_id = statsRowCounts.object_id left join ( SELECT sm.name [schema] , tb.name logical_table_name , tb.object_id object_id , SUM(rg.row_count) actual_row_count FROM sys.schemas sm INNER JOIN sys.tables tb ON sm.schema_id = tb.schema_id INNER JOIN sys.pdw_table_mappings mp ON tb.object_id = mp.object_id INNER JOIN sys.pdw_nodes_tables nt ON nt.name = mp.physical_name INNER JOIN sys.dm_pdw_nodes_db_partition_stats rg ON rg.object_id = nt.object_id AND rg.pdw_node_id = nt.pdw_node_id AND rg.distribution_id = nt.distribution_id WHERE rg.index_id = 1 GROUP BY sm.name, tb.name, tb.object_id ) actualRowCounts on objIdsWithStats.object_id = actualRowCounts.object_id
查詢 2: 檢查每個數據表上上次更新統計數據的時間,以尋找統計數據的存留期。
注意
如果數據行的值分佈有重大變更,則不論上次更新統計數據為何,您都應該更新統計數據。
SELECT sm.[name] AS [schema_name], tb.[name] AS [table_name], co.[name] AS [stats_column_name], st.[name] AS [stats_name], STATS_DATE(st.[object_id],st.[stats_id]) AS [stats_last_updated_date] FROM sys.objects ob JOIN sys.stats st ON ob.[object_id] = st.[object_id] JOIN sys.stats_columns sc ON st.[stats_id] = sc.[stats_id] AND st.[object_id] = sc.[object_id] JOIN sys.columns co ON sc.[column_id] = co.[column_id] AND sc.[object_id] = co.[object_id] JOIN sys.types ty ON co.[user_type_id] = ty.[user_type_id] JOIN sys.tables tb ON co.[object_id] = tb.[object_id] JOIN sys.schemas sm ON tb.[schema_id] = sm.[schema_id] WHERE st.[user_created] = 1;
例如,專用 SQL 集區資料倉儲中的日期資料行,通常需要定期更新統計資料。 專用 SQL 集區每次載入新的資料列,就會加入新的載入日期或交易日期。 這些增加項目會造成資料散發變更,並使統計資料過時。
相反地,客戶資料表上性別資料行的統計資料可能永遠不需要更新。 假設客戶間的散發固定不變,將新資料列加入至資料表變化並不會改變資料散發情況。
如果專用 SQL 集區只包含一種性別,而新的需求導致多種性別時,即需要更新性別資料行的統計資料。
如需詳細資訊,請參閱統計資料的一般指引。
實作統計數據管理
擴充數據載入程式通常是個好主意,以確保在載入結束時更新統計數據,以避免或最小化並行查詢之間的封鎖或資源爭用。
數據載入是數據表最常變更其大小或值分佈時。 資源載入是實作部分管理程序的邏輯位置。
以下提供指導原則,以便更新您的統計資料:
- 請確定每個載入的資料表至少有一個統計資料物件已更新。 這會在統計資料更新過程中更新資料表大小 (資料列計數和頁面計數) 資訊。
- 著重於參與 JOIN、GROUP BY、ORDER BY 和 DISTINCT 子句的資料行。
- 請考慮更頻繁地更新 遞增索引鍵 數據行,因為這些值不會包含在統計數據直方圖中。
- 請考慮較不要頻繁更新靜態散發資料行。
- 請記住,每個統計資料物件會依序更新。 僅只實作
UPDATE STATISTICS <TABLE_NAME>
不一定理想,尤其是對具有許多統計資料物件的寬型資料表而言。
如需詳細資訊,請參閱基數估計。
範例:建立統計資料
下列範例顯示如何使用各種選項來建立統計資料。 您針對每個數據行使用的選項取決於資料的特性,以及數據行在查詢中的使用方式。
使用預設選項建立單一資料行統計資料
若要建立資料行的統計資料,請提供統計資料物件的名稱和資料行的名稱。
此語法會使用所有預設選項。 根據預設,建立統計資料時,會取樣資料表的 20%。
CREATE STATISTICS [statistics_name] ON [schema_name].[table_name]([column_name]);
例如:
CREATE STATISTICS col1_stats ON dbo.table1 (col1);
檢查每個資料列以建立單一資料行統計資料
20% 的預設取樣率足以應付大部分的情況。 不過,您可以調整取樣率。
若要取樣整個資料表,請使用此語法:
CREATE STATISTICS [statistics_name] ON [schema_name].[table_name]([column_name]) WITH FULLSCAN;
例如:
CREATE STATISTICS col1_stats ON dbo.table1 (col1) WITH FULLSCAN;
指定取樣大小以建立單一資料行統計資料
或者,您可以以百分比指定取樣大小:
CREATE STATISTICS col1_stats ON dbo.table1 (col1) WITH SAMPLE = 50 PERCENT;
只對某些資料列建立單一資料行統計資料
您也可以對資料表中部分的資料列建立統計資料。 這稱為 篩選統計數據。
例如,當您計劃查詢大型分割資料表的特定分割時,可以使用篩選的統計資料。 藉由只針對數據分割值建立統計數據,統計數據的精確度會改善,進而改善查詢效能。
這個範例會建立某個值範圍的統計資料。 您可以輕鬆地定義這些值以符合分割中的值範圍。
CREATE STATISTICS stats_col1 ON table1(col1) WHERE col1 > '2000101' AND col1 < '20001231';
注意
若要讓查詢最佳化工具在選擇分散式查詢計劃時考慮使用篩選的統計資料,查詢必須符合統計資料物件的定義。 使用上述範例,查詢的 WHERE 子句需要指定介於 2000101 和 20001231 之間的 col1 值。
使用所有選項建立單一資料行統計資料
您也可以將選項結合在一起。 以下範例會使用自訂樣本大小建立篩選的統計資料物件:
CREATE STATISTICS stats_col1 ON table1 (col1) WHERE col1 > '2000101' AND col1 < '20001231' WITH SAMPLE = 50 PERCENT;
如需完整參考,請參閱 CREATE STATISTICS。
建立多重資料行統計資料
若要建立多重資料行統計資料物件,請利用上述範例,但要指定更多資料行。
注意
用來估計查詢結果中資料列數目的長條圖,只適用於統計資料物件定義中所列的第一個資料行。
在此範例中,直方圖位於 product_category
上。 跨資料列統計資料會在 和 product_sub_category
上product_category
計算:
CREATE STATISTICS stats_2cols ON table1 (product_category, product_sub_category) WHERE product_category > '2000101' AND product_category < '20001231' WITH SAMPLE = 50 PERCENT;
由於和 product_sub_category
之間product_category
有相互關聯,因此如果同時存取這些數據行,多數據行統計數據物件可能會很有用。
對資料表中的所有資料行建立統計資料
建立統計數據的其中一種方式是在建立數據表之後發出 CREATE STATISTICS
命令:
CREATE TABLE dbo.table1
(
col1 int
, col2 int
, col3 int
)
WITH
(
CLUSTERED COLUMNSTORE INDEX
)
;
CREATE STATISTICS stats_col1 on dbo.table1 (col1);
CREATE STATISTICS stats_col2 on dbo.table2 (col2);
CREATE STATISTICS stats_col3 on dbo.table3 (col3);
使用預存程序在 SQL 集區建立所有資料行的統計資料
專用 SQL 集區沒有相當於 sp_create_stats
SQL Server 中的系統預存程式。 此預存程序會在仍無統計資料的 SQL 集區,建立各資料行的單一資料行統計資料物件。
下列範例示範如何開始使用 SQL 集區設計。 請放心地依照您的需求進行調整。
CREATE PROCEDURE [dbo].[prc_sqldw_create_stats]
( @create_type tinyint -- 1 default 2 Fullscan 3 Sample
, @sample_pct tinyint
)
AS
IF @create_type IS NULL
BEGIN
SET @create_type = 1;
END;
IF @create_type NOT IN (1,2,3)
BEGIN
THROW 151000,'Invalid value for @stats_type parameter. Valid range 1 (default), 2 (fullscan) or 3 (sample).',1;
END;
IF @sample_pct IS NULL
BEGIN;
SET @sample_pct = 20;
END;
IF OBJECT_ID('tempdb..#stats_ddl') IS NOT NULL
BEGIN;
DROP TABLE #stats_ddl;
END;
CREATE TABLE #stats_ddl
WITH ( DISTRIBUTION = HASH([seq_nmbr])
, LOCATION = USER_DB
)
AS
WITH T
AS
(
SELECT t.[name] AS [table_name]
, s.[name] AS [table_schema_name]
, c.[name] AS [column_name]
, c.[column_id] AS [column_id]
, t.[object_id] AS [object_id]
, ROW_NUMBER()
OVER(ORDER BY (SELECT NULL)) AS [seq_nmbr]
FROM sys.[tables] t
JOIN sys.[schemas] s ON t.[schema_id] = s.[schema_id]
JOIN sys.[columns] c ON t.[object_id] = c.[object_id]
LEFT JOIN sys.[stats_columns] l ON l.[object_id] = c.[object_id]
AND l.[column_id] = c.[column_id]
AND l.[stats_column_id] = 1
LEFT JOIN sys.[external_tables] e ON e.[object_id] = t.[object_id]
WHERE l.[object_id] IS NULL
AND e.[object_id] IS NULL -- not an external table
)
SELECT [table_schema_name]
, [table_name]
, [column_name]
, [column_id]
, [object_id]
, [seq_nmbr]
, CASE @create_type
WHEN 1
THEN CAST('CREATE STATISTICS '+QUOTENAME('stat_'+table_schema_name+ '_' + table_name + '_'+column_name)+' ON '+QUOTENAME(table_schema_name)+'.'+QUOTENAME(table_name)+'('+QUOTENAME(column_name)+')' AS VARCHAR(8000))
WHEN 2
THEN CAST('CREATE STATISTICS '+QUOTENAME('stat_'+table_schema_name+ '_' + table_name + '_'+column_name)+' ON '+QUOTENAME(table_schema_name)+'.'+QUOTENAME(table_name)+'('+QUOTENAME(column_name)+') WITH FULLSCAN' AS VARCHAR(8000))
WHEN 3
THEN CAST('CREATE STATISTICS '+QUOTENAME('stat_'+table_schema_name+ '_' + table_name + '_'+column_name)+' ON '+QUOTENAME(table_schema_name)+'.'+QUOTENAME(table_name)+'('+QUOTENAME(column_name)+') WITH SAMPLE '+CONVERT(varchar(4),@sample_pct)+' PERCENT' AS VARCHAR(8000))
END AS create_stat_ddl
FROM T
;
DECLARE @i INT = 1
, @t INT = (SELECT COUNT(*) FROM #stats_ddl)
, @s NVARCHAR(4000) = N''
;
WHILE @i <= @t
BEGIN
SET @s=(SELECT create_stat_ddl FROM #stats_ddl WHERE seq_nmbr = @i);
PRINT @s
EXEC sp_executesql @s
SET @i+=1;
END
DROP TABLE #stats_ddl;
若要使用預設值為資料表中的所有資料行建立統計資料,請執行預存程序。
EXEC [dbo].[prc_sqldw_create_stats] 1, NULL;
若要使用 fullscan 建立資料表中所有資料行的統計資料,請呼叫下列程序:
EXEC [dbo].[prc_sqldw_create_stats] 2, NULL;
若要在數據表中的所有數據行上建立取樣統計數據,請輸入 3,以及取樣百分比。 此程式會使用 20 % 的取樣率。
EXEC [dbo].[prc_sqldw_create_stats] 3, 20;
範例:更新統計資料
若要更新統計資料,您可以:
- 更新一個統計資料物件。 指定您要更新的統計資料物件名稱。
- 更新資料表上的所有統計資料物件。 指定資料表名稱,而不是一個特定統計資料物件。
更新一個特定統計資料物件
使用下列語法來更新特定統計資料物件:
UPDATE STATISTICS [schema_name].[table_name]([stat_name]);
例如:
UPDATE STATISTICS [dbo].[table1] ([stats_col1]);
藉由更新特定統計資料物件,即可減少管理統計資料所需的時間和資源。 這需要經過思考,才能選擇更新的最佳統計資料物件。
更新資料表的所有統計資料
更新資料表上所有統計資料物件的簡單方法為:
UPDATE STATISTICS [schema_name].[table_name];
例如:
UPDATE STATISTICS dbo.table1;
UPDATE STATISTICS
語句很容易使用。 只要記住這會更新資料表上的所有統計資料,因此可能會執行超出所需的更多工作。 如果效能不是問題,這是保證統計數據為最新狀態的最簡單且最完整的方式。
注意
當您更新數據表上的所有統計數據時,專用 SQL 集區會執行掃描來取樣每個統計數據對象的數據表。 如果資料表很大,而且有許多資料行以及許多統計資料,則根據需求來更新個別統計資料可能比較有效率。
如需 UPDATE STATISTICS
程序的實作,請參閱暫存資料表。 實作方法與上述的 CREATE STATISTICS
程序有點不同,但結果相同。
如需完整語法,請參閱更新統計資料。
統計資料中繼資料
您可利用數個系統檢視和函式來尋找統計資料相關資訊。 例如,使用 stats-date 函式來查看最後建立或更新統計資料的時間,即可查看統計資料物件是否可能過期。
統計資料的目錄檢視
這些系統檢視提供統計資料的相關資訊:
目錄檢視 | 描述 |
---|---|
sys.columns | 每個數據行各一個數據列 |
sys.objects | 資料庫中每個物件的一個數據列 |
sys.schemas | 資料庫中每個架構的一個數據列 |
sys.stats | 每個統計數據物件的一個數據列 |
sys.stats_columns | 統計數據物件中每個數據行的一個數據列;連結回 sys.columns |
sys.tables | 每個資料表各有一個資料列(包括外部資料表) |
sys.table_types | 每個數據類型各有一個數據列 |
統計資料的系統函式
這些系統函式很適合用於處理統計資料:
系統函式 | 描述 |
---|---|
STATS_DATE | 上次更新統計數據物件的日期 |
DBCC SHOW_STATISTICS | 統計數據物件所瞭解之值分佈的摘要層級和詳細資訊 |
將統計資料資料行和函式結合成一個檢視
此檢視會將與統計數據和函 STATS_DATE()
式結果相關的數據行結合在一起。
CREATE VIEW dbo.vstats_columns
AS
SELECT
sm.[name] AS [schema_name]
, tb.[name] AS [table_name]
, st.[name] AS [stats_name]
, st.[filter_definition] AS [stats_filter_definition]
, st.[has_filter] AS [stats_is_filtered]
, STATS_DATE(st.[object_id],st.[stats_id])
AS [stats_last_updated_date]
, co.[name] AS [stats_column_name]
, ty.[name] AS [column_type]
, co.[max_length] AS [column_max_length]
, co.[precision] AS [column_precision]
, co.[scale] AS [column_scale]
, co.[is_nullable] AS [column_is_nullable]
, co.[collation_name] AS [column_collation_name]
, QUOTENAME(sm.[name])+'.'+QUOTENAME(tb.[name])
AS two_part_name
, QUOTENAME(DB_NAME())+'.'+QUOTENAME(sm.[name])+'.'+QUOTENAME(tb.[name])
AS three_part_name
FROM sys.objects AS ob
JOIN sys.stats AS st ON ob.[object_id] = st.[object_id]
JOIN sys.stats_columns AS sc ON st.[stats_id] = sc.[stats_id]
AND st.[object_id] = sc.[object_id]
JOIN sys.columns AS co ON sc.[column_id] = co.[column_id]
AND sc.[object_id] = co.[object_id]
JOIN sys.types AS ty ON co.[user_type_id] = ty.[user_type_id]
JOIN sys.tables AS tb ON co.[object_id] = tb.[object_id]
JOIN sys.schemas AS sm ON tb.[schema_id] = sm.[schema_id]
WHERE 1=1
AND st.[user_created] = 1
;
DBCC SHOW_STATISTICS() 範例
DBCC SHOW_STATISTICS()
顯示統計數據物件內保存的數據。 此資料來自三個部分:
- 頁首
- 密度向量
- 長條圖
有關統計資料的標頭中繼資料。 此長條圖會顯示統計資料物件的第一個索引鍵資料行中的值散發。 密度向量可測量跨資料行關聯性。
注意
專用 SQL 集區會使用統計資料物件中的任何資料來計算基數估計值。
顯示標頭、密度和長條圖
這個簡單範例顯示統計資料物件的所有三個部分:
DBCC SHOW_STATISTICS([<schema_name>.<table_name>],<stats_name>)
例如:
DBCC SHOW_STATISTICS (dbo.table1, stats_col1);
顯示 DBCC SHOW_STATISTICS() 的一或多個部分
如果您只想要檢視特定部分,請使用 WITH
子句並指定您要查看哪些部分:
DBCC SHOW_STATISTICS([<schema_name>.<table_name>],<stats_name>) WITH stat_header, histogram, density_vector
例如:
DBCC SHOW_STATISTICS (dbo.table1, stats_col1) WITH histogram, density_vector
DBCC SHOW_STATISTICS() 差異
DBCC SHOW_STATISTICS()
相較於 SQL Server,更嚴格地在專用 SQL 集區中實作:
- 不支援未記載的功能。
- 無法使用
Stats_stream
。 - 無法聯結特定統計數據子集的結果。 例如:
STAT_HEADER JOIN DENSITY_VECTOR
。 -
NO_INFOMSGS
無法設定訊息隱藏。 - 無法使用統計數據名稱周圍的方括弧。
- 無法使用資料行名稱來識別統計數據物件。
- 不支援自定義錯誤 2767。