SQL Server 数据库级别默认的事务隔离级别是Read,

作者: www.9159.com  发布:2019-11-21

我们在Oracle和MySQL数据库中已经对一致性读的概念比较熟悉了,但是在SQL Server中却鲜少提及,但SQL Server自2005版本以来其实也实现了一致性读,几乎所有关系型数据库产品的一致性读都是通过MVCC机制实现的,说白了就是修改之前先把数据存一份儿。

隔离级别定义事务处理数据读取操作的隔离程度,在SQL Server中,隔离级别只会影响读操作申请的共享锁(Shared Lock),而不会影响写操作申请的互斥锁(Exclusive Lock),隔离级别控制读操作的行为:

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

MVCC的意思就是Multi-Version Concurrency Control--多版本并发控制,这里的version就是指的数据的前镜像,多了一份数据自然就减少了争用,增加了并发。

  • 在读数据时是否使用共享锁,申请何种类型的锁;
  • 事务持有读锁的时间;
  • 读操作引用被其他事务更新,但尚未提交的数据行时,控制读操作的行为:
    • 被阻塞,等待其他事务释放互斥锁;
    • 获取更新之前的数据值,从tempdb中读取行版本,该行版本在事务开始时已经提交;Retrieves the committed version of the row that existed at the time the statement or transaction started.
    • 读没有提交的数据,获取更新之后的数据值;

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

SQL Server数据库在Read committed snapshot和snapshot隔离级别下通过MVCC机制实现了一致性读,其机制如下:

在执行写操作时,事务总是持有互斥锁,直到事务结束才释放,互斥锁不受事务隔离级别的影响。在SQL Server中,互斥锁和任意锁都不兼容,在同一时间,同一个数据行上,只能有一个事务持有互斥锁,就是说,写操作是顺序进行的,完全隔离的,不能并发执行。隔离和并发,此消彼长。

1,并发控制策略

MVCC一致性读步骤:

Choosing a transaction isolation level does not affect the locks acquired to protect data modifications. A transaction always gets an exclusive lock on any data it modifies, and holds that lock until the transaction completes, regardless of the isolation level set for that transaction. 

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

  • A new transaction is initiated, and it is assigned a transaction sequence number.
  • 每个事务开始时被分配一个事务序列号Transaction Sequence Number(TSN)。
  • The Database Engine reads a row within the transaction and retrieves the row version from tempdb whose sequence number is closest to, and lower than, the transaction sequence number.

  • 读事务通过数据库引擎在tempdb中检索TSN小于当前读事务TSN的行(这些行都带有各自事务的TSN信息)。

  • The Database Engine checks to see if the transaction sequence number is not in the list of transaction sequence numbers of the uncommitted transactions active when the snapshot transaction started.

  • 数据库引擎检查:步骤2中找到的行的TSN是否在未提交事务列表中,此列表中的未提交事务都是读事务启动时就已经处于active状态的事务。
  • The transaction reads the version of the row from tempdb that was current as of the start of the transaction. It will not see new rows inserted after the transaction was started because those sequence number values will be higher than the value of the transaction sequence number.

  • 当前事务只读取:离当前读事务TSN最近,且小于当前读事务TSN的行版本。这意味只读取最新的已提交数据。

  • The current transaction will see rows that were deleted after the transaction began, because there will be a row version in tempdb with a lower sequence number value.

  • 当前事务永远不会读取到其他事物的未提交修改,因为在tempdb中总会存在修改行的行版本(即前镜像)。

事务的隔离级别共有5个,使用SET命令修改Session-Level的隔离级别,使用DBCC UserOptions 查看当前Session的隔离级别:

1.1,悲观策略(Pessimistic Approach)**

需要考虑的一种情形是:

图片 1图片 2

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

在读事务开始后有其他DML事务修改、插入、删除数据并在读到数据之前就提交,那么读事务会不会读取到这些更新?

SET TRANSACTION ISOLATION LEVEL
     READ UNCOMMITTED
    | READ COMMITTED
    | REPEATABLE READ
    | SNAPSHOT
    | SERIALIZABLE

DBCC UserOptions

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

由于条件所限,还未进行测试,但是猜测如下:

View Code

1.2,乐观策略(Optimistic Approach)

不会读取到更新,因为这些行版本会在tempdb中保留一段时间(同一数据可能存在多个行版本),只要检测到这些行版本的TSN大于当前读事务的TSN那么就意味着这些行在读事务开启后经过了修改,读事务只要找到这些行版本中TSN最新的一个就可以。

一,事务的隔离级别

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

如果猜测正确那么也意味着tempdb中的行版本(即行的前镜像)是有保留时间的,类似于Oracle的Undo_retention。

SQL Server 数据库级别默认的事务隔离级别是Read Committed,用户不能修改Database-Level默认的隔离级别,但是,用户能够修改Session-Level默认的事务隔离级别。Session-Level默认的事务隔离级别是Read Committed,该隔离级别受到数据库选项 READ_COMMITTED_SNAPSHOT 的影响,决定Read Committed隔离级别是使用行版本控制事务的读操作,还是使用加共享锁来控制事务的读操作,在默认的Read Committed隔离级别下:

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

如果猜测与实践不符,那么就意味着tempdb中的行版本会在事务提交后立马消失,读事务会读取到事务开始后的一部分提交的修改。

  • 如果设置选项READ_COMMITTED_SNAPSHOT为OFF,那么事务在执行读操作时申请共享锁,阻塞其他事务的写操作;
  • 如果设置选项READ_COMMITTED_SNAPSHOT为ON,那么事务在执行读操作时使用Row Versioning,不会申请共享锁,不会阻塞其他事务的写操作;

乐观估计效率更高,部分原因是在大多数情况下,事务冲突不经常发生。当冲突发生时,使用悲观估计法,事务需要等待;使用乐观估计法,SQL Server使事务失败,并回滚事务操作,因此,在发生更新冲突时,需要在客户端进行异常检测,重新执行事务。

 

在任何隔离级别下,事务在执行写操作时都申请互斥锁(exclusive lock),持有互斥锁直到事务结束,互斥锁不受隔离级别的控制;而共享锁(Shared Lock)受到隔离级别的控制,隔离级别影响Shared Lock的申请和释放:

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

本文主要观点来自官网博客:

  • 在 Read Uncommitted隔离级别下,读操作不会申请Shared Lock;
  • 在 Read Committed(不使用row-versioning),Repeatable Read 和 Serializable隔离级别下,都会申请Shared Lock;
  • 在 Read Committed(不使用row-versioning) 隔离级别下,在读操作执行时,申请和持有Share Lock;一旦读操作完成,释放Shared Lock;
  • 在 Repeatable Read 和 Serializable隔离级别下,事务会持有Shared Lock,直到事务结束(提交或回滚);
  • 在Serializable隔离级别下,事务会持有范围Shared Lock(Range Lock),锁定一个范围,在事务活跃期间,其他事务不允许在该范围中进行更新(Insert 或 delete)操作;

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

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/snapshot-isolation-in-sql-server#how-snapshot-isolation-and-row-versioning-work 

SQL Server支持使用Row Version的隔离级别,事务的读操作只申请SCH-S 表级锁,不会申请Page 锁和Row 锁,事务的修改操作仍然申请锁

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

关于Read committed snapshot和snapshot隔离级别,参考

  • 当数据库选项 READ_COMMITTED_SNAPSHOT 设置为ON,Read Committed隔离级别使用Row Version提供语句级别(Statement-Level)的读一致性;
    • When a transaction runs at the read committed isolation level, all statements see a snapshot of data as it exists at the start of the statement. 
  • Snapshot隔离级别使用Row Version 提供事务级别(Transaction-Level)的读一致性。在当前事务开始时,任何读操作,都基于相同的数据库snapshot。当读取被其他事务修改的数据行时,从tempdb中获取行版本数据。使用Snapshot隔离级别时,必须设置数据库选项ALLOW_SNAPSHOT_ISOLATION为ON;
    • When reading rows modified by another transaction, they retrieve the version of the row that existed when the transaction started.
  • 在snapshot 和 read committed snpshot隔离级别下,事务读取的数据都是已提交的;
  • 注意语句级别的读一致性和事务级别的读一致性是snapshot 和 read committed snpshot 最大的区别:
    • 事务级别的读一致性是指:在事务开始,到事务提交期间,该事务持有数据的一个快照。如果在该事务活动期间,其他事务更新表数据,该事务只会读取快照数据,不会读取到被其他事务更新的数据值;
    • 语句级别的读一致性是指:单个语句(single statement)看到的数据是一致性的;在当前事务活动期间,事务中的语句能够读取到被其他事务提交更新的数据值;例如,在语句stmt1执行时,事务没有提交更新,stmt1看到Reader1的值是2;当语句stmt2执行时,事务提交更新,stmt2看到Reader2的值是3;

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

二,使用行版本(Row Version)的隔离级别

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

在默认的隔离级别Read Commited下,在执行读操作时,事务申请shared lock,读写操作相互阻塞。在隔离级别Read Uncommitted下,事务不会申请shared lock,因此读操作不会阻塞写操作,但是读操作可能会读到脏数据。脏数据是指被其它尚未提交的事务修改之后的数据值,不是指更新之前的数据值。

1,MOT的SNAPSHOT隔离级别

行版本是指存储在tempdb中,含有数据行和TSN的数据。数据表的一个Data Row,可以有多个Row Version。修改操作发生时,SQL Server 创建一个Row Version,将Original Row复制到Row Version,并将当前事务的TSN也存储在Row Version中。因此,Row Version存储的是修改之前的数据值。

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

SQL Server 提供Snapshot隔离级别,用于读取修改之前的数据值。在Snapshot隔离级别下,事务在修改任何数据之前,先将原始数据行复制到tempdb,创建数据行的一个原始版本(Row Version),注意,SQL Server只会复制被修改的数据行,对于未修改的数据行,不会保存行版本数据。后续其他事务的读操作都去读该复制的行版本。在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.

1,启用Snapshot隔离级别

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

设置数据库选项 ALLOW_SNAPSHOT_ISOLATION 为 ON,没有改变Session-Level的事务隔离级别,需要修改Session-Level的事务隔离级别为SNAPSHOT,才能使用行版本数据

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

alter database current
set allow_snapshot_isolation on;
  • 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.

在使用Snapshot隔离级别时,必须将当前Session的隔离级别设置为Snapshot,只有这样,当前事务才能访问Row Versioning的数据:

2,提升事务的隔离级别

SET TRANSACTION ISOLATION LEVEL SNAPSHOT

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

2,数据库选项READ_COMMITTED_SNAPSHOT(简称RCS)

  • 设置数据库选项 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)

在默认的隔离级别Read Committed下,使事务能够访问Row Versioning数据,需要将数据库选项READ_COMMITTED_SNAPSHOT设置为ON:

因此,在显式事务中,通过解释性(Interpreted)TSQL访问MOT时,必须:

alter database current
set allow_snapshot_isolation on;

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

前提是必须设置数据库选项ALLOW_SNAPSHOT_ISOLATION为ON;一旦启用RCS选项,在默认的Read Committed 隔离级别中,事务访问版本化的数据行。在RCS隔离级别下,事务有两个特性:

如果发生MSSQLSERVER_41333 错误,说明产生交叉事务隔离错误(CROSS_CONTAINER_ISOLATION_FAILURE),原因是当前事务的隔离级别太高,解决方法是:将Session-Level的事务隔离级别降低到Read Committed。

  • 事务使用行版本(Row version)代替加锁,读操作不会阻塞其他事务的写操作;
  • RCS隔离级别保证语句级别的事务一致性,查询语句只能读取在该语句执行时已经提交的数据,如果在该语句执行时数据更新尚未提交,该语句读取不到;

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

3,READ COMMITTED Snapshot隔离级别

SQL Server 支持四种事务初始化模式:

在Read Committed 隔离级别下,事务不能读取被其他事务修改,但尚未提交的数据,即只能读取已提交更新的数据,READ COMMITTED隔离级别的行为受到数据库选项:READ_COMMITTED_SNAPSHOT的影响:

  • 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事务: 图片 3图片 4

      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

  • 如果设置RCS选项为OFF(默认设置),数据库引擎使用Shared Lock阻止其他事务修改当前事务正在读取的数据;当读取被其他事务修改,但尚未提交更新的数据行时,该读操作将被阻塞;

    • If READ_COMMITTED_SNAPSHOT is set to OFF (the default), the Database Engine uses shared locks to prevent other transactions from modifying rows while the current transaction is running a read operation. The shared locks also block the statement from reading rows modified by other transactions until the other transaction is completed.
  • 如果设置RCS选项为ON,数据库引擎使用行版本化(Row Versioning)的数据实现语句级别的一致性,不会阻塞其他事务的写操作,但只能读取已提交更新的数据

    • If READ_COMMITTED_SNAPSHOT is set to ON, the Database Engine uses row versioning to present each statement with a transactionally consistent snapshot of the data as it existed at the start of the statement. Locks are not used to protect the data from updates by other transactions.

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

三,启用快照隔离级别

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

1,使用snapshot 隔离级别

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

step1,设置数据库选项

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

ALTER DATABASE CURRENT SET SINGLE_USER 
WITH ROLLBACK IMMEDIATE; 
ALTER DATABASE CURRENT SET ALLOW_SNAPSHOT_ISOLATION ON; 
--ALTER DATABASE CURRENT SET READ_COMMITTED_SNAPSHOT OFF; 
ALTER DATABASE CURRENT SET MULTI_USER;

2,如果设置Session的隔离级别为Read Committed:

step2,修改Session-Level的隔离级别为snapshot

  • 在Autocommit (单语句事务)模式下,能够访问MOT;
  • 在显式和隐式模式下,不能访问MOT;
set transaction isolation level snapshot

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

2,使用Read_Committed_Snapshot隔离级别

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).

ALTER DATABASE CURRENT SET SINGLE_USER 
WITH ROLLBACK IMMEDIATE; 
ALTER DATABASE CURRENT SET ALLOW_SNAPSHOT_ISOLATION ON; 
ALTER DATABASE CURRENT SET READ_COMMITTED_SNAPSHOT ON; 
ALTER DATABASE CURRENT SET MULTI_USER;

要想在显式事务或隐式事务模式下访问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
    

 图片 5

3,如果设置Session的隔离级别为Snapshot,无法访问MOT

snapshot隔离级别不会阻塞其他事务的写操作,该隔离级别忽略数据的修改操作,只读取row versioning的数据,就是说,读取到的是数据修改之前的版本,当snapshot事务尝试修改由其他事务修改的数据时,产生更新冲突,写操作异常终止。

alter database current set allow_snapshot_isolation on
set transaction isolation level snapshot

read committed 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.

五,Snapshot隔离级别(翻译MSDN)

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

在SNAPSHOT隔离级别下,任何写操作都会将更新之前的数据行保存到tempdb中,读取操作要么从Original Database的数据表中读取数据,要么从tempdb中读取行版本数据。Snapshot隔离级别指定:在一个事务中,任何语句读取的数据,是事务一致性的版本。事务一致性是指在事务开始时,在表级别创建数据快照,只能识别其他事务已提交的数据更新。在事务开始之后,当前事务不会识别其他事务执行的数据更新。Sanpshot隔离级别实现事务级别的数据一致性。SQL Server 使用tempdb来存储行版本化(row versioning)的数据,如果数据更新较多,存储的行版本太多,会导致tempdb成为系统瓶颈。

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

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 effect is as if the statements in a transaction get a snapshot of the committed data as it existed at the start of the transaction.

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.

1,在Snapshot隔离级别下,更新操作创建Row Version

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

一旦启用Snapshot隔离级别,在事务中执行更新操作时,SQL Server将被更新的数据行的原始版本存储在tempdb中,即在tempdb中保存数据行的Original data,因此,读取行版本的数据,都只能读取到数据行被更新之前的值。每一个事务都拥有一个唯一的,递增的顺序号,记作TSN(Transaction Sequence Number),TSN能够唯一标识一个事务,每一个行版本都存储一个TSN,标识创建该行版本的事务。

图片 6

Once snapshot isolation is enabled, updated row versions for each transaction are maintained in tempdb. A unique transaction sequence number identifies each transaction, and these unique numbers are recorded for each row version.

四,行版本(Row Version)

2,Snapshot隔离实现事务一致性

对硬盘表(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的内存地址来寻址。

Snapshot隔离级别实现事务级别的数据一致性,这意味着,在单个事务中的所有查询语句,看到的是相同版本的数据。在Snapshot隔离级别下,事务在读取数据不需要加行级锁或页级锁,读写操作互不阻塞。

 图片 7

The term "snapshot" reflects the fact that all queries in the transaction see the same version, or snapshot, of the database, based on the state of the database at the moment in time when the transaction begins. No locks are acquired on the underlying data rows or data pages in a snapshot transaction, which permits other transactions to execute without being blocked by a prior uncompleted transaction. Transactions that modify data do not block transactions that read data, and transactions that read data do not block transactions that write data, as they normally would under the default READ COMMITTED isolation level in SQL Server. This non-blocking behavior also significantly reduces the likelihood of deadlocks for complex transactions.

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.

3,Snapshot 使用乐观并发模式

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

Snapshot隔离级别使用乐观并发模式,如果一个Snapshot 事务尝试去提交数据行的更新,但是该数据行已经被其他事务修改,并且修改的时间早于当前事务开始的时间,那么SQL Server将当前事务作为失败者,并回滚其事务操作。乐观并发模式用于冲突较少的环境中,如果Application在更新数据时经常发生冲突,Snapshot隔离级别可能不是最好的选择。

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

Snapshot isolation uses an optimistic concurrency model. If a snapshot transaction attempts to commit modifications to data that has changed since the transaction began, the transaction will roll back and an error will be raised. 

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

4,Snapshot 隔离和 Row Version的工作模式

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

当启用Snapshot隔离级别时,每一个更新数据的操作都会在tempdb中存储该行的原始副本,术语叫作行版本(RowVersion),SQL Server为每个行版本添加事务的TSN,该TSN能够唯一标识更新操作所在的事务。读操作在读数据时,按照以下顺序进行:

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

  • 创建一个新的事务,为其分配TSN,一个唯一,递增的序号;
  • snapshot事务从数据表中读取数据行,从tempdb中读取行版本(row version),该行版本的TSN最接近当前事务的TSN,但比当前事务的TSN小;
  • 在创建Snapshot时,从已提交的事务中获取行版本数据,如果行版本数据标识的事务尚未提交,那么从更早的事务中获取已提交更新的数据;
  • 事务从tempdb中读取行版本数据,事务不会看到新插入的数据,因为插入数据的TSN比当前事务的TSN大;
  • 事务能够看到被其他事务删除的数据,前提是删除数据的事务的TSN比当前事务的TSN大,这是因为其他事务将行版本保存到tempdb中,当前事务从tempdb中读取行版本数据;
  • 在事务开始时,其他事务已经提交更新的数据版本,能够被当前事务识别;如果其他事务没有提交更新,那么当前事务读取不到更新之后的数据,只能读取到已经存在,事务已经提交更新的数据;
  • 在事务开始之后,其他事务所执行的数据更新不会被当前事务识别;例如:
    • 其他事务插入的新数据不会被当前事务读取到;
    • 其他食物删除的旧数据,当前事务仍然能够读取到;

When the SNAPSHOT isolation level is enabled, each time a row is updated, the SQL Server Database Engine stores a copy of the original row in tempdb, and adds a transaction sequence number to the row. The following is the sequence of events that occurs:

五,MOT的事务处理

  • A new transaction is initiated, and it is assigned a transaction sequence number.

  • The Database Engine reads a row within the transaction and retrieves the row version from tempdb whose sequence number is closest to, and lower than, the transaction sequence number.

  • The Database Engine checks to see if the transaction sequence number is not in the list of transaction sequence numbers of the uncommitted transactions active when the snapshot transaction started.

  • The transaction reads the version of the row from tempdb that was current as of the start of the transaction. It will not see new rows inserted after the transaction was started because those sequence number values will be higher than the value of the transaction sequence number.

  • The current transaction will see rows that were deleted after the transaction began, because there will be a row version in tempdb with a lower sequence number value.

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

The net effect of snapshot isolation is that the transaction sees all of the data as it existed at the start of the transaction, without holding or placing any locks on the underlying tables. This can result in performance improvements in situations where there is contention.

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

A snapshot transaction always uses optimistic concurrency control, with holding any locks that would prevent other transactions from updating rows. If a snapshot transaction attempts to commit an update to a row that was changed after the transaction began, the transaction is rolled back, and an error is raised.

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

 

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

参考文档:

图片 8

Snapshot Isolation in SQL Server

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

Isolation Levels in the Database Engine.aspx)

  • 在该阶段,有时会产生更新冲突(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.

SQL SERVER – Difference Between Read Committed Snapshot and Snapshot Isolation Level

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返回任何结果集,重试逻辑的示例代码如下:

图片 9图片 10

-- 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

本文由9159.com发布于www.9159.com,转载请注明出处:SQL Server 数据库级别默认的事务隔离级别是Read,

关键词:

上一篇:没有了
下一篇:没有了