In-Memory:内存优化表的事务处理

内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row versioning)建立数据快照,读操做不会对数据加锁,所以,读写操做不会相互阻塞。写操做会申请行级锁,若是两个事务尝试更新同一数据行,SQL Server检测到写-写冲突,产生错误(Error 41302),将后后建立的事务做为失败者,回滚事务的操做。虽然MOT事务使用无锁结构(Lock-Free),不会产生阻塞,可是,访问MOT仍然会产生Wait,一般状况下,等待时间是很是短暂的。数据库

一,MOT使用乐观并发事务控制缓存

1,并发控制策略session

事务的并发控制策略分为乐观策略和悲观策略,SQL Server支持两种并发策略。数据结构

1.1,悲观策略(Pessimistic Approach)并发

悲观策略认为每个数据更新都潜在地存在冲突,为了不数据争用,事务在读取数据时申请共享锁,在更新数据时对数据加互斥锁(Locking)。在冲突发生时,经过加锁阻塞其余事务;其余事务检测到冲突后,等待拥有资源的事务释放互斥锁,其余事务只有获取到资源上的加锁,才能执行读写操做。app

悲观策略主要用于数据争用激烈,而且发生发冲突时用锁保护数据的成本低于回滚事务的成本的环境中。异步

1.2,乐观策略(Optimistic Approach)ide

乐观策略认为执行的数据更新操做不多存在冲突,事务在读取数据时,不锁定数据;在更新数据时,事务只在提交时检查更新的有效性,若是有其余事务更新该数据,将产生更新冲突的错误,那么事务不等待,SQL Server选择一个事务做为失败者,并回滚事务执行的操做。乐观策略效率更高,部分缘由是在大多数状况下,更新冲突不常常发生。当冲突发生时,使用悲观策略,事务须要等待;使用乐观策略,SQL Server使事务失败,回滚事务操做。oop

乐观策略主要用于数据争用不大,而且偶尔回滚事务的成本低于读取数据时锁定数据的成本的环境中。性能

乐观估计效率更高,部分缘由是在大多数状况下,事务冲突不常常发生。当冲突发生时,使用悲观估计法,事务须要等待;使用乐观估计法,SQL Server使事务失败,并回滚事务操做,所以,在发生更新冲突时,须要在客户端进行异常检测,从新执行事务。

2,MOT使用乐观并发控制(Optimistic Concurrency Control,简称OCC)

乐观策略使用行版本化(row versioning)实现并发控制,对于disk-based table,使用tempdb存储行版本数据;对于MOT,在内存中存储行版本数据。

乐观策略认为冲突和失败是不常见的,OCC认为访问MOT的事务不会和其余并发执行的事务产生冲突,任何操做都会执行成功。在访问MOT时,事务不会加锁(Lock或Latch)以保证读操做的隔离性,所以,读写操做互不阻塞,也不会产生等待。一旦产生写-写冲突,SQL Server将选择建立时间晚的事务做为失败者,并回滚该事务操做。

二,MOT支持的事务隔离级别(Transaction Isolation Level)

在In-Memory OLTP系统中,存在两种事务隔离级别,访问硬盘表(Disk-Based Table,简称DBT)的事务,和访问MOT的事务;和传统的事务隔离级别不一样,在一个事务中,存在两个隔离级别。

1,MOT的SNAPSHOT隔离级别

实际上,访问MOT,事务必须处在SNAPSHOT隔离级别下,SNAPSHOT隔离级别指定在读操做执行时,数据在事务级别保持一致性,这意味着,在一个事务中的任何读操做,读取的数据是事务一致性的数据版本。事务一致性是指在事务开始时,建立数据快照:在事务开始时,已经提交的事务更新,可以被该事务识别;在事务开始以后,被其余事务提交的数据更新操做,不会被当前事务识别。

This isolation level specifies that data read by any statement in a transaction will be the transactionally consistent version of the data that existed at the start of the transaction. The transaction can only recognize data modifications that were committed before the start of the transaction. Data modifications made by other transactions after the start of the current transaction are not visible to statements executing in the current transaction. The statements in a transaction get a snapshot of the committed data as it existed at the start of the transaction.

在SQL Server 2016中,有两种方式指定隔离级别:当在解释性TSQL中访问MOT时,使用Table Hint指定SNAPSHOT隔离级别;当在Natively Compiled 存储过程当中访问MOT时,必须在Atomic Block中指定隔离级别为SNAPSHOT。

SNAPSHOT隔离级别只会影响读操做,而写操做不受隔离级别的影响,和其余事务彻底隔离,所以,在Snapshot隔离级别下,当并发事务尝试去更新同一行数据时,并发事务产生更新冲突,抛出错误 41302,41325,或41305,SQL Server选择一个开始时间晚的事务做为失败者,并回滚其操做,产生的Error是:

  • Error 41302. The current transaction attempted to update a record in table X that has been updated since this transaction started. The transaction was aborted. When the current transaction attempts to insert a row with the same primary key value as a row that was inserted by another transaction that committed before the current transaction, there will be a failure to commit with the following error message.
  • Error 41325. The current transaction failed to commit due to a serializable validation failure. If a transaction writes to a table that is dropped before the transaction commits, the transaction terminates with the following error message:
  • Error 41305. The current transaction failed to commit due to a repeatable read validation failure.

2,提高事务的隔离级别

在显式事务(Explicit)模式中,若是默认的事务隔离级别低于SNAPSHOT,那么必须提高事务隔离级别,才能访问MOT,有两种实现方式: 

  • 设置数据库选项 MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT 为ON,该选项的做用是:当事务隔离级别比SNAPSHOT低时(好比,READ COMMITTED or READ UNCOMMITTED),访问MOT的事务都会自动升级到SNAPSHOT隔离级别:
  • ALTER DATABASE CURRENT SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT=ON 
  • 为MOT使用Table Hint:with(snapshot)

所以,在显式事务中,经过解释性(Interpreted)TSQL访问MOT时,必须:

  • 使用Table Hint指定隔离级别:WITH(SNAPSHOT),WITH(REPEATABLEREAD) 和 WITH(SERIALIZABLE) 
  • 设置数据库选项:MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT 为ON

若是发生MSSQLSERVER_41333 错误,说明产生交叉事务隔离错误(CROSS_CONTAINER_ISOLATION_FAILURE),缘由是当前事务的隔离级别过高,解决方法是:将Session-Level的事务隔离级别下降到Read Committed。

3,事务初始化模式(Transaction Initiation Modes)

SQL Server 支持四种事务初始化模式:
  • Autocommit:自动提交模式(默认模式),将单个语句做为一个事务,在语句开始时,隐式开始一个事务;在语句结束时,隐式提交该事务;
    • 在autocommit模式下,访问MOT不须要使用Table Hint指定事务隔离级别;SQL Server自动为MOT应用SNAPSHOT隔离。
  • Explicit:显式模式,使用begin tran 显式开始一个事务,使用commit tran 提交事务,或使用rollback tran 回滚事务。在显式事务中,将事务中的一个,或多个查询语句做为单个事务进行处理;
    • 在显式模式下,访问MOT必须使用SNAPSHOT隔离级别,经过使用Table Hint 指定SNAPSHOT 隔离级别,
    • 或设置数据库选项 MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT 为ON来实现;
  • Implicit:隐式模式,查询语句隐式开始一个事务,必须显式使用commit tran 提交事务,或使用rollback tran回滚事务。使用该模式,必须设置选项:
    SET IMPLICIT_TRANSACTION ON
  • Atomic block:原子块模式,只能用于Natively Compiled SP中。在Atomic block中的全部查询语句都做为单个事务提交或回滚。
    • 在Atomic block中,支持的事务隔离级别是:TRANSACTION ISOLATION LEVEL = { SNAPSHOT | REPEATABLE READ | SERIALIZABLE }  
    • 在Natively Compiled SP中,使用BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, ...) 定义Atomic block事务:
      create procedure schema_name.sp_name
      with native_compilation, schemabinding, execute as owner  
      as
      begin atomic with (transaction isolation level=snapshot, language=N'us_english') 
          statement1;
          statement2;
          ....
      end 
      View Code

三,访问MOT的事务隔离级别

在访问MOT时,最方便的作法是:使用默认的隔离级别 Read Committed,而且设置数据库选项:MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT 为ON。

1, 若是设置Session的隔离级别为Read Uncommitted,事务访问MOT,将产生错误,MOT不支持Read Uncommitted隔离级别

The transaction isolation level 'READ UNCOMMITTED' is not supported with memory optimized tables.

2,若是设置Session的隔离级别为Read Committed:

  • 在Autocommit (单语句事务)模式下,可以访问MOT;
  • 在显式和隐式模式下,不能访问MOT;

在显式事务中,访问MOT,将产生错误:

Accessing memory optimized tables using the READ COMMITTED isolation level is supported only for autocommit transactions. It is not supported for explicit or implicit transactions. Provide a supported isolation level for the memory optimized table using a table hint, such as WITH (SNAPSHOT).

要想在显式事务或隐式事务模式下访问MOT,有两种方式:

  • 使用Table Hint:with(snapshot),该hint只能用于MOT;WITH(REPEATABLEREAD) 和 WITH(SERIALIZABLE) ;
  • 设置数据库选项:MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT 为ON;
    ALTER DATABASE CURRENT SET MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT=ON

3,若是设置Session的隔离级别为Snapshot,没法访问MOT

alter database current set allow_snapshot_isolation on
set transaction isolation level snapshot

访问MOT,将产生错误,MOT 和 Natively Compiled模块在Session的事务隔离为Snapshot时没法访问或建立:

Memory optimized tables and natively compiled modules cannot be accessed or created when the session TRANSACTION ISOLATION LEVEL is set to SNAPSHOT.

4,若是设置Session的隔离级别为Repeatable Read or Serializable时,访问MOT必须使用snapshot隔离级别;

若是Session的隔离级别是Repeatable Read 或 Serializable,那么访问MOT必须使用Table Hint:with(snapshot),在snapshot隔离级别下访问MOT:

The following transactions must access memory optimized tables and natively compiled modules under snapshot isolation: RepeatableRead transactions, Serializable transactions, and transactions that access tables that are not memory optimized in RepeatableRead or Serializable isolation.

综上所述,访问MOT时,须要设置兼容的事务隔离级别:

四,行版本(Row Version)

对硬盘表(Disk-Based Table,简称DBT),Snapshot隔离级别将行版本化的数据存储在tempdb中;在其余隔离级别(例如,Read Committed,Repeatable,Serializable)下,事务经过加锁避免冲突。对于MOT,事务不会加锁,MOT使用多行版本实现事务的并发控制,和Disk-Based Table不一样的是,MOT的版本化数据存储在MOT的内存数据结构中,而不是存储在tempdb中。MOT的每个数据行在内存中可能存在多个版本,每个版本都保存在相同的数据结构中。实际上,MOT的数据结构是Row Version的集合,相同Row的不一样Version不须要存储在连续的内存地址中,每个Row Version是分散地存储在MOT中,每个Row Version使用8B的内存地址来寻址。

 

The table has three rows: r1, r2, and r3. r1 has three versions, r2 has two versions, and r3 has four versions. Note that different versions of the same row do not necessarily occupy consecutive memory locations. The different row versions can be dispersed throughout the table data structure.

1,MOT的多版本(Multi-Versioning)

MOT的同一行数据能够有不一样的版本,所以,并发执行事务可能访问同一行数据的不一样版本,因为在同一时刻,任何数据行都有可能拥有不一样行版本,而且都是有效的;若是根据数据行的不一样版本执行数据更新操做,有可能产生逻辑错误。MOT维护的多行版本(Row-Version)不是存储在tempdb中,而是直接存储在MOT中,做为MOT数据结构的一部分存储在内存中。

2,使用行版本实现Snapshot事务隔离

在单个事务中,访问MOT的全部操做,都使用在事务上一致的快照(Transactionally-Consistent),所谓事务一致性是指在一个事务开始时,建立MOT的数据快照,在该事务活跃期间,事务的全部操做都是基于该数据行快照。若是其余事务修改数据,不会影响该事务读取的数据,例如其余事务将数据由3更新成4,在当前事务中,读操做读到的数据仍然是3;若是在当前事务中尝试修改已被其余事务修改的数据,将产生更新冲突。

访问MOT的事务使用行版本化(row versioning)得到一个事务一致性的数据快照(snapshot),在单个事务中,任何数据操做读取的数据是:

  • 在事务开始时,其余事务已经提交更新的数据版本,可以被当前事务识别;若是其余事务没有提交更新,那么当前事务读取不到更新以后的数据,只能读取到已经存在,事务已经提交更新的数据;
  • 在事务开始以后,其余事务所执行的数据更新不会被当前事务识别;例如:
    • 其余事务插入的新数据不会被当前事务读取到;
    • 其余食物删除的旧数据,当前事务仍然可以读取到;

五,MOT的事务处理

1,交叉事务(cross-container transaction)

交叉事务是指在一个事务中,解释性TSQL语句同时访问MOT和DBT。在交叉事务中,访问MOT的操做和访问DBT(Disk-Based Table)的操做都拥有本身独立的事务序号,就像在一个大的交叉事务下,存在两个单独的子事务,分别用于访问MOT和DBT;在sys.dm_db_xtp_transactions (Transact-SQL)中,访问DBT的事务使用transaction_id标识,访问MOT的事务序号使用xtp_transaction_id标识。

2,访问MOT的事务生命周期

当事务涉及到MOT时,处理事务的生命周期(lifetime)分为三个phase:常规处理,验证阶段,提交处理,如图:

Phase1:常规处理阶段,事务全部的查询和更新操做都在这个阶段执行:

  • 在该阶段,有时会产生更新冲突(Update Conflict),若是当前事务更新的数据行,被其余事务更新,但未提交,那么会产生更新冲突;
    • If any query tries to update a row that has already been updated by an active transaction, an ‘update conflict’ error is generated.
  • 在该阶段,有时会产提交依赖(Commit Dependence),这是由于事务读取到被其余事务更新,可是还没有提交(处于验证或提交阶段);
    • 依赖失败(Dependency failure):若是当前事务依赖的事务提交失败,那么当前事务失败,产生错误 41301;
  • During regular processing, a transaction can read rows written by other transactions that are in the validation or commit phase, but have not yet committed. The rows are visible because the logical end time of the transactions has been assigned at the start of the validation phase.

Phase2:验证阶段,从该阶段开始时,在逻辑上事务已经完成,只是没有提交,其余事务可以看到当前事务更新以后的数据值;

  • 在验证阶段开始时,事务的更新操做已经完成,认为事务逻辑上完成,这使得事务更新对其余事务可见。在该阶段,事务并无提交,SQL Server对事务更新进行验证;
    • The validation phase begins by assigning the end time, thereby marking the transaction as logically complete. This makes all changes of the transaction visible to other transactions, which will take a dependency on this transaction, and will not be allowed to commit until this transaction has successfully committed. In addition, transactions which hold such dependencies are not allowed to return result sets to the client to ensure the client only sees data that has been successfully committed to the database.
  • 在验证阶段,对Repeatable Read 和 Serializable进行验证,,检查数据范围是否有更新。
    • 对于Repeatable Read, 检查行是不是重复读的,若是有数据行被其余事务更新,那么事务提交失败,抛出错误 41305;
      • If any of the rows have been updated or changed, the transaction fails to commit with error 41305 ("The current transaction failed to commit due to a repeatable read validation failure.").
    • 对于Serializable,检查数据范围是有更新,在数据范围中,检查是否有其余事务插入新的数据行,是否有数据行被其余事务删除,若是数据范围变化,那么事务验证失败,抛出错误 41325;
      • The system validates that no phantom rows have been written to the database. The read operations performed by the transaction are evaluated to determine that no new rows were inserted in the scan ranges of these read operations.
    • This phase comprises the repeatable read and serializable validation. For repeatable read validation it checks whether any of the rows read by the transaction has since been updated. For serializable validation it checks whether any row has been inserted into any data range scanned by this transaction. 

Phase3:事务提交处理阶段,事务日志记录到日志文件,事务提交完成,一旦日志写入到Disk,控制权返回到客户端

  • During the commit phase, the changes to durable tables are written to the log, and the log is written to disk. 
  • Once the log record for the transaction has been written to disk, control is returned to the client.
  • After commit processing completes, all dependent transactions are notified that they can commit.

3,等待(Waiting)

访问MOT使用乐观多版本并发控制,不须要加锁,不会产生阻塞,可是,仍然会产生等待(Waiting),可是,永远不可能等待Lock释放,而是等待:

  • 若是一个事务依赖其余事务,那么将产生提交依赖,必须等待其余事务提交成功,当前事务才能提交;
  • 等待事务日志持久化写入到Disk上的事务日志文件(.ldf)中;
  • 提交依赖等待不能避免,一般持续的时间很是短暂;

在执行数据更新操做,须要等待事务日志持久化写入到Disk,虽然等待持续的时间一般很是短暂,可是,能够经过如下两个方式来避免:

  • 使用Delayed Durability;
  • 建立Non-Durable的MOT,使用SCHEMA_ONLY将彻底避免日志写操做,对非持久化表执行的任何更新操做都不会产生任何的日志IO操做;

六,冲突检测和重试逻辑(Conflict Detection and Retry Logic)

1,冲突检测

跟事务相关的错误有两类,这两类错误都会致使事务失败和回滚。大多数状况下,任意一个错误发生,都须要从新执行事务:

  • 并发事务之间产生冲突,分为更新冲突(Update Conflict)和验证失败(Validation Failure):
    • 更新冲突:在同一时刻,有两个并发事务尝试更新同一数据行;错误代码是41302;
      • This error condition occurs if two concurrent transactions attempt to update or delete the same row at the same time. One of the two transactions receives this error message and will need to be retried. 
    • 验证失败:验证事务更新是否知足隔离级别Repeatable Read 和 Serializable的条件,检查数据行是否重复读,检查数据范围是否不变;错误代码是41305,41325;
  • 依赖失败:当前事务依赖其余事务,而依赖的事务提交失败;错误代码是 41301;

2,重试逻辑(Retry Logic)

若是事务失败是因为上述两种状况,那么这个事务应该从新执行,重试逻辑能够实如今Client或Server端,一般推荐在Client实现重试逻辑,由于在Client端执行重试逻辑更高效,并能对事务失败的异常进行复杂处理。

在Server端执行重试逻辑,仅用于在事务失败时,不向Client返回任何结果集,重试逻辑的示例代码以下:

-- Retry logic, in Transact-SQL.  
CREATE PROCEDURE usp_update_salesorder_dates  
AS  
BEGIN  
    DECLARE @retry INT = 10;  

    WHILE (@retry > 0)  
    BEGIN  
        BEGIN TRY  
            BEGIN TRANSACTION;  

            UPDATE dbo.SalesOrder_mo WITH (SNAPSHOT)  
                set OrderDate = GetUtcDate()  
                where CustomerId = 42;  

            UPDATE dbo.SalesOrder_mo WITH (SNAPSHOT)  
                set OrderDate = GetUtcDate()  
                where CustomerId = 43;  

            COMMIT TRANSACTION;  
            SET @retry = 0;  -- //Stops the loop.  
        END TRY  

        BEGIN CATCH  
            SET @retry -= 1;  

            IF (@retry > 0 AND ERROR_NUMBER() in (41302, 41305, 41325, 41301, 41839, 1205)  )  
            BEGIN  
                IF XACT_STATE() = -1  
                    ROLLBACK TRANSACTION;  

                WAITFOR DELAY '00:00:00.001';  
            END  
            ELSE  
            BEGIN  
                print 'Suffered an error for which Retry is inappropriate.';  
                THROW;  
            END  
        END CATCH  

    END -- //While loop  
END; 
View Code

七,事务的懒提交(Lazy Commit)

在SQL Server中,事务提交能够是彻底持久化的(Full Durable,默认),也能够是延迟持久化的(Delayed Durable),也叫作Lazy Commit。

彻底持久化(Full Durable)事务是指:只有当事务日志记录写入到Disk上的事务日志文件(.ldf)以后,事务才提交成功,并将控制权返回到客户端(Client);而延迟持久化(Delayed Durable)事务是指:写事务日志的操做是异步,事务在事务日志写入Disk以前,提交成功,就是说,一旦查询语句执行成功,事务就提交成功,并将控制权返回到Client,可是数据更新可能并无记录到事务日志文件(.ldf)中,直到事务更新的日志被持久化记录到Disk上的事务日志文件以后,数据更新才变成持久,存储数据更新丢失的可能性。

懒提交事务持久化使用异步写模式,将事务日志异步地写入到事务日志文件(.ldf)中。在异步写日志模式下,SQL Server把产生的事务日志先保存在缓存中,直到填满缓存空间,或发生缓存刷新事件,事务日志才被写入到事务日志文件(.ldf)中。懒提交之因此可以减小IO操做的延迟和竞争,是由于有如下三点优点:

  • 事务提交不须要等待写日志操做的完成,一旦查询语句执行完成,就把控制权返回给Client,提升了数据更新的响应速度;
  • 减小并发的事务产生写日志竞争的可能性;
  • 在懒提交模式下,日志被缓存起来,系统一次可以将更大块的日志记录写入到Disk,减小了Disk IO竞争,提升了数据更新的性能;

在SQL Server 2016中,有如下三种方式使用懒提交模式:

1,将数据库设置为懒提交模式

ALTER DATABASE DatabaseName
SET DELAYED_DURABILITY = { DISABLED | ALLOWED | FORCED }  

2,在Natively Compiled SP中,将Atomic Block设置为懒提交

CREATE PROCEDURE <procedureName>WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER    
AS BEGIN ATOMIC WITH     
(    
    DELAYED_DURABILITY = ON,    
    TRANSACTION ISOLATION LEVEL = SNAPSHOT,    
    LANGUAGE = N'English' …    
)    
END

3,在Commit子句中,指定懒提交选项

COMMIT [ { TRAN | TRANSACTION } ] [ transaction_name ] ] [ WITH ( DELAYED_DURABILITY = { OFF | ON } ) ] 

 

参考文档:

Transactions in Memory-Optimized Tables

Introduction to Memory-Optimized Tables

Transactions with Memory-Optimized Tables

Control Transaction Durability

相关文章
相关标签/搜索