In-Memory:在內存中創建臨時表和表變量


在Disk-Base數據庫中,由於臨時表和表變量的數據存儲在tempdb中,如果系統頻繁地創建和更新臨時表和表變量,大量的IO操作集中在tempdb中,tempdb很可能成為系統性能的瓶頸。在SQL Server 2016的內存(Memory-Optimized)數據庫中,如果考慮使用內存優化結構來存儲臨時表,表變量,表值參數的數據,那么將完全消除IO操作的負載消耗,發揮大內存的優勢,大幅提高數據庫的性能。

在SQL Server 2016中,能夠直接創建內存優化的表類型,表變量和表值參數的數據只存儲在內存中;不能直接在內存中創建臨時表,但是,SQL Server提供一個變通方法(Workaround),通過行級安全RLS(Row-Level-Security)控制,指定只有當前Session才能訪問特定的數據,將內存優化表轉換為Session級別的臨時表,間接實現臨時表的局部性和自動清空特性。

一,內存優化表類型(Memory-Optimized Table Type)

內存優化表類型定義的表變量,表值參數能夠大幅提高效率(efficiency),有4個顯著的特點:

  • 數據僅存儲在內存中,在讀寫數據時,不會產生任何的IO消耗,消除了tempdb的競爭和利用率;
  • 必須有一個索引,Hash 或 Nonclustered 都行;每一個內存優化表必須創建一個索引;
  • 只需要指定啟用內存優化:MEMORY_OPTIMIZED = ON,只持久化Schema;
  • 必須先創建表類型,后創建表值變量;

1,創建內存優化表類型

CREATE TYPE dbo.TypeTable  
AS TABLE  
(  
Column1  INT NOT NULL,  
Column2  VARCHAR(10) NOT NULL,
INDEX idxName NONCLUSTERED(Column1)
)  
WITH(MEMORY_OPTIMIZED = ON); 

2,創建內存優化表變量

declare @Table dbo.TypeTable 

二,創建“臨時內存優化表”

在Disk-Base數據庫中,局部臨時表#temp的作用域是session,創建在tempdb中,一旦session生命周期結束,系統自動回收其存儲空間。在SQL Server 2016中,不能直接在tempdb中創建內存優化表。要使用臨時內存優化表,有一個變通的方法,在DB中創建內存優化表,通過Row-Level-Security控制Session能夠訪問的數據行,間接實現Session級別的臨時表。

Step1,創建內存優化表,只持久化Table Schema

CREATE TABLE dbo.SessionTempTable  
(  
    Column1 INT NOT NULL,  
    Column2 NVARCHAR(4000) NULL,  
    SpidFilter SMALLINT NOT NULL DEFAULT (@@spid),  
    INDEX ix_SpidFiler NONCLUSTERED (SpidFilter),  
    --INDEX ix_SpidFilter HASH (SpidFilter) WITH (BUCKET_COUNT = 64),  
    CONSTRAINT CHK_soSessionC_SpidFilter CHECK ( SpidFilter = @@spid ),  
)  
WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY);  
go 

Step2,創建RLS,控制用戶只能訪問當前Session的數據

推薦為Predicate function 和 Security Policy創建單獨的Schema,然后在該Schema下創建Predicate function 和 Security Policy,對於Predicate function必須使用 NATIVE_COMPLIATION選項創建。

create schema rls
authorization dbo;

CREATE FUNCTION rls.fn_SpidFilter
(@SpidFilter smallint)  
RETURNS TABLE  
WITH SCHEMABINDING , NATIVE_COMPILATION  
AS  
RETURN  
    SELECT 1 AS fn_SpidFilter  
    WHERE @SpidFilter = @@spid;  
go

CREATE SECURITY POLICY rls.soSessionC_SpidFilter_Policy  
ADD FILTER PREDICATE rls.fn_SpidFilter(SpidFilter)  
ON dbo.SessionTempTable  
WITH (STATE = ON);  
go 

Step3,使用內存優化臨時表

  • 表名替換:使用 dbo.Temp 代替 #Temp;
  • 不能創建和刪除臨時表
    • 移除代碼“create table #temp”,使用“delete from dbo.Temp”子句取代,將舊數據清空;
    • 移除代碼“drop table #temp”,建議使用 “delete from dbo.Temp” 子句,在當前Session結束前將當前Session產生的數據清空,節省內存空間;

雖然臨時表的使用和管理有點麻煩,但是,這點麻煩和大幅的性能提升來比,微不足道,建議使用內存優化表來代替臨時表,體驗飛一般的速度。

三,維護

1,通過RLS來創建內存臨時表,如何清理臨時表占用的內存空間?

試想出現異常,在當前Session將海量數據插入到臨時表之后,Session異常終止,此時,Session沒有來得及清空(Purge)臨時表中的數據,這些數據仍然駐留在內存中。如果這種異常出現的頻率很高,那么會導致內存優化表消耗大量的系統內存,必須有機制來定期清理臨時表占用的內存空間。

step1,創建一個用戶,RLSAdmin,授予db_owner的權限

--create User
create user RLSAdmin without login;
alter role db_owner
add member RLSAdmin;
go

step2,修改Predicate Function,如果用戶是RLSAdmin,允許訪問Base Table的所有數據行;

--create predicate function
CREATE FUNCTION rls.fn_SpidFilter
(@SpidFilter smallint)  
RETURNS TABLE  
WITH SCHEMABINDING , NATIVE_COMPILATION  
AS  
RETURN  
    SELECT 1 AS fn_SpidFilter  
    WHERE @SpidFilter = @@spid or User_Name()='RLSAdmin';  
go

step3,創建Schedule,定期檢查數據庫中的臨時表,如果發現臨時表中的存在未被清理的無效數據,那么刪除該部分數據,釋放內存。

execute as RLSAdmin;

delete temp
from dbo.SessionTempTable temp
left join sys.dm_exec_sessions s
    on temp.SpidFilter=s.session_id 
        and s<>@@spid 
        and s.session_id>50
where s.session_id is null; 

revert;

該腳本僅僅提供一種思路,在產品環境中,需要多測試,以防錯誤刪除數據。

 

參考文檔:

Memory-Optimized Table Variables

Faster temp table and table variable by using memory optimization

Improving temp table and table variable performance using memory optimization

CREATE TYPE (Transact-SQL)

Indexes for Memory-Optimized Tables


免責聲明!

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



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