1. 程式人生 > 資料庫 >SQL Server—— 在ETL過程列儲存索引vs 行儲存索引

SQL Server—— 在ETL過程列儲存索引vs 行儲存索引

本人新書上市,請多多關照:《SQL Server On Linux運維實戰 2017版從入門到精通》

在這裡插入圖片描述

這幾天我一直在跟進公司的一個性能問題,裡面涉及到聚集列儲存索引的問題。跟微軟的技術支援討論了一下,他們的建議可以考慮轉成非聚集的列儲存索引,那我到底怎麼做好呢?我覺得有必要研究一下這兩者的差異,說不定可以得出一個“不用列儲存索引”的結論呢。
為了感覺記錄下處理過程,在本系列中先緩一下分割槽的內容,插播一篇關於列儲存的文章。

選擇列儲存前需要了解的內容

  在選擇之前,首先要想一下下面的問題:

  • 我們到底要不要用列儲存索引?
  • 如果要用,用在哪些表上?用哪種?
  • 如果是非聚集的列儲存索引,建在哪些列上?

SQL Server 2012

  在SQL 2012的時候,只有非聚集的列儲存索引,同時它不允許建立後更新,所以如果選擇列儲存,就只有非聚集可選且只適合不怎麼更新的表。這種限制使得非聚集列儲存索引很難被推廣。其中不允許更新的限制成了絕大部分企業不使用該功能的最重要因素,雖然可以用一些方式來實現,不過確實不夠完美。

SQL Server 2014

  SQL Server 2014是一個開拓性的版本,不僅引入了記憶體技術(這是這個版本最大的亮點),還增強了列儲存技術和其他比如AlwaysOn方面的高可用技術。在這個版本中,引入了聚集列儲存索引且可更新,但是此時非聚集的列儲存索引依舊是不可更新。可更新的聚集列儲存索引的出現,一時間使得很多人認為DW只需要聚集的列儲存所有,不過當然還沒有什麼技術是可以面面俱到的。

SQL Server 2016

  SQL Server 2016,列儲存索引發展成3個獨立的體系:

  1. 基於磁碟的聚集列儲存索引:主要針對DW,基於磁碟(disk-based)是相對於基於記憶體而言,即從SQL 2014出現的記憶體技術。
  2. 基於磁碟的可更新的非聚集列儲存索引:主要針對混合場景OLTP+實時報表,終於可以更新非聚集列儲存了。
  3. In-Memory 聚集列儲存索引:主要針對基於記憶體技術的混合場景。
      到了這個時候,列儲存索引基本上就能應對所有常規場景了,聚集列儲存索引已經可以應對絕大部分的需求,不過也還有一些場景不適合,比如索引檢視,事務複製,CDC等,還有一些資料型別並不支援聚集列儲存索引。

SQL Server 2017

  SQL Server 2017正式以相容Linux和Windows平臺為主要賣點來發布,針對列儲存索引,2017引入了聚集列儲存索引對LOBs的支援,對非持久的計算列支援。還有非聚集列儲存索引對聯機重建的支援。
  

Azure SQL DB

  Azure SQL DB本質上跟當前最新的資料庫版本(本文釋出時是SQL 2019)相同,並且由於雲平臺的特性,它具有更快的新功能釋出,幾乎所有技術都在Auzre上先運用然後才會更新到本地版。

什麼時候適合用列儲存索引

  首先要注意我這裡沒討論用哪種,只是考慮用列儲存還是行儲存(也就是常規索引)。
  有幾個關鍵因素會影響是否使用:

  1. 查詢絕大部分表資料時:比如10億資料的表,經常需要查詢9億資料,那麼這個時候列儲存索引會很高效。
  2. 資源比如記憶體/CPU/磁碟空間是查詢效能的瓶頸時:因為列儲存索引帶有高度壓縮的特性,所以對資源有大量要求的查詢可以通過列儲存索引的方式來減少資料集。
  3. 列裡面包含大量相似的資料或者可以實現高度壓縮的資料時。可以大幅度節省空間消耗。
  4. 針對ETL過程,對於歸檔所有資料時,聚集列儲存索引可以節省大量空間。

  如果是考慮聚集列儲存索引,首先要考慮是否有不支援的資料型別,其次就是單純測試效能時,可能需要其他技術來做輔助,比如記憶體表。

  上面的內容也意味著如果你只需要查詢少量資料或者高度唯一的資料時,列儲存索引並不一定是最好的選擇。

  假設我們現在確實需要用到列儲存索引,那麼本文打算介紹一下如何去選擇。

影響匯入的因素

  影響載入速度的關鍵因素就是儘量減少對基礎結構的額外負擔。說白了就是沒有索引和沒有壓縮。但是在SQL 2014中,聚集列儲存索引的Delta-Stores使用頁壓縮排行壓縮。一開始的設計應該時為了把Delta-Stores佔用的空間釋放出來,但是從實踐來看不見得是有效的。因為不管載入多快,都會被壓縮操作拖延總體時間。
  從SQL 2016開始,對如並行插入進行了改進。特別是對於具有大量文字列的寬表。它主要的做法是移除Delta-Stores上的壓縮。

實驗

  說太多好像沒什麼意義,要不還是直接來看實驗吧。首先建立一個表,然後通過迴圈插入100萬隨機數,看一下在CCITable中使用聚集列儲存索引聚集及非聚集行儲存索引的差異。
  下面的程式碼需要單獨執行,按照Step來執行,在Step 4和Step 7 時開啟實際執行計劃,其他時候可以關閉,減少執行時間,不過不關閉也沒關係。
  第一步,建立一個表,都是BIGINT型別。此時表是堆表。
  第二步,迴圈100萬次插入隨機資料。並檢查表大小
  第三步,建立兩個非聚集索引。並檢查表大小
  第四步,開啟執行計劃幷包含SET STATISTICS IO命令,進行全表的掃描(實際上就是索引掃描)及聚合計算。可以得到執行計劃和IO資訊。
  第五步,移除表上的所有索引
  第六步,建立聚集列儲存索引,聚集列儲存索引不需要指定列,實際上就是把整個錶轉換成一個列儲存格式。同時檢查表大小。
  第七步,按照四步的方式再次執行並檢視結果。

--Step 1 Create Table
CREATE TABLE CCITable (ValueOne BIGINT,ValueTwo BIGINT,ValueDate DATETIME)

--Step 2 1 million data loading
DECLARE @rand1 BIGINT,@rand2 BIGINT,@b INT = 1

WHILE @b <= 1000000
BEGIN
 SELECT @rand1 = CAST(((RAND()*0.00001) * ABS(CHECKSUM(NEWID()))) AS BIGINT)
 SELECT @rand2 = CAST(((RAND()*0.0001) * ABS(CHECKSUM(NEWID()))) AS BIGINT)
 
 INSERT INTO CCITable VALUES (@rand1,@rand2,DATEADD(DD,-(@rand1/10),GETDATE()))
 
 SET @b = @b + 1
END
--check the table size
SELECT
s.Name AS SchemaName,t.Name AS TableName,p.rows AS RowCounts,CAST(ROUND((SUM(a.used_pages) / 128.00),2) AS NUMERIC(36,2)) AS Used_MB,CAST(ROUND((SUM(a.total_pages) - SUM(a.used_pages)) / 128.00,2)) AS Unused_MB,CAST(ROUND((SUM(a.total_pages) / 128.00),2)) AS Total_MB
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE t.name='CCITable'
GROUP BY t.Name,s.Name,p.Rows


--Step 3 create row store index and check the table size again
CREATE NONCLUSTERED INDEX IX_CCITable_ValueDate ON CCITable (ValueDate)
CREATE NONCLUSTERED INDEX IX_CCITable_Value ON CCITable (ValueOne)

SELECT
s.Name AS SchemaName,p.Rows


--Step 4 query 
set statistics io on ;
SELECT 
 MIN(ValueOne),MAX(ValueOne),AVG(ValueOne),STDEV(ValueOne),SUM(ValueOne)/(AVG(ValueOne)),MIN(ValueTwo),MAX(ValueTwo),AVG(ValueTwo),STDEV(ValueTwo)
FROM CCITable

set statistics io off;
--Step 5 remove indexes
DROP INDEX IX_CCITable_ValueDate ON CCITable
DROP INDEX IX_CCITable_Value ON CCITable

--Step 6 Create a clustered columnstore index and check the table size again
CREATE CLUSTERED COLUMNSTORE INDEX CCI_CCITable ON CCITable

SELECT
s.Name AS SchemaName,p.Rows


--Step 7 query 
set statistics io on ;
SELECT 
 MIN(ValueOne),STDEV(ValueTwo)
FROM CCITable
DROP TABLE CCITable

set statistics io off;

  在上面,可以看到我並沒有對很多列加索引,因為對於ETL環境,很多表都具有上百個列(我剛處理的公司的情況就是有600列),這樣不僅使得索引低效,也使得儲存開銷明顯增大。

如果要查詢數百列的表的幾乎所有資料,對所有列加索引並使用索引掃描在大多數情況下都沒有明顯的用處。

  上面程式碼的結果如下,可以看出列儲存索引在這種特定情況下更加有效。
  堆表匯入速度,36分鐘:
在這裡插入圖片描述
  堆表佔據空間33.45MB
在這裡插入圖片描述

  非聚集索引在掃描過程中的I/O情況:

(1 行受影響)
Table 'CCITable'. Scan count 1,logical reads 4274,physical reads 0,page server reads 0,read-ahead reads 0,page server read-ahead reads 0,lob logical reads 0,lob physical reads 0,lob page server reads 0,lob read-ahead reads 0,lob page server read-ahead reads 0.

  非聚集索引掃描的執行計劃:
在這裡插入圖片描述
在這裡插入圖片描述

(1 行受影響)
Table 'CCITable'. Scan count 1,logical reads 0,lob logical reads 1177,lob physical reads 2,lob read-ahead reads 3858,lob page server read-ahead reads 0.
Table 'CCITable'. Segment reads 1,segment skipped 0.

在這裡插入圖片描述

  另外我們在這個過程中也檢查了表的體積,可以看到聚集列儲存索引對空間消耗更小。

  非聚集索引化後表大小:
在這裡插入圖片描述

  只有聚集列儲存索引時的表大小:
在這裡插入圖片描述

  現在我們來試一下相同環境下(換一個表),變成聚集列儲存索引後對匯入的影響。要注意列儲存索引有一個Delta-Store的特性,這個特性會影響效能,一般建議是一次匯入剛好超過102400行資料。這樣可以不經過Delta-Store,否則資料先進入Delta-Store然後再轉換成列儲存,這一步會有明顯的開銷。

CREATE TABLE CCITable_1 (ValueOne BIGINT,ValueDate DATETIME)
GO
CREATE CLUSTERED COLUMNSTORE INDEX CCI_CCITable ON CCITable_1
GO
DECLARE @rand1 BIGINT,@b INT = 1

WHILE @b <= 1000000
BEGIN
 SELECT @rand1 = CAST(((RAND()*0.00001) * ABS(CHECKSUM(NEWID()))) AS BIGINT)
 SELECT @rand2 = CAST(((RAND()*0.0001) * ABS(CHECKSUM(NEWID()))) AS BIGINT)
 
 INSERT INTO CCITable_1 VALUES (@rand1,GETDATE()))
 
 SET @b = @b + 1
END

  時間對比相差無幾,也可以初步判斷影響不大,不過這個可能會有很多因素影響結果,所以最好還是在實際環境測一下:
在這裡插入圖片描述

總結

  從實驗看到,列儲存索引,雖然這裡沒有演示非聚集列儲存索引,但是對於特定情況的ETL甚至DW環境,比普通行儲存更有用。
  當然,所有的決定都依賴於特定環境,如果你的I/O很好,可以考慮直接把表建成聚集列儲存索引然後匯入。
  另外有個非常重要的影響因素就是ETL過程的方式,對於現今的大資料生態系統中各種可以實現ETL過程的軟體和其他傳統軟體,在匯入過程的行為都會影響最終的效能。

  資料對比:
在這裡插入圖片描述