在今年的8月份,我寫了篇文章,介紹了我還不推薦使用者使用記憶體OLTP的各個理由。近日很多人告訴我,他們有一些效能的問題,並考慮使用記憶體OLTP來解決它們。
眾所皆知,在SQL Server裡記憶體OLTP是個非常特別的技術,在很多情況下並不適用,但這是在SQL Server 2014裡的首次實現,它有很多限制,我在這篇文章裡已經介紹。
感謝上帝——現在事情已經改變了!幾個星期前,SQL Server 2016的CTP 3版本已經可以公開下載了。在記憶體OLTP領域,微軟做出了巨大的改進。我們來詳細看下。
首先我們建立測試的資料庫:
- USE master
- GO
- -- Create new database
- CREATE DATABASE InMemoryOLTP
- GO
- --Add MEMORY_OPTIMIZED_DATA filegroup to the database.
- ALTER DATABASE InMemoryOLTP
- ADD FILEGROUP InMemoryOLTPFileGroup CONTAINS MEMORY_OPTIMIZED_DATA
- GO
- USE InMemoryOLTP
- GO
- -- Add a new file to the previous created file group
- ALTER DATABASE InMemoryOLTP ADD FILE
- (
- NAME = N'InMemoryOLTPContainer',
- FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\InMemoryOLTPContainer'
- )
- TO FILEGROUP [InMemoryOLTPFileGroup]
- GO
外來鍵約束(Foreign-Key Constraints)
一個最重要的改變,或者我應該說是提升——是支援外來鍵約束!是的,你沒有聽錯:記憶體OLTP現在支援外來鍵約束。在最初的實現裡通常你會期望是支援外來鍵約束,因為這是涉及OLTP情景,但在SQL Server 2014上微軟並不支援。我們來看下面的程式碼:
- -- Create a parent table
- CREATE TABLE Parent
- (
- ParentID INT IDENTITY(1, 1) NOT NULL,
- Col1 CHAR(100) NOT NULL,
- Col2 CHAR(100) NOT NULL,
- Col3 CHAR(100) NOT NULL,
- CONSTRAINT chk_PrimaryKey_Parent PRIMARY KEY NONCLUSTERED HASH (ParentID) WITH (BUCKET_COUNT = 1024)
- )
- WITH (MEMORY_OPTIMIZED = ON)
- GO
- -- Create a child table
- CREATE TABLE Child
- (
- ChildID INT IDENTITY(1, 1) NOT NULL,
- ParentID INT NOT NULL,
- Col1 CHAR(100) NOT NULL,
- Col2 CHAR(100) NOT NULL,
- Col3 CHAR(100) NOT NULL,
- -- Create a FK constraint between both tables
- CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
- REFERENCES Parent (ParentID),
- CONSTRAINT chk_PrimaryKey_Child PRIMARY KEY NONCLUSTERED HASH (ChildID) WITH (BUCKET_COUNT = 1024)
- )
- WITH (MEMORY_OPTIMIZED = ON)
- GO
- -- Insert some records into both tables
- INSERT INTO Parent VALUES ('a', 'a', 'a'), ('b', 'b', 'b'), ('c', 'c', 'c')
- INSERT INTO Child VALUES (1, 'a', 'a', 'a'), (1, 'b', 'b', 'b'), (1, 'c', 'c', 'c')
- GO
這段程式碼在2個表(parent和child表)之間建立了一個簡單的外來鍵約束。另外我也在2個表裡也插入了些測試資料。現在我們對這2個表進行簡單的查詢:
- -- The unnecessary join is removed in the execution plan.
- SELECT c.* FROM Parent p
- JOIN Child c ON c.ParentID = p.ParentID
- GO
當你看查詢本身時,你會看見我只想返回child表的內容。基於外來鍵約束,查詢優化器知道在parent表裡肯定有記錄存在。因此查詢優化器通過移除不需要的表連線來簡化查詢。當你看執行計劃時,你會看到這個簡化真的發生了——非常棒:
在SQL Server的記憶體OLTP裡,這是其中一個最大的改進——支援外來鍵約束。對於外來鍵約束的支援同樣支援本地編譯的儲存過程,如下程式碼所示:
- -- Create a natively compiled Stored Procedure
- CREATE PROCEDURE InMemoryOLTPProcedure
- WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
- AS
- BEGIN
- ATOMIC WITH
- (
- TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = 'us_english'
- )
- SELECT c.ChildID, c.ParentID, c.Col1, c.Col2, c.Col3 FROM dbo.Parent p
- JOIN dbo.Child c ON c.ParentID = p.ParentID
- END
- GO
當你執行本地編譯儲存過程時,同樣你沒有可用的實際執行計劃。你能檢視的只有估計執行計劃……
檢查約束(Check Constraints)
另外非常棒的提升是現在我們支援檢查約束。檢查約束非常重要,因為它告訴查詢優化器你資料長相的更多資訊。基於這些資訊,查詢優化器可以給你更好效能的執行計劃。下面這段程式碼給你展示了一個使用記憶體OLTP如何定義檢查約束的簡單例子。
- -- You can't create a CHECK constraint on a Memory Optimized Table
- CREATE TABLE CheckConstraint
- (
- ID INT IDENTITY(1, 1) PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1024),
- Value INT NOT NULL DEFAULT 1 CONSTRAINT ck_Value CHECK (Value = 1)
- )
- WITH
- (
- MEMORY_OPTIMIZED = ON,
- DURABILITY = SCHEMA_AND_DATA
- )
- GO
從這個表定義你可以看到,這裡我建立了一個簡單的檢查約束,告訴SQL Server在列值裡只儲存1的值。甚至否定檢查在與約束檢查組合也是支援的。下面這個查詢會導致在執行計劃裡有常數掃描運算子。
- -- Contradiction detection works with In-Memory OLTP.
- SELECT * FROM CheckConstraint
- WHERE Value = 0
- GO
在字元列上的索引
回到使用SQL Server 2014的舊時光裡,記憶體OLTP裡不支援字元列上的索引,因為你必須使用BIN2排序。對於大多數人來說這是個專案障礙,因為當你在字元列上進行比較或排序時,使用另一個排序會影響結果。
使用SQL Server 2016,微軟現在已經最終移除了這個限制,現在你可以在字元列上直接建立雜湊或範圍索引,不需要使用BIN2排序。我們來看下面的例子,在SQL Server 2016裡現在是正常執行的。
- -- Creates a table with an index on a character column.
- -- This works now without any problems in SQL Server 2016.
- CREATE TABLE TestTable1
- (
- Col1 CHAR(10) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1024),
- Col2 VARCHAR(100) NOT NULL,
- Col3 VARCHAR(100) NOT NULL
- ) WITH
- (
- MEMORY_OPTIMIZED = ON,
- DURABILITY = SCHEMA_AND_DATA
- )
- GO
非常炫——太棒了!
架構和桶數改變
在SQL Server 2014裡這個真的是很糟也很爛:在記憶體優化表上架構修改是不完全不支援的。你需要刪掉並重建你的表,即使你只想增加一個索引或修改現存的索引。抱歉,這是我絕不推薦使用者使用記憶體OLTP的一個最主要原因。即使對於雜湊索引修改桶數,你也要刪除並重建你的表。
使用SQL Server 2016,生活現在好多了,一切變得簡單了。首先你可以使用簡單的ALTER INDEX REBUILD語句來修改現存索引的桶數。要留意的是你需要有表大小的2倍記憶體。下列程式碼顯示這個提升:
- -- We can change now the bucket count without dropping the table
- ALTER TABLE Parent
- ALTER INDEX chk_PrimaryKey_Parent
- REBUILD WITH (BUCKET_COUNT = 1048576)
- GO
另外在你建立記憶體優化表後,現在你可以修改你的表,甚至建立索引。我還沒嘗試所有的可能修改,但下面的程式碼可以給你一個你期望SQL Server 2016的一個大致想法。
- -- Creates a table with an index on a character column.
- -- This works now without any problems in SQL Server 2016.
- CREATE TABLE TestTable1
- (
- Col1 CHAR(10) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1024),
- Col2 VARCHAR(100) NOT NULL,
- Col3 VARCHAR(100) NOT NULL
- ) WITH
- (
- MEMORY_OPTIMIZED = ON,
- DURABILITY = SCHEMA_AND_DATA
- )
- GO
- -- Create a new system-versioned table
- CREATE TABLE Persons
- (
- ID INT IDENTITY(1, 1) PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1024),
- FirstName VARCHAR(100) NOT NULL,
- LastName VARCHAR(100) NOT NULL,
- City VARCHAR(100) NOT NULL,
- -- Needed for System-Versioned Tables
- StartDate DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
- EndDate DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
- PERIOD FOR SYSTEM_TIME (StartDate, EndDate)
- )
- WITH
- (
- -- Needed for System-Versioned Tables
- SYSTEM_VERSIONING = ON
- (
- -- Name of the history table (optional)
- HISTORY_TABLE = dbo.PersonHistory
- ),
- MEMORY_OPTIMIZED = ON,
- DURABILITY = SCHEMA_AND_DATA
- )
- GO
- -- The current table is a Memory-Optimized Table, the history table
- -- is a traditional Disk-Based Table.
- SELECT is_memory_optimized, * FROM sys.tables
- WHERE object_id IN(OBJECT_ID('Persons'), OBJECT_ID('PersonHistory'))
- GO
- -- Create a parent table
- CREATE TABLE LetsTrySchemaChanges
- (
- ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1024),
- Col1 CHAR(100) NOT NULL,
- Col2 CHAR(100) NOT NULL,
- Col3 CHAR(100) NOT NULL
- )
- WITH (MEMORY_OPTIMIZED = ON)
- GO
- -- Schema Changes are now also supported on Memory-Optimized tables.
- ALTER TABLE LetsTrySchemaChanges ADD Test CHAR(100) NULL
- GO
- -- The creation of an index after the table creation is not supported
- CREATE NONCLUSTERED HASH INDEX idx_Test ON LetsTrySchemaChanges(Col3)
- WITH (BUCKET_COUNT = 1024)
- GO
小結
從這個文章裡,你可以看到在SQL Server 2016裡,記憶體OLTP已經徹底翻新了。現在如果有人問我它們是否應該使用記憶體中OLTP,我會說是的——如要你有對應的問題,而且這些問題使用SQL Server的傳統關係引擎不能解決的。
除了我在這篇文章裡提到的提升外,SQL Server 2016裡的記憶體OLTP可以給你的其他的提升,我會在接下來的文章裡談到。
對這些提升你有啥想法?請盡情留言。
參考文章:
http://www.sqlpassion.at/archive/2015/11/16/why-i-now-eventually-recommend-in-memory-oltp/