文章出處

一、概述

 

列存儲索引是SQL Server 2012中為提高數據查詢的性能而引入的一個新特性,顧名思義,數據以列的方式存儲在頁中,不同于聚集索引、非聚集索引及堆表等以行為單位的方式存儲。因為它并不要求存儲的列必須唯一,因此它可以通過壓縮將重復的列合并,從而減少查詢時的磁盤IO,提高效率。

為了分析列存儲索引,我們先看看B樹或堆中的數據的存儲方式,如下圖,在page1上,數據是按照行的方式存儲數據的,假設一行有10列,那么在該頁上,實際的存儲也會以每行10列的方式存儲,如下圖中的C1到C10。

假設我們執行select c1,c2 from table時,數據庫會讀取整個page1,顯然,從C3到C10并不是我們想要的數據,但因為數據庫每次讀的最小單位是一頁,因此這些不得不都加載到內存中。如果數據頁多時,必然要消耗更過的IO和內存。

image

如果是列存儲索引,數據按列的方式存儲在一個頁面中,如下圖,page1中只存儲表中C1列,page2只存儲c2列,以此類推,page10存儲c10列。

假設我們執行select c1,c2 from table時,結果會怎樣呢?數據庫只會讀page1和page2,至于page3到page10因為沒有對應的數據,數據庫不會去讀這些頁,也不會加載到內存中,相比行存儲而言,減少了磁盤IO和優化了內存的使用。

image

 

下文做了一個技術驗證,用來分析列存儲索引的查詢性能。

思路:做兩張一模一樣的分區表(分區表可以更好的展示效果),含1000000行數據,然后給其中一張表(sales2)建立聚集索引,另一張表(sales)建列存儲索引,最后來對比這兩張表的查詢性能。

 

二、創建表

 

先做兩張相同的表,創建的語句如下:

create partition function pf (date) as range left for values

('20110712', '20110713', '20110714', '20110715', '20110716');

go

create partition scheme ps as partition pf all to ([PRIMARY]);

go

create table sales (

[id] int not null identity (1,1),

[date] date not null,

itemid smallint not null,

price money not null,

quantity numeric(18,4) not null)

on ps([date]);

go

declare @i int = 0;

begin transaction;

while @i < 1000000

begin

declare @date date = dateadd(day, @i /250000.00, '20110712');

insert into sales2 ([date], itemid, price, quantity)

values (@date, rand()*10000, rand()*100 + 100, rand()* 10.000+1);

set @i += 1;

if @i % 10000 = 0

begin

raiserror (N'Inserted %d', 0, 1, @i);

commit;

begin tran;

end

end

commit;

GO

 

三、查詢含聚集鍵的表

 

(1) 創建表sales2的聚集鍵

CREATE CLUSTERED INDEX Clu_sales2_index ON sales2(date,price,quantity) on ps([date]);

查看表的存儲信息

select * from sys.system_internals_partitions p

where p.object_id = object_id('sales2');

select au.* from sys.system_internals_allocation_units au

join sys.system_internals_partitions p

on p.partition_id = au.container_id

where p.object_id = object_id('sales2');

GO

 

image

該表一共有6個分區,其中只有4個分區有數據,每個分區250000行,已使用1089頁,。

(2) 執行查詢語句 (注意清掉緩存)

SET STATISTICS IO ON;

SET STATISTICS TIME ON;

SELECT COUNT(*),SUM(price*quantity) FROM sales2 WHERE date='20110713';

GO

image

我們可以看到,在這個查詢中,一共有1089次邏輯讀(等于該表每個分區中的已使用頁數),CPU時間為62毫秒,占用時間為261毫秒。

備注:CPU時間,執行語句的時間;

占用時間,從磁盤讀取數據開始到完全處理使用的時間。

 

四、查詢含列存儲索引的表

 

(1) 創建表sales的列存儲索引

create columnstore index cs_sales_price on sales ([date], price, quantity) on ps([date]);

查看表的存儲信息:

select * from sys.system_internals_partitions p

where p.object_id = object_id('sales')

and index_id = 2;

select au.* from sys.system_internals_allocation_units au

join sys.system_internals_partitions p

on p.partition_id = au.container_id

where p.object_id = object_id('sales')

and index_id = 2;

GO

image

在建有列存儲索引后,表的行數并沒有改變,每個分區依然還是250000行,但頁面數明顯減少,且頁的類型由原來的IN_ROW_DATA變成了LOB_DATA。

(2) 執行查詢語句

select count(*), sum(price*quantity) from sales where date = '20110713'

image

在這個查詢中,一共有363次邏輯讀(等于該表每個分區),CPU時間為93毫秒,占用時間為191毫秒。

 

總結

從兩次查詢的結果來看,無論是邏輯讀的次數和占用時間,在列存儲索引的表中執行查詢明顯要快于聚集索引的表。

而且,從兩種表的存儲結構中可以看到,列存儲索引占用的頁面數量較聚集索引的少,這也印證了列存儲索引的壓縮功能。

備注:通過兩次查詢,我們看到兩者的CPU時間差距不是很大,相反聚集索引占用的時間更小,考慮到列存儲實際上是壓縮存儲,我認為在一張小表或者簡單的表中,對列存儲索引差查詢或許會占用多的CPU時間,因為查詢時需要解壓(我沒有具體驗證過),因此列存儲索引在小表中的優勢主要體現在IO和空間上,實際上列存儲索引的對象往往是含有大數據量的表,數據量越大,其優勢體現越明顯。

說明:準確的說本文并不是原創,文章是從如下地址翻譯過來,然后結合自己的實踐,增加了一些自己的理解。

http://rusanu.com/2011/07/13/how-to-update-a-table-with-a-columnstore-index/


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()