將一個基於磁盤的表遷移到SQL Server中的一個內存優化的表


本文是微軟的譯文,對應的原文是:https://www.red-gate.com/simple-talk/sql/database-administration/migrating-disk-based-table-memory-optimized-table-sql-server/

以前稱為Hekaton的特性,現在是內存中的OLTP,可以提供非常有用的性能提升,您可以仔細地選擇表來進行內存優化。如何將現有表轉換為內存優化的表呢?這個過程不是很簡單,但是內存中的OLTP表所帶來的好處是值得您付出努力的。Alex Grinberg帶着你的基本知識。

內存中的OLTP,也稱為Hekaton,可以顯著提高OLTP(聯機事務處理)數據庫應用程序的性能。它提高了吞吐量,減少了事務處理的延遲,並且可以幫助改善數據暫態的性能,比如臨時表和ETL期間(提取轉移和加載)。內存中的OLTP是一種內存優化的數據庫引擎,它集成到SQL Server引擎中,並針對事務處理進行了優化。

為了使用內存中的OLTP,您將一個重訪問的表定義為內存優化。內存優化的表是完全事務性的、持久的,並且可以使用與基於磁盤的表相同的方式訪問。一個查詢可以同時引用Hekaton內存優化表和基於磁盤的表。事務可以在Hekaton表和基於磁盤的表中更新數據。只引用內存優化表的存儲過程可以被本機編譯為機器代碼,以便進行進一步的性能改進。內存中的OLTP引擎是為一個非常高的會話而設計的

注意:在SQL Server 2014中引入了內存優化的OLTP表。不幸的是,它有許多限制,因此使用起來很不實際。在SQL Server 2016中,內存優化的表得到了極大的改進,並且大大減少了約束。在SQL Server 2016版本中,只有幾個限制仍然存在。本節提供的所有示例和技術只適用於SQL Server 2016版本。

在開始使用內存優化的表之前,必須使用一個memory最優化數據filegroup來創建一個數據庫。這個filegroup用於存儲SQL Server需要的數據和delta文件對來恢復內存優化的表。盡管創建它們的語法與創建常規filestream filegroup的語法幾乎相同,但它也必須指定包含memory優化數據選項。

創建

CREATE DATABASE [TestDB]

ON  PRIMARY

( NAME = N'TestDB_data', FILENAME = N'C:\SQL2016\TestDB_data.mdf'),

FILEGROUP [TestDBSampleDB_mod_fg] CONTAINS MEMORY_OPTIMIZED_DATA  DEFAULT

( NAME = N'TestDB_mod_dir', FILENAME = N'C:\SQL2016\TestDB_mod_dir' , MAXSIZE = UNLIMITED)

LOG ON

( NAME = N'TestDBSampleDB_log', FILENAME = N'C:\SQL2016\TestDB_log.ldf' )

如果希望為現有數據庫啟用memory最優化數據選項,則需要使用memory優化數據選項創建一個filegroup,然后將文件添加到filegoup中。

ALTER DATABASE [TestDB]
ADD FILEGROUP [TestDBSampleDB_mod_fg] CONTAINS MEMORY_OPTIMIZED_DATA;
 
ALTER DATABASE [TestDB]
ADD FILE (NAME='TestDB_mod_dir', FILENAME='C:\SQL2016\TestDB_mod_dir')
TO FILEGROUP [TestDBSampleDB_mod_fg];
 
執行下面的SQL代碼,以驗證是否啟用了一個數據庫內存優化數據選項。
USE TestDB
SELECT g.name, g.type_desc, f.physical_name
FROM sys.filegroups g JOIN sys.database_files f ON g.data_space_id = f.data_space_id
WHERE g.type = 'FX' AND f.type = 2
作為檢查memory優化數據的另一種選擇:打開數據庫屬性,選擇Filegroups,在memory優化數據選項中顯示filegroup名稱。
C:\Users\AGRINB~1\AppData\Local\Temp\SNAGHTML1a68a483.PNG
 
下面是基於磁盤的表和內存優化表之間的差異列表:
 
MEMORY_OPTIMIZED property –當訪問內存優化的表時,不需要從磁盤讀取這些頁面。所有的數據都存儲在內存中。
DURABILITY property –內存優化的表可以是持久的(schemaanddata),也可以是非持久的(模式)。默認情況下,這些表是持久的(schemaanddata),這些持久表也滿足了所有其他事務需求;它們是原子的、孤立的、一致的。一組檢查點文件(數據和delta文件對),這些文件只用於恢復目的,是使用駐留在內存優化的文件組中的操作系統文件創建的,這些文件組跟蹤對持久表中的數據的更改。這些檢查點文件只是應用程序。非持久的,沒有記錄的,只使用一個選項模式。正如該選項所指出的,表模式將是持久的,即使數據不是。在事務處理過程中,這些表不需要任何IO操作,也不需要對這些表的檢查點文件進行任何操作。只有在SQL
Server運行時,數據才可以在內存中使用。
Indexes –沒有在內存優化的表上實現集群索引。索引不是作為傳統的b樹存儲的。內存優化的表支持散列索引,存儲為哈希表,其中有鏈表,將散列的所有行連接到相同的值和“范圍”索引,這些索引是使用特殊的bw樹存儲的。使用bw-tree的范圍索引可以用來快速查找范圍謂詞中的符合條件的行,就像傳統的b-樹一樣,但是它是用樂觀的並發控制設計的,沒有鎖定或鎖定。
 
將基於磁盤的磁盤遷移到內存優化的OLTP表
表是列和行的集合。為了將基於磁盤的表遷移到內存優化的表,有必要了解內存優化的表列數據類型的限制。
不支持以下數據類型: datetimeoffset, geography, geometry, hierarchyid, rowversion, xml, sql_ variant, all User-Defined Types and all legacy LOB data types (including text, ntext, and image)
支持數據類型包括:
bit, tinyint, smallint, int, bigint. Numeric and decimal
  • money and smallmoney
  • float and real
  • date/time types: datetime, smalldatetime, datetime2, date and time
  • char(n), varchar(n), nchar(n), nvarchar(n), sysname, varchar(MAX) and nvarchar(MAX)
  • binary(n), varbinary(n) and varbinary(MAX)
  • Uniqueidentifier

創建內存優化的表的語法與創建基於磁盤的表的語法幾乎完全相同,有一些限制,以及一些必要的擴展。其中的一些區別是:

要設置 MEMORY_OPTIMIZED = ON

The DURABILITY property is set to SCHEMA_AND_DATA or SCHEMA_ONLY (SCHEMA_AND_DATA is default)

The memory-optimized table must have a PRIMARY KEY index. If HASH index is selected for the primary key, then BUCKET_COUNT must be specified.

在內存優化表中只允許包括主鍵在內的8個索引

IDENTITY properties have to be set as seed = 1 and increment = 1 only.

在內存優化的表中不允許計算列(Computed Columns)

不加選擇地創建內存優化的表是一個壞主意。同時,對於OS和其他SQL服務器進程也需要內存,將盡可能多的表遷移到內存優化的表中並不是一個好主意。因為內存優化的表是用開放式並發控制設計的,沒有鎖定或被鎖,選擇轉換的最佳表應該是具有“鎖定和鎖定配置文件”(被檢測為會話“攔截器”的表)的表,其中包括最可寫的表(插入、更新和刪除)和最可讀的表,然而,這個列表對於遷移來說還不是很完整。但是,不應該遷移的表是靜態元數據表;違反了內存優化表的限制的表;表的行數更少。

在SQL Server 2016中,可以使用SQL Server
PowerShell生成一個遷移清單。在對象資源管理器中,右鍵單擊一個數據庫,然后單擊Start PowerShell;驗證以下提示出現,執行以下代碼:PS SQLSERVER:\SQL\{Instance Name}\DEFAULT\Databases\{DB Name}>  

輸入以下命令(用您的目標文件夾路徑替換C:Temp。如果您喜歡使用更通用的方法$Env:Temp或$Env:報告輸出的路徑,然后驗證這些命令的PowerShell路徑。只需運行$Env:Temp或$Env:PowerShell命令窗口中的路徑,您的本地路徑將被返回)。清單PowerShell命令示例:

 Save-SqlMigrationReportFolderPathC:\Temp”

注意:如果您需要在單個表上運行遷移報告,那么就展開數據庫節點,展開表節點,右鍵單擊表,然后從彈出菜單中選擇Start PowerShell。

文件夾路徑將被創建,以防它不存在。遷移檢查表報告將為數據庫中的所有表和存儲過程生成,並且報告將出現在FolderPath指定的位置。因此,報告的文件夾路徑將被指定為PowerShell腳本中的FolderPath,以及檢查清單執行的數據庫名稱。在這個例子中,它是C:\Temp\Northwind.

檢查表報告可以指出,已經超過了內存優化表的一個或多個數據類型限制。但是,這並不意味着不能將表遷移到內存優化的表中。報告指出每一列是否滿足了成功的標准,如果沒有,則提示如果表對遷移很重要,那么如何糾正問題。例如,一個數據庫有一個表testdisk。為了演示的目的,我們將在稍后的報告中看到這個表,其中包含了大量的遷移違規行為。

------------------------------------------------------------------------------------------------------------------------------------------------

--以下表跟該篇文章沒有關系,僅僅是說明如何在內存表上面加索引:

CREATE TABLE [dbo].[Bobcat_AccessLog](  id uniqueidentifier not null default NEWSEQUENTIALID(),  [SN] [varchar](50) NOT NULL,  [StationID] [varchar](60) NOT NULL,  [LogTime] [datetime] NOT NULL,    CONSTRAINT PK_Bobcat_AccessLog_ID PRIMARY KEY NONCLUSTERED HASH  (   ID  )WITH (BUCKET_COUNT = 100) ,  INDEX [IX_AccessLog_LTime]  NONCLUSTERED ([LogTime]),  INDEX [IX_AccessLog_SN]  NONCLUSTERED ([SN],[StationID]) ) WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_only )

------------------------------------------------------------------------------------------------------------------------------------------------

CREATE TABLE dbo.TEST_Disk(
ID  int IDENTITY(10000, 1),
ProductID int NOT NULL,
OrderQty int NOT NULL,
SumOrder as ProductID + OrderQty,
XMLData XML NULL,
Description varchar(1000) SPARSE,
StartDate datetime CONSTRAINT DF_TEST_DiskStart DEFAULT getdate() NOT NULL,
ModifiedDate datetime CONSTRAINT DF_TEST_DiskEnd DEFAULT getdate() NOT NULL,
CONSTRAINT PK_TEST_Disk_ID PRIMARY KEY CLUSTERED
(
ID
)
)
 
遷移清單完成后,我們有以下報告:
  • XMLData column have XML data type
  • SumOrder is Computed Column
  • Description column is SPARSE
  • ID have IDENTITY seed value 10000

 根據這份報告,所有的違規行為都必須得到糾正,或者不能遷移表格。讓我們修正所有這些違規行為:

 XMLData列將被轉換為nv(MAX)數據類型;這就是XML的本質。但是,當應用程序或數據庫沒有實現UNICODE時,可以考慮VARCHAR(MAX)數據類型。

SumOrder是計算的列,其中的值通過公式ProductID列進行計算,並使用OrderQty列(公式ProductID+OrderQty僅為演示目的而創建)。ProductID和OrderQty列都有int數據類型。因此,SumOrder列從ProductID和OrderQty列中繼承了int數據類型(如何糾正計算的列問題,將在“修復計算列問題”小節中解釋)。

Description列,為了糾正這個問題,只需刪除稀疏選項

 ID column IDENTITY:將對應的seed value設置成1

在實現了所有的更正之后,testmemory內存優化表的DDL腳本將會是:

CREATE TABLE dbo.TEST_Memory(
ID  int IDENTITY(1, 1),
ProductID int NOT NULL,
OrderQty int NOT NULL,
SumOrder int NULL,
XMLData nvarchar(MAX) NULL,
Description varchar(1000) NULL,
StartDate datetime CONSTRAINT DF_TEST_MemoryStart DEFAULT getdate() NOT NULL,
ModifiedDate datetime CONSTRAINT DF_TEST_MemoryEnd DEFAULT getdate() NOT NULL,
CONSTRAINT PK_TEST_Memory_ID PRIMARY KEY NONCLUSTERED HASH
(
ID
)WITH (BUCKET_COUNT = 1572864)
) WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA )
 
現在,我們需要將IDENTITY seed設置為10,000,但是,內存優化的表不支持DBCC命令來重置標識,設置IDENTITY_INSERT TEST_Memory ON將為我們完成
-- 1. Insert dummy row
SET IDENTITY_INSERT TEST_Memory ON
INSERT TEST_Memory (ID,ProductID, OrderQty, SumOrder)
SELECT 10000, 1,1,1
SET IDENTITY_INSERT TEST_Memory OFF
 
-- 2. Remove the record
DELETE TEST_Memory WHERE ID = 10000
 
-- 3. Verify Current Identity
SELECT TABLE_NAME, IDENT_SEED(TABLE_NAME) AS Seed, IDENT_CURRENT(TABLE_NAME) AS Current_Identity
FROM INFORMATION_SCHEMA.TABLES
WHERE OBJECTPROPERTY(OBJECT_ID(TABLE_NAME), 'TableHasIdentity') = 1
AND TABLE_NAME = 'TEST_Memory'
 
當所有這三個步驟都被應用時,您將把IDENTITY設置為10,000的所需值。
現在我們將加載一些測試數據。為了准備好大量的行,我們將通過下面的SQL腳本創建一個testdataload表,以便將100萬行裝載到表中。本文將介紹所有的SQL語法
 
;With ZeroToNine (Digit) As
(Select 0 As Digit
        Union All
  Select Digit + 1 From ZeroToNine Where Digit < 9),
    OneMillionRows (Number) As (
        Select
          Number = SixthDigit.Digit  * 100000
                 + FifthDigit.Digit  *  10000
                 + FourthDigit.Digit *   1000
                 + ThirdDigit.Digit  *    100
                 + SecondDigit.Digit *     10
                 + FirstDigit.Digit  *      1
        From
            ZeroToNine As FirstDigit  Cross Join
            ZeroToNine As SecondDigit Cross Join
            ZeroToNine As ThirdDigit  Cross Join
            ZeroToNine As FourthDigit Cross Join
            ZeroToNine As FifthDigit  Cross Join
            ZeroToNine As SixthDigit
)
Select   Number+1 ID,ABS(CHECKSUM(NEWID())) % 50 ProductID, ABS(CHECKSUM(NEWID())) % 55 OrderQty
, (SELECT Number+1 as ProductID,ABS(CHECKSUM(NEWID())) % 50 as OrderQty FROM master.dbo.spt_values as data
WHERE type = 'p' and data.number = v.number % 2047 FOR XML AUTO, ELEMENTS, TYPE  ) XMLData
INTO TEST_DataLoad
From OneMillionRows v
 
當testdataload准備就緒時,讓我們為基於磁盤的和內存優化的表運行一個測試負載。服務器上有32個CPU;512
GB的內存;核聚變(固態)驅動器。但是,內存優化的表執行的速度是磁盤表的兩倍多。
---- Load disk-based table
SET STATISTICS TIME ON;
INSERT [dbo].[TEST_Disk] ( ProductID, OrderQty )
select ProductID, OrderQty from TEST_DataLoad
SET STATISTICS TIME OFF;
SQL Server Execution Times:
   CPU time = 5968 ms,  elapsed time = 6322 ms.
 
---- Load the memory-optimized table
SET STATISTICS TIME ON;
INSERT [dbo].[TEST_Memory](ProductID, OrderQty, SumOrder)
select ProductID, OrderQty,ProductID + OrderQty from TEST_DataLoad
SET STATISTICS TIME OFF;
SQL Server Execution Times:
   CPU time = 2500 ms,  elapsed time = 2561 ms.
 
創建了表之后,您將不得不計划一個新的索引策略,並選擇適合於表使用方式的有效索引。正如前面提到的,內存優化表索引不能作為集群創建,也不能像傳統的b樹那樣存儲:它們是使用特殊的bw樹存儲的。可以在內存優化的表上創建最多8個索引。有兩種類型的內存優化表索引:
 
 
范圍索引針對兩個謂詞進行了優化。如果不確定某個特定列需要多少桶,或者如果您知道您將根據一系列值搜索您的數據,那么您應該考慮創建一個范圍索引,而不是一個散列索引。范圍索引是使用一種名為bw-tree的新數據結構實現的,該結構最初是由Microsoft Research在2011年設想和描述的。樹狀樹是一種鎖和無鎖的b樹的變種。
散列和范圍索引都可以作為獨立的DDL或表內聯DDL創建,例如:
CREATE TABLE dbo.TEST_Memory(
ID  int IDENTITY(1, 1),
ProductID int NOT NULL,
OrderQty int NOT NULL,
SumOrder int NOT NULL,
XMLData nvarchar(MAX) NULL,
Description varchar(1000) NULL,
StartDate datetime CONSTRAINT DF_TEST_MemoryStart DEFAULT getdate() NOT NULL,
ModifiedDate datetime CONSTRAINT DF_TEST_MemoryEnd DEFAULT getdate() NOT NULL,
INDEX [IX_ TEST_Memory_ProductID] NONCLUSTERED HASH ([ProductID]) WITH (BUCKET_COUNT = 1048576),
INDEX [IX_ TEST_Memory_OrderQty] NONCLUSTERED (OrderQty),
CONSTRAINT PK_TEST_Memory_ID PRIMARY KEY NONCLUSTERED HASH
(
ID
)WITH (BUCKET_COUNT = 1572864)
)
散列和范圍索引都可以作為獨立的DDL或表內聯DDL創建,例如:
CREATE TABLE dbo.TEST_Memory(
ID  int IDENTITY(1, 1),
ProductID int NOT NULL,
OrderQty int NOT NULL,
SumOrder int NOT NULL,
XMLData nvarchar(MAX) NULL,
Description varchar(1000) NULL,
StartDate datetime CONSTRAINT DF_TEST_MemoryStart DEFAULT getdate() NOT NULL,
ModifiedDate datetime CONSTRAINT DF_TEST_MemoryEnd DEFAULT getdate() NOT NULL,
INDEX [IX_ TEST_Memory_ProductID] NONCLUSTERED HASH ([ProductID]) WITH (BUCKET_COUNT = 1048576),
INDEX [IX_ TEST_Memory_OrderQty] NONCLUSTERED (OrderQty),
CONSTRAINT PK_TEST_Memory_ID PRIMARY KEY NONCLUSTERED HASH
(
ID
)WITH (BUCKET_COUNT = 1572864)
)
修復計算的列問題
其中一個報告的問題是,內存中的OLTP表不允許計算列。根據表如何使用數據庫或應用程序,解決方案可以從非常簡單到復雜。例如,在創建內存中的OLTP表時,不要包含計算列,或者將列名稱保持為可空,以使用適當的數據類型(為了保留遺留表結構,就像我為testmemory表所做的那樣,只是為了保存遺留表結構)。對於更復雜的解決方案,如創建用戶定義的表類型,在本地編譯的存儲過程中實現它。
避免在內存中OLTP表中計算列問題的一個簡單方法是使用計算公式創建一個視圖。例如:
CREATE VIEW vw_TEST_Memory
AS
SELECT ID
,ProductID
,OrderQty
,SumOrder = ProductID + OrderQty
,XMLData
,StartDate
,ModifiedDate
FROM dbo. TEST_Memory
下一步是為vwtestmemory視圖創建一個替代觸發器,並使用這個視圖插入新的行。例如:
CREATE TRIGGER tr_TEST_Memory ON dbo.vw_TEST_Memory
INSTEAD OF INSERT
AS
INSERT dbo.TEST_Memory ( ProductID, OrderQty, SumOrder, XMLData, StartDate, ModifiedDate)
SELECT ProductID, OrderQty, ProductID + OrderQty, XMLData, StartDate, ModifiedDate
FROM inserted
 

The next step is to create an INSTEAD OF trigger for the vw_TEST_Memory view, and use this view to insert new rows. For example:

 

 

With this option, the SumOrder column will preserve the formula. However, the insert process will lose some speed.

Let’s review more complex scenario: an application code implementing a User-Defined Table Type (UDTT) and the Stored Procedure. To achieve the maximum benefit of an In-Memory table, we need to implement it in the Natively Compiled Stored Procedure. For a disk table, the User-Defined Table Type has following create syntax:

 

 

For an In-Memory table, the UDTT option MEMORY_OPTIMIZED has to be enabled, and the UDTT must have an index. Without those two options, the UDTT can be created, but SQL Server will raise an error when the UDTT is bound to the Natively Compiled Stored Procedure. Here is a sample error massege:

 

 

Create UDTT DDL code:

 

 

Once the UDTT is created, we can create a Natively Compiled Stored Procedure (link to see details about Natively Compiled Stored Procedure https://msdn.microsoft.com/en-us/library/dn133184.aspx). For example:

 

 

To migrate the regular stored procedure to the Natively Compiled Stored Procedure, the following options are required need to be included:

  1. After parameters list (if it exists) add WITH NATIVE_COMPILATION, SCHEMABINDING options
  2. The T-SQL code body surrounded with BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL=snapshot, LANGUAGE=N’us_english’) … END . Where the transaction isolation level can be selected from supported levels (https://msdn.microsoft.com/en-us/library/dn133175.aspx):
    • SNAPSHOT
    • REPEATABLE READ
    • SERIALIZABLE
    • READ COMMITTED

遷移現有DML觸發器

 值得注意的是,一個內存中的OLTP表不支持觸發器。但是,可以使用內存中的OLTP表來代替觸發器。觸發器的遷移規則與存儲過程相同。
  WITH NATIVE_COMPILATION, SCHEMABINDING options must be added after ON [tableName] section
  • The T-SQL code body surrounded with BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL=snapshot, LANGUAGE=N’us_english’) … END

 例如,磁盤表觸發器的代碼

 
CREATE TRIGGER tr_TriggerName
ON TableName
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
/*
The trigger code here
*/
END
 為了將觸發器遷移到內存中的OLTP表,您可以使用下面的代碼:

 

CREATE TRIGGER tr_TriggerName
ON TableName
WITH NATIVE_COMPILATION, SCHEMABINDING
AFTER INSERT, UPDATE, DELETE  
AS
BEGIN ATOMIC
WITH (TRANSACTION ISOLATION LEVEL=snapshot, LANGUAGE=N'us_english')
/*
The trigger code here
*/
END

 沒有DDL觸發器支持內存中的OLTP表。在線書籍向SQL Server dba和開發人員展示了以下信息:

 如果數據庫或服務器有一個或多個在createtable上定義的DDL觸發器或任何包含它的事件組,那么您就不能創建內存優化的表。如果數據庫或服務器有一個或多個DDL觸發器定義在droptable或任何包含它的事件組上,那么您就不能刪除一個內存優化的表。

如果在createprocedure、dropprocedure或任何包含這些事件的事件組中存在一個或多個DDL觸發器,就無法創建本地編譯的存儲過程。

結論

 內存中的OLTP表是在SQL Server 2014中引入的。但是,在內存OLTP表中使用的大量限制實際上是不可能的。值得慶幸的是,在SQL Server
2016中,許多限制都被消除了,這使得我們可以開始在數據庫中實現內存中的OLTP表。正如您在本文中所讀到的,將磁盤表遷移到內存中的OLTP表所需要的過程並不簡單,並且需要進行分析,才能最終決定遷移。但是,內存中的OLTP表所帶來的好處是值得的

 

 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM