SQL Server 2016:內存列存儲索引
SQL Server 2016的一項新特性是可以在“內存優化表(Memory Optimized Table)”上添加“列存儲索引(Columnstore Index)”。要理解這是什么意思,我們應該首先解釋術語列存儲索引和內存優化表。
列存儲索引是一種按照列而不是行組織數據的索引。每個數據塊只存儲一個列的數據,最多包含100萬行。因此,如果數據為5列1000萬行,那么就需要存儲在50個數據塊中。當只查詢部分列時,這種數據組織策略特別有效,因為數據庫不會從磁盤讀取用戶不關心的列。
列存儲索引比表掃描要快得多,但沒有傳統的B樹索引那么快。這特別適合於那種無法預測需要什么索引的即時報表。
內存優化表正如它的名字, 它是一個經過優化並一直駐留在內存中的表。這有許多好處,比如鎖無關寫,但它也有很大的局限性。比如,只允許有8個索引,這對於用於即時查詢的表而言限制太大。
SQL Server 2016部分地彌補了這種限制,它允許那8個索引中的其中一個為列存儲索引。但要遵循如下規則:
•像內存優化表上的其它索引一樣,列存儲索引必須在表創建時定義。
•列存儲索引必須包含基表中的所有列。(在普通表上的列存儲索引不存在這種限制。)
•列存儲索引必須包含基表中的所有行。換言之,它不能是“篩選索引(filtered index)”。
一個與內存優化表相關的特性是創建本地編譯查詢。數據庫使用C編譯器將這些查詢編譯成了機器碼,而不使用SQL Server解釋器。使用列存儲索引的查詢可以使用這個選項,而不用總是通過解釋器運行。
--------------------------------------------------
SQL Server 2016 列存儲索引功能增強
列存儲索引(columnstore index)在SQL Server 2012中已經引入,其帶來性能提升的同時也有很多限制,比如對帶有列存儲索引的表進行INSERT, UPDATE和DELETE時,會遇到如下錯誤提示:

由於這種限制,索引列存儲索引並不太適合在SQL Server 2012 OLTP DB中應用。不過,SQL Server 2016對列存儲索引做了很多改進,其中我覺得最大的變化是可更新的列存儲索引,即可以直接對帶有列存儲索引的表進行INSERT, UPDATE和DELETE,因此,我們可以在SQL Server 2016環境中嘗試應用這以功能,已提升查詢性能。若想具體了解列存儲索引的概念、特征、創建及使用,可參考我之前整理的Blog。
在SQL 2016環境測試的過程中,我發現列存儲索引對於有聚集函數的T-SQL,有很好的性能提升,比如下面這個示例,性能提升約15倍:
JOIT表有1500833筆記錄,復制一份到JOIT_CSI表,2張表的唯一區別是JOIT_CSI有非聚集列存儲索引, 在統計列SERNUM個數的查詢中,可以發現JOIT需要7210ms,而JOIT_CSI只有463ms,性能提升約15倍。感興趣的,可以去發掘其他性能提升的最佳實踐。



--------------------------------------------------
SQL Server 2016新特性: In-Memory OLTP
下面的示例(取自MSDN),展示了如何通過T-SQL創建memory-optimized filegroup、Memory-Optimized Tables,最終可以看到基於磁盤表和內存優化表之間的性能差異,及Native SP帶來的進一步性能提升。
-
創建數據庫,及其內存優化文件組(memory-optimized filegroup)
CREATE DATABASE imoltp;
go
ALTER DATABASE imoltp ADD FILEGROUP [imoltp_mod]
CONTAINS MEMORY_OPTIMIZED_DATA;
ALTER DATABASE imoltp ADD FILE
(name = [imoltp_dir], filename= 'c:\data\imoltp_dir')
TO FILEGROUP imoltp_mod;
go
USE imoltp;
go
2、創建Memory-OptimizedTables, and NCSProc
go
DROP PROCEDURE IF EXISTS ncsp;
DROP TABLE IF EXISTS sql;
DROP TABLE IF EXISTS hash_i;
DROP TABLE IF EXISTS hash_c;
go
CREATE TABLE [dbo].[sql] (
c1 INT NOT NULL PRIMARY KEY,
c2 NCHAR(48) NOT NULL
);
go
CREATE TABLE [dbo].[hash_i] (
c1 INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT=1000000),
c2 NCHAR(48) NOT NULL
) WITH (MEMORY_OPTIMIZED=ON, DURABILITY = SCHEMA_AND_DATA);
go
CREATE TABLE [dbo].[hash_c] (
c1 INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT=1000000),
c2 NCHAR(48) NOT NULL
) WITH (MEMORY_OPTIMIZED=ON, DURABILITY = SCHEMA_AND_DATA);
go
CREATE PROCEDURE ncsp
@rowcount INT,
@c NCHAR(48)
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS
BEGIN ATOMIC
WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
DECLARE @i INT = 1;
WHILE @i <= @rowcount
BEGIN;
INSERT INTO [dbo].[hash_c] VALUES (@i, @c);
SET @i += 1;
END;
END;
Go
3、執行下面的T-SQL,可看到內存優化表的性能狀況
go
SET STATISTICS TIME OFF;
SET NOCOUNT ON;
-- Inserts, one at a time.
DECLARE @starttime DATETIME2 = sysdatetime();
DECLARE @timems INT;
DECLARE @i INT = 1;
DECLARE @rowcount INT = 1000000;
DECLARE @c NCHAR(48) = N'12345678901234567890123456789012345678';
-- Harddrive-based table and interpreted Transact-SQL.
BEGIN TRAN;
WHILE @i <= @rowcount
BEGIN;
INSERT INTO [dbo].[sql] VALUES (@i, @c);
SET @i += 1;
END;
COMMIT;
SET @timems = datediff(ms, @starttime, sysdatetime());
SELECT 'A: Disk-based table and interpreted Transact-SQL: '
+ cast(@timems AS VARCHAR(10)) + ' ms';
-- Interop Hash.
SET @i = 1;
SET @starttime = sysdatetime();
BEGIN TRAN;
WHILE @i <= @rowcount
BEGIN;
INSERT INTO [dbo].[hash_i] VALUES (@i, @c);
SET @i += 1;
END;
COMMIT;
SET @timems = datediff(ms, @starttime, sysdatetime());
SELECT 'B: memory-optimized table with hash index and interpreted Transact-SQL: '
+ cast(@timems as VARCHAR(10)) + ' ms';
-- Compiled Hash.
SET @starttime = sysdatetime();
EXECUTE ncsp @rowcount, @c;
SET @timems = datediff(ms, @starttime, sysdatetime());
SELECT 'C: memory-optimized table with hash index and native SP:'
+ cast(@timems as varchar(10)) + ' ms';
go
DELETE sql;
DELETE hash_i;
DELETE hash_c;
go
執行結果:

--------------------------------------------------
SQL server 2014 內存表特性概述
內存優化表是SQL server2014版本中推出的新特性之一。也是基於create table創建的,只不過是駐留在內存中表。從內存讀取表中的行和將這些行寫入內存。 整個表都駐留在內存中。表數據的另一個副本維護在磁盤上,但僅用於持續性目的。內存中 OLTP 與 SQL Server 集成,以便在所有方面(如開發、部署、可管理性和可支持性)提供無縫體驗。 內存優化表中的行是版本化的。 這意味着表中的每行都可能有多個版本。 所有行版本均維護在同一個表數據結構中。 本文主要描述SQL server 2014內存表的相關特性。
一、基本特性
是一張持續駐留在內存中的表。
使用基於行版本化特性(等同於Oracle MVCC),需要維護每一個行的多個不同版本。
行版本控制用於實現對同一行的並發讀取和寫入,注意此處是並發。
如表tb1有三行:r1、r2 和 r3。 r1 有三個版本,r2 有兩個版本,r3 有四個版本。
同一行的不同版本不必占用連續的內存位置。 不同的行版本可分散到整個表數據結構中。
二、持久化特性
支持事務(ACID)原則的完全持久化表,因為磁盤上會有相應的副本。
使用延遲事務提交寫入磁盤。缺點是丟失已提交但未保存到磁盤的事務。
非持久的內存優化表,不記錄這些表的日志且不在磁盤上保存它們的數據。掉電丟失,等同mysql memory引擎。
三、性能與可伸縮
使用本機編譯的存儲過程獲得最佳性能,解釋性TSQL一般。
對於基於復雜存儲過程實現邏輯,且應用較少調用的的場景,表現優異。
內存表哈希索引高於非聚集索引,內存表非聚集索引性能高於磁盤表非聚集索引。
解決了IO瓶頸,缺點是需要增大內存開銷。
避免了閂鎖與旋轉鎖爭用。
基於樂觀並發控制形式來實現所有事務隔離級別,解決了讀阻塞寫的問題。Oracle是用MVCC及undo來搞定。
四、內存表圖示描述
下圖為內存表調用方式描述圖 
下圖為本文草畫的流程圖 
--------------------------------------------------
今天的世界已經是一個大數據的世界,伴隨數據量爆發式增長的還有硬件的計算能力、不斷增強的CPU計算能力和單位GB內存價格的不斷下降,更好地利用這些強大的資源是大勢所趨。微軟SQL Server 2014提供了眾多激動人心的新功能,但其中最讓人期待的特性之一就是代號為” Hekaton”的內存數據庫了,內存數據庫特性並不是SQL Server的替代,而是適應時代的補充,現在SQL Server具備了將數據表完整存入內存的功能。相比較於Oracle的TimesTen和IBM的SolidDB,Hekaton是完全集成於數據庫引擎並且不需要額外付費的功能。
一、內存數據庫出現的背景
在傳統的數據庫表中,由於磁盤的物理結構限制,OLTP類操作引起的隨機查找會給IO系統帶來高昂的開銷,因此傳統的表和索引的結構設計為使用B-Tree而盡量減少隨機查找,但由於機械磁盤和數據庫鎖的存在,B-Tree結構在處理大並發的OLTP環境時就顯得非常乏力,雖然有很多辦法來解決這類問題,比如說樂觀並發控制、應用程序緩存、分布式架構等,但采用上述方案會導致修改引用程序,這不僅成本高且風險極大。而隨着這些年硬件的發展,現在服務器擁有幾百G內存並不罕見,此外由於硬件NUMA架構的成熟,也消除了多CPU訪問內存的瓶頸問題,因此具備了使用新方式來處理更大並發和數據量的條件,這種新的方式就是使用內存計算技術。
內存的學名叫做Random Access Memory(RAM),因此如其特性一樣,是隨機訪問的,因此對於內存,隨機查找不會引入額外開銷,使用Hash-Index這樣的數據結構更符合內存的特性,而對應並發的隔離方式也對應的變成了MVCC(多版本並發控制),從而消除了鎖引入的性能瓶頸。因此內存數據庫可以在同樣的硬件資源下,處理更多的並發和請求,並且不會被鎖阻塞,在SQL Server 2014中,集成了這個強大的內存數據引擎,如果結合SSD AS Buffer Pool特性,所產生的效果將會非常值得期待。
二、SQL Server內存數據庫的組成和表現形式
在SQL Server 2014的內存數據庫引擎由兩部分組成:內存優化表和本地編譯存儲過程。雖然內存數據庫集成進入關系數據庫引擎,但訪問內存數據庫的方法對於客戶端來說是透明的,這也意味着從客戶端應用程序的角度來看,並不會知道內存數據庫引擎的存在。如圖1所示。
首先內存優化表完全不會再存在鎖的概念(雖然之前的版本有快照隔離這個樂觀並發控制的概念,但快照隔離仍然需要在修改數據的時候加鎖),此外內存優化表Hash-Index結構使得隨機讀寫的速度極大提高,內存優化表還可以設置為使用非持久化日志,既數據既不寫日志,也不會CheckPoint到磁盤,從而極大的降低了IO壓力(適合於ETL中間結果操作,或者其他允許丟失數據的場景),這樣一來也可以消除寫日志引入的性能瓶頸。
下面來創建一個內存優化表:
首先,內存優化表需要數據庫中存在一個特殊的文件組,以供存儲內存優化表的CheckPoint文件,與傳統的mdf或ldf文件不同的是,該文件組是一個目錄而不是一個文件,因為CheckPoint文件只會將新增的數據附加在到新的CheckPoint文件,而不會修改現有的CheckPoint文件,如圖2所示。
下面再來看一下內存優化文件組在磁盤系統的存儲形式,如圖3所示。
創建完內存優化文件組之后,接下來再創建一個內存優化表,如圖4所示。
目前SSMS還不支持UI界面創建內存優化表,因此只能通過T-SQL來創建內存優化表,如圖5所示。
這里創建一個簡單的內存優化表,這里上述設置Hash Bucket為1024,目前SQL Server 2014還不支持動態的Hash Bucket,因此必須手動設置該值。表中設置了Memory_Optimized為ON意味着表是內存優化表,而Durability設置為Schema_And_Data則意味着內存優化表中數據也是持久化,這意味着除非啟用了SQL Server 2014的延遲寫特性,數據不會由於異常情況導致丟失。
當表創建好之后,就可以查詢數據了,值得注意的是,查詢內存優化表需要snapshot隔離等級或者hint,這個隔離等級與快照隔離是不同的,如圖6所示。
三、遷移現有環境到內存優化表
1.內存引擎與高可用特性兼容性
將現有數據庫遷移到內存優化表需要充分考慮現有數據庫系統的環境,從大的方向來說,內存優化表與一部分SQL Server高可用特性不兼容,比如說,數據庫鏡像和復制,但其他諸如AlwaysOn可用性組、日志傳送、備份還原等高可用特性與內存優化表完全兼容。
2.內存引擎與傳統引擎的無縫集成
內存引擎的最小粒度是表,所以僅可以將數據庫中的部分表轉為內存優化表。內存優化表與磁盤表可以無縫的進行交互,從而可以充分利用這兩個引擎的優點。在圖7中將內存優化表和磁盤表進行連接操作。
3.內存引擎與表級別特性的兼容性
內存優化表還與其他一些表級別特性不兼容,比如說遷移到內存優化表的表中不能存在計算列、觸發器等。微軟在SQL Server 2014中提供了一個叫做”內存優化顧問”的工具,該工具可以幫助數據庫人員快速找出不兼容內存優化表的部分並幫助完成遷移。下面來看內存優化顧問。
4.內存優化顧問
不是所有的數據都可以無縫遷移到內存優化表中,如果表上存在一些諸如計算列、外鍵、Default或Check約束的對象時,則無法將現有表遷移到內存優化表中。因此使用微軟提供的內存優化顧問可以使得遷移過程更加平滑。
通過SQL Server 2014的SSMS,在需要驗證的表上右擊,在彈出菜單中選擇“內存優化顧問”來打開內存優化顧問,如圖8所示。
打開內存優化顧問后,可以利用內存優化顧問找到阻止遷移到內存優化表的問題,如圖9所示。
而如果不存在阻止遷移到內存優化表的問題,則可以利用內存優化顧問直接將表和表中的數據遷移到內存優化表中。如圖10和圖11所示。
四、內存數據庫性能測試
有了前文的理論鋪墊后,下面來做一個簡單的性能測試,來比對使用內存優化表結合本地編譯存儲過程與傳統的B-Tree表,在B-Tree表上進行性能測試的結果如圖12所示,在內存優化表加本地編譯存儲過程上進行性能測試的結果如圖13所示。
內存優化表在10倍於傳統表並發的情況下,執行時間卻只有傳統表上執行時間的三分之一,因此不難看出,內存優化表+本地編譯存儲過程有接近幾十倍的性能提升。
五、小結
SQL Server 2014中的內存數據庫是一項可以極大的提升OLTP性能的功能,通過測試可以看出OLTP環境下使用內存數據引擎來說有幾十倍的性能提升,微軟同時還提供了內存優化顧問工具來使得遷移更加平滑。但是內存數據庫的要求也比較嚴格,現有數據庫如果希望能夠享受內存優化表帶來的性能提升,則需要做一些前期鋪墊工作。













