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表示有移除致使。

相关文章
相关标签/搜索