In-Memory:內存數據庫

在逝去的2016後半年,因爲項目須要支持數據的快速更新和多用戶的高併發,我試水SQL Server 2016的In-Memory OLTP,建立內存數據庫實現項目的需求,如今項目接近尾聲,系統運行穩定,寫一篇博客,記錄一下使用內存數據庫的經驗。html

從SQL Server 2016開始支持In-Memory OLTP,通俗地講,是內存數據庫,使用內存優化表(Memory-Optimized Table,簡稱MOT)來實現,MOT駐留在內存中。在查詢MOT時,只從內存中讀取數據行,不會產生Disk IO;在更新MOT時,數據的更新直接寫入到內存中。內存優化表可以在硬盤上維護一個數據副本,該副本只用於持久化數據,不用於數據讀寫操做。只有在數據庫恢復時,數據庫引擎纔會從該副本中讀取數據。ios

在內存數據庫中,不是全部的數據都須要存儲在內存中,有些數據仍然可以存儲在Disk上,硬盤表(Disk-Based Table,簡稱DBT)是傳統的表存儲結構,每一個Page是8KB,在查詢和更新DBT時,產生Disk IO操做,將數據從Disk讀取到內存,或者將數據更新異步寫入到Disk中。sql

內存數據庫將本來存儲在Disk上的數據,存儲在內存中,利用內存的高速訪問優點實現數據的快速查詢和更新,可是,內存數據庫,不單單是存儲空間的變化,內存數據庫引擎實現本地編譯模塊(Natively compiled)、交叉事務(Cross-Container Transaction)和查詢互操做(Query Interop):數據庫

  • 本地編譯模塊:若是代碼模塊只訪問MOT,那麼能夠將該模塊定義爲本地編譯模塊,SQL Server直接將TSQL腳本編譯成機器代碼;SQL Server 2016支持本地編譯的模式有:存儲過程(SP),觸發器(Trigger),標量值函數(Scalar Function)或內嵌多語句函數(Inline Multi-Statement Function)。相比於解釋性(Interpreted)TSQL 模塊,機器代碼直接使用內存地址,性能更高。
  • 交叉事務:在解釋性TSQL模塊中,一個事務既能訪問硬盤表,也能訪問內存優化表;實際上,SQL Server建立了兩個事務,一個事務用於訪問硬盤表,一個事務用於訪問內存優化表,在DMV中,分別使用transaction_id 和 xtp_transaction_id 來標識。
  • 查詢互操做:解釋性TSQL腳本可以訪問內存優化表和硬盤表,本地編譯模塊只能訪問內存優化表。

內存數據被整合到SQL Server關係引擎中,使用內存數據庫時,客戶端應用程序甚至感覺不到任何變化,DAL接口也不須要作任何修改。因爲Query Interop的存在,任何解釋性TSQL腳本都能透明地訪問MOT,只是性能沒有本地編譯TSQL腳本性能高。在使用分佈式事務訪問MOT時,必須設置合適的事務隔離級別,推薦使用Read Committed,若是發生MSSQLSERVER_41333 錯誤,說明產生交叉事務隔離錯誤(CROSS_CONTAINER_ISOLATION_FAILURE),緣由是當前事務的隔離級別過高。數組

一,建立內存數據庫

內存優化表的數據必須存儲在包含Memory_Optimized_Data的File Group中,該FileGroup能夠有多個File,每一個File其實是Folder,一個DB只能建立一個包含Memory_Optimized_Data的File Group。併發

step1,建立一個數據庫,建立的Data File的數量最好和CPU內核數量保持一致,存放在不一樣的物理磁盤上;異步

use master 
go 

--Create database
create database Test_MemboryDB
on Primary
(
name=Test_MemoryDB_1,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_1.mdf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_2,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_2.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_3,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_3.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_4,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_4.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_5,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_5.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_6,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_6.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_7,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_7.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_8,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_8.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_9,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_9.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_10,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_10.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_11,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_11.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_12,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_12.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_13,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_13.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_14,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_14.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_15,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_15.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_16,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_16.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_17,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_17.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_18,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_18.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_19,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_19.ndf',
size=5GB,
FileGrowth=1GB
),
(
name=Test_MemoryDB_20,
filename='D:\Program Files\Microsoft SQL Server\Test_MemoryDB_20.ndf',
size=5GB,
FileGrowth=1GB
)
LOG ON 
( 
name = N'Test_MemboryDB_log', 
filename = N'D:\Program Files\Microsoft SQL Server\Test_MemboryDB_log.ldf' , 
size = 10GB , 
filegrowth = 1GB 
)
GO
View Code

step2,爲數據庫建立一個包含內存優化數據的FileGroup,向該FileGroup中添加「File」,其實是目錄(Directory),用於存儲內存優化數據文件,主要是CheckPoint文件,用於還原持久化的內存優化表。分佈式

-- Add File Group from memory-optimized data
alter database [Test_MemboryDB]
add filegroup fg_MemoryOptimizedData
contains MEMORY_OPTIMIZED_DATA;

alter database [Test_MemboryDB]
add file 
(
name=Test_MemboryDBDirectory,
filename='D:\Program Files\Microsoft SQL Server\Test_MemboryDBDirectory'
)
to FILEGROUP fg_MemoryOptimizedData;

文件組屬性:CONTAINS MEMORY_OPTIMIZED_DATA 子句,指定File Group用於存儲內存優化表數據,每一個數據庫只能指定一個存儲內存優化數據的File Group,能夠在該File Group下建立多個Directory,分佈在不一樣的物理Disk上,加快內存優化表數據還原的速度。ide

二,建立內存優化表

內存優化表用於存儲用戶數據,能夠持久化存儲,數據存儲在內存中,同時,在Disk上維護數據的一個副本,經過選項 DURABILITY= SCHEMA_AND_DATA 指定持久化存儲內存優化表;也能夠只存儲在內存中,經過選項DURABILITY= SCHEMA_ONLY指定。在內存優化表上,能夠建立nonclustered index 或nonclustered hash index,每一個內存優化表中至少建立一個Index。函數

--create memory optimized table
create table [dbo].[products]
(
    [ProductID] [bigint] not null,
    [Name] [varchar](64) not null,
    [Price] decimal(10,2) not null,
    [Unit] varchar(16) not null,
    [Description] [varchar](max) null,
    constraint [PK__Products_ProductID] primary key nonclustered hash ([ProductID])with (bucket_count=2000000)
    ,index idx_Products_Price  nonclustered([Price] desc)
    ,index idx_Products_Unit nonclustered hash(Unit) with(bucket_count=40000)
)
with(memory_optimized=on,durability= schema_and_data)
go

1,內存優化:MEMORY_OPTIMIZED

[MEMORY_OPTIMIZED = {ON | OFF}]

默認值是OFF,指定建立的表是硬盤表;設置選項MEMORY_OPTIMIZED爲ON,指定建立的表是內存優化表;

2,持久性:Durability 

DURABILITY = {SCHEMA_ONLY | SCHEMA_AND_DATA}

默認值是SCHEMA_AND_DATA,指定建立的內存優化表是持久化的,這意味着,數據更新會持久化存儲到Disk上,在SQL Server重啓以後,內存優化表的數據能跟根據存儲在Disk上的副本還原。選項 SCHEMA_ONLY 指定建立的內存優化表是非持久化的,這意味着Table Schema是持久化存儲到Disk上,可是,任何數據更新都不會持久化到Disk上,在SQL Server重啓以後,內存優化表的數據會丟失。

3,哈希索引和範圍索引

內存優化表支持Hash Index,屬性 BUCKET_COUNT 指定爲Hash Index建立的bucket的數量,通常hash bucket的數量是數據行的1-2倍,若是沒法估計bucket的數量,請建立範圍索引(NonClustered Index),索引結構是Bw-Tree。

Hash 索引由一個數組和多個數據行鏈組成,每個數組元素叫作一個Hash Bucket,經過內置的Hash函數,將Hash索引的Key映射到Hash Bucket上,例如,若是Hash Index的Key是(Col1,Col2),根據HashFunction(Col1,Col2)返回的Hash Value,將數據行映射到指定的Hash Bucket上;若是多個Key映射到同一個Hash Bucket上,那麼這些Key組成一個鏈。例如:數據表結構是(Name,City),在Name字段上建立Hash Index,Hash值相同的數據行連接成一個單向鏈。

三,建立Natively Compiled SP

本地編譯SP在建立時編譯成機器代碼,整個SP以原子方式執行,這意味着,以SP爲單位,整個SP中的全部操做是一個原子操做,要麼執行成功,要麼執行失敗。

create procedure dbo.usp_GetProduct
    @ProductID bigint not null
with native_compilation, schemabinding, execute as owner
as
begin atomic with (transaction isolation level = snapshot, language = N'US_English')  
select  [ProductID]
      ,[Name]
      ,[Price]
      ,[Unit]
      ,[Description]
from [dbo].[Products]
where ProductID=@ProductID
end
go 

1,在本地編譯SP中,可以爲參數,變量指定Nullability屬性,默認值是NULL

NOT NULL 屬性:不能爲參數或變量指定NULL值,

  • 在本便編譯SP中,爲參數指定NOT NULL屬性,不能爲參數指定NULL值;
  • 在本便編譯SP中,爲變量定義NOT NULL屬性,必須在Declare時初始化變量;

2,本地編譯SP必須包含兩個選項:SCHEMABINDING 和 ATOMIC Block

  • SCHEMABINDING:綁定引用的內存優化表
  • ATOMIC Block:在原子塊中的全部語句,以單個事務運行;在事務成功時,全部語句都提交成功;在事務失敗時,全部語句都回滾。Atomic Bloc保證原子地執行SP,若是SP在其餘事務的上下文中被調用,那麼該SP開始一個新的事務。
    • Atomic blocks guarantee atomic execution of the stored procedure. If the procedure is invoked outside the context of an active transaction, it will start a new transaction, which commits at the end of the atomic block.

使用Atomic Block必須設置兩個選項:

  • TRANSACTION ISOLATION LEVEL:指定Atomic Block開啓事務的隔離級別,一般指定Snapshot隔離級別;
  • LANGUAGE:指定SP上下文的語言;

3,解釋型SP和本地編譯SP的區別

解釋性SP可以訪問硬盤表(Disk-Based Table)和內存優化表(Memory-Optimized Table),其真正的區別是解釋性(Interpreted)SP在第一次執行時編譯,而本地編譯(Natively Compiled)SP是在建立時編譯,而且直接編譯成機器代碼,綁定的是內存地址。

4,延遲持久化

在本地編譯SP中,設置Atoic Block的選項:DELAYED_DURABILITY = ON ,使SP對內存優化表的更新操做,以異步寫事務日誌方式,延遲持久化到Disk,這意味着,若是內存優化表維護了一個Disk-Based 的副本,數據在內存中修改以後,不會當即更新到Disk-Based 的副本中,這有丟失數據的可能性,可是可以減小Disk IO,提升數據更新的性能。

四,使用內存優化的表變量和臨時表

傳統的表變量和臨時表,都使用tempdb存儲臨時數據,而tempdb不是內存數據庫,使用Disk存儲臨時表和表變量的數據,會產生Disk IO和競爭,SQL Server提供了內存優化的表變量,將臨時數據存儲在內存中,詳細信息,請參考個人博客:《In-Memory:在內存中建立臨時表和表變量》。

五,內存數據庫的事務處理

交叉事務是指在一個事務中,解釋性TSQL語句同時訪問內存優化表(Memory-Optimized Table,簡稱MOT)和硬盤表(Disk-Based Table,簡稱DBT)。在交叉事務中,訪問MOT的操做和訪問DBT的操做都擁有本身獨立的事務序號,就像在一個大的交叉事務下,存在兩個單獨的子事務,分別用於訪問MOT和DBT;在sys.dm_db_xtp_transactions (Transact-SQL)中,訪問DBT的事務使用transaction_id標識,訪問MOT的事務序號使用xtp_transaction_id標識。詳細信息,請參考個人博客:《In-Memory:內存優化表的事務處理

 

參考文檔:

In-Memory OLTP (In-Memory Optimization)

In-Memory Overview

相關文章
相關標籤/搜索