zoukankan      html  css  js  c++  java
  • 将一个基于磁盘的表迁移到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:SQL2016TestDB_data.mdf'),

    FILEGROUP [TestDBSampleDB_mod_fg] CONTAINS MEMORY_OPTIMIZED_DATA  DEFAULT

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

    LOG ON

    ( NAME = N'TestDBSampleDB_log', FILENAME = N'C:SQL2016TestDB_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:SQL2016TestDB_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:UsersAGRINB~1AppDataLocalTempSNAGHTML1a68a483.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}DEFAULTDatabases{DB Name}>  

    输入以下命令(用您的目标文件夹路径替换C:Temp。如果您喜欢使用更通用的方法$Env:Temp或$Env:报告输出的路径,然后验证这些命令的PowerShell路径。只需运行$Env:Temp或$Env:PowerShell命令窗口中的路径,您的本地路径将被返回)。清单PowerShell命令示例:

     Save-SqlMigrationReportFolderPathC:Temp”

    注意:如果您需要在单个表上运行迁移报告,那么就展开数据库节点,展开表节点,右键单击表,然后从弹出菜单中选择Start PowerShell。

    文件夹路径将被创建,以防它不存在。迁移检查表报告将为数据库中的所有表和存储过程生成,并且报告将出现在FolderPath指定的位置。因此,报告的文件夹路径将被指定为PowerShell脚本中的FolderPath,以及检查清单执行的数据库名称。在这个例子中,它是C:TempNorthwind.

    检查表报告可以指出,已经超过了内存优化表的一个或多个数据类型限制。但是,这并不意味着不能将表迁移到内存优化的表中。报告指出每一列是否满足了成功的标准,如果没有,则提示如果表对迁移很重要,那么如何纠正问题。例如,一个数据库有一个表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表所带来的好处是值得的

     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. Return a deep copy of the list.
    leetcode算法
    leetcode
    UVA 11076 Add Again
    UVA 10892 LCM Cardinality
    HDU 5961 传递
    UVALive 7040 Color
    2014ACM/ICPC亚洲区广州站题解
    HDU 5136 Yue Fei's Battle
    HDU 5129 Yong Zheng's Death
  • 原文地址:https://www.cnblogs.com/ziqiumeng/p/7705684.html
Copyright © 2011-2022 走看看