SQL Server In-Memory OLTP Internals for SQL Server 2016

SQL Server In-Memory OLTP Internals for SQL Server 2016
這份白皮書是在上一份《 SQL Server In-Memory OLTP Internals Overview 》基礎上的,不少東西都是同樣的再也不介紹,只介紹不相同的部分。

行和索引存儲

Range索引

Range 索引在 2014 的時候仍是不支持的。 Range index 使用 bwtree 數據結構。 Bwtree btree 同樣有葉子結點和中間節點。最重要的不通點是, bwtree page 指針是一個邏輯的 page id ,而不是物理的 page no PID 表示 mapping table 上的位置, mapping table PID 和物理內存地址關聯。 Bwtree index page 是歷來不更新的,而是增長一個新的,而後讓 mapping table 的相同 PID 指向一個不一樣的物理內存地址。

具體的bwtree的算法能夠看:http://www.cnblogs.com/Amaranthus/p/4375331.htmlhtml

列存儲索引

列存儲索引基本結構

SQL Server 2016 內存優化表支持彙集的列存儲索引。列存儲索引是高複合的索引,並非由行來組織,而是用列來組織的。行被分爲多個組,一個組最多能夠有 2^20 行,而後把某一列的數據放入行組中,不會去管剩下的行。

每一個行組,SQL Server都會使用Vertipaq壓縮算法,從新編碼和排列行組中的順序來打到最有的壓縮效果。每一個行組中的列都是獨立保存的,這個結構稱之爲段(segment),每一個段都是一個LOB,保存在LOB的分配段元中。段是數據讀寫的基本單元,如圖,表示吧一組多個索引列轉化爲幾個段算法

上圖中,表被分爲3個行組,每一個行組有4個段,一共有12個段。數據結構

爲了支持彙集行存儲索引的更新,有2個額外的結構。一個獨立的內部表(deleted rows table DRT)。顧名思義是用來作被刪除行的bitmap,用來保存全部已經刪除的行的rowid。新行加入會被保存在一個堆中,Delta Store。當行數達到必定行數(一般是2^20或者10萬行)SQL Server會吧這些行轉化爲新的壓縮的行組。併發

內存優化表中彙集列存儲索引和內存優化表的非彙集索引是分開保存的,是數據的一個副本。實際上,內存優化表的彙集列存儲索引你能夠理解爲,保存了全部列的非彙集列存儲索引。由於數據是高效壓縮的,所以開銷比較少。由於類存儲索引能夠壓縮到原始數據的10%,所以開銷也只有10%app

全部的類存儲索引段都是在內存中的。爲了恢復的目的,每一個行組在內存優化文件組中都保存成一個獨立的文件類型爲LARGE DATA,在文件中對於某個行組,全部的段都是存放在一塊兒的。SQL Server也維護了一個指針,指向每一個段而且能夠訪問這個段,特別是訪問了部分列的時候。這個部分會在下面CHECKPOINT FILES的時候介紹。新的行會被以列存儲索引保存,可是並不會立刻加入到壓縮行組中,新的行只能使用內存優化表的其餘索引來訪問。如圖,新的行和整個表分開維護的。你能夠認爲這些行是「delta rowgroup」和磁盤表的Delta Store相似,可是這些行是內存優化表的一部分,可是不是技術上的列存儲索引的一部分。其實是課件的delta rowgroup性能

內存優化表中的列存儲索引只能在interop模式下由優化器進行選擇。查詢使用類存儲索引能夠併發而且對於高性能有不少好處。原生編譯過程是不會使用列存儲索引的,而且全部的查詢都不會併發執行。若一個SQL Server 2016的內存優化表有彙集列存儲索引,那麼就有2varheap,一個用於壓縮行組,另一個用來保存新行,可讓SQL Server快速識別哪些行尚未進入壓縮段,這些行也在可見的delta rowgroup中。優化

2個後臺線程每2分鐘執行一次,用來檢查delta rowgroup中的行。注意這些行包含最新插入的,和update的,在內存優化表update就是delete+insert。若是這些行數超過10萬那麼就有下面2個操做:編碼

  1. 行會被複制到一個或者多個行組,每一個段都會被壓縮轉化變成彙集列存儲索引的一部分。
  2. 行會從特定的內存分配器移到常規的內存存儲。

SQL Server並不會是實際統計行數,而是使用評估。沒有行組的行數能夠超過1048576.若是超過有10萬行,那麼就會建立另一個行組。若是小於10萬行那麼這些行仍是會被留在原來的地方。spa

由於最新插入的行會被頻繁更新,或者會被刪除,想要延遲對最新行的壓縮,能夠設置一個等待量。當內存優化表有彙集列存儲索引,那麼就能夠增長一個COMPRESSION_DELAY的參數,指定新行必須在delta rowgroup中呆多久。只有超過參數的行數超過10萬纔會被壓縮到常規的列存儲索引行組中。線程

當行被轉換到壓縮rowgroup以後,全部刪除的行都會被放到Delete Rows表中,和磁盤表的彙集列存儲索引。當行多的時候查詢會很沒有效率。這種狀況下重組列存儲索引並無什麼用,除非刪除而且重建索引。一旦rowgroup90%的行被刪除,剩下的10%會自動被插入到未壓縮的varheap,在內存優化表的Delta rowgroup中。Rowgroup的存儲會被進行垃圾回收。

Note:
前面提到的,若是內存優化表有任何LOB或者溢出列,列存儲索引不能在上面被建立。由於最大的行不能超過8060字節。另一旦內存優化表有一個列存儲索引,就不能使用alter table操做。須要先刪除列存儲索引,alter,而後再建立列存儲索引。

如下是建立內存優化表的腳本,有2個索引,一個range索引一個列存儲索引,而後查詢內存消費。而且設置COMPRESSION_DELAY60分鐘。

USE master ;
GO
SET NOCOUNT ON ;
GO
DROP DATABASE IF EXISTS IMDB ;
GO
CREATE DATABASE IMDB ;
GO
ALTER DATABASE IMDB
    ADD FILEGROUP IMDB_mod_FG
    CONTAINS MEMORY_OPTIMIZED_DATA ;
GO
ALTER DATABASE IMDB
    ADD FILE (    NAME = 'IMDB_mod' ,
                 FILENAME = 'c:\HKData\IMDB_mod'
             )
    TO FILEGROUP IMDB_mod_FG ;
GO
USE IMDB ;
GO
DROP TABLE IF EXISTS dbo . OrderDetailsBig ;
GO
CREATE TABLE dbo . OrderDetailsBig
    (
        OrderID INT NOT NULL ,
        ProductID INT NOT NULL ,
        UnitPrice MONEY NOT NULL ,
        Quantity SMALLINT NOT NULL ,
        Discount REAL NOT NULL INDEX IX_OrderID NONCLUSTERED HASH ( OrderID )
                                   WITH ( BUCKET_COUNT = 20000000 ) ,
        INDEX IX_ProductID NONCLUSTERED ( ProductID ) ,
        CONSTRAINT PK_Order_Details
            PRIMARY KEY NONCLUSTERED
                (
                    OrderID ,
                    ProductID
                ) ,
        INDEX clcsi_OrderDetailsBig CLUSTERED COLUMNSTORE
            WITH ( COMPRESSION_DELAY = 60 )
    )
WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA );
GO
SELECT OBJECT_NAME ( c . object_id ) AS table_name ,
       a . xtp_object_id ,
       a . type_desc ,
       minor_id ,
       memory_consumer_id AS consumer_id ,
       memory_consumer_type_desc AS consumer_type_desc ,
       memory_consumer_desc AS consumer_desc ,
       CONVERT ( NUMERIC ( 10 , 2 ), allocated_bytes / 1024. / 1024 ) AS allocated_MB ,
       CONVERT ( NUMERIC ( 10 , 2 ), used_bytes / 1024. / 1024 ) AS used_MB
FROM    sys . memory_optimized_tables_internal_attributes a
       JOIN sys . dm_db_xtp_memory_consumers c ON a . object_id = c . object_id
                                                AND a . xtp_object_id = c . xtp_object_id
       LEFT JOIN sys . indexes i ON c . object_id = i . object_id

                                  AND c.index_id = i.index_id;

返回的結果:

上圖,顯示錶本身有6行。有一個內存消費者用於壓縮rowgroupHKCS_COMPRESSED消費者),2個用於range index1個用於hash index2個用於表的行存儲(rowstore)(這個和白皮書中說的不一樣),行存儲中其中一個是爲了表中的行,第二個是delta rowgroup。每一個有列存儲索引的表都有4個內部表,xtp_object_id都不相同。每一個內部表爲了訪問方便至少有一個索引用於數據訪問。四個內部表:ROW_GROUP_INFO_TABLE(+hash索引)SEGMENTS_TABLE(+2hash索引)DICTIONARIES_TABLE(+hash 索引),DELETED_ROW_TABLE+hash索引)。(這些內部表的細節白皮書沒有介紹)

除了看內存消費者以外,另一個要檢查的DMVsys.dm_db_column_store_row_group_ physical_stats這個視圖不僅僅是顯示了每一個COMPRESSED而且OPENrowgroup的行數。你能夠用一下腳本查看:

BEGIN TRAN ;
DECLARE @i INT = 0 ;
WHILE ( @i < 10000000 )
    BEGIN
        INSERT INTO dbo . OrderDetailsBig
        VALUES ( @i , @i % 1000000 , @i % 57 , @i % 10 , 0.5 );
        SET @i = @i + 1 ;
        IF ( @i % 264 = 0 )
            BEGIN
                COMMIT TRAN ;
                BEGIN TRAN ;
            END ;
    END ;
COMMIT TRAN ;
SELECT    row_group_id ,
         state_desc ,
         total_rows ,
         trim_reason_desc
FROM      sys . dm_db_column_store_row_group_physical_stats
WHERE     object_id = OBJECT_ID ( 'dbo.OrderDetailsBig' )
ORDER BY row_group_id ;
GO

能夠經過time_reason_desc字段能夠查看爲何rowgroup的行會少於1048576行。若是沒有小於1048576那麼就顯示NO_TRIM。由於OPENrowgroup是不壓縮的,所以爲null,若爲STATS_MISMATCH表示行太少,若爲SPILLOVER表示有移除致使。

相關文章
相關標籤/搜索