MSSQL-并发控制-1-Transaction


 
   MSSQL并发控制原先打算分为两个部分写:隔离级别及锁,写的过程当中,发现须要说起下事务的相关内容,故加多一篇博文,共3篇。
 


 
    若是转载,请注明博文来源:  www.cnblogs.com/xinysu/   ,版权归 博客园 苏家小萝卜 全部。望各位支持!
 


   

   并发控制,在于控制每个事务的操做过程以及它们对资源的占用状况,同时要保证事务的ACID特性。这里简单描述事务类别、ACID特性及对分布式事务的简要说明。
 

1 事务类别

    从提交方式:自动提交事务、手动提交事务
    从开启方式:显式事务、隐式事务
    其余:批范围事务、分布式事务

1.1 显式事务

    经过API函数或者发布T-SQL begin transaction、commit transaction、commit work、rollback transaction、rollback work 、save transaction等明肯定义事务的开始和结束。
 
    这里简要说明commit、 save transaction 、rollback 、 xact_abort。

1.1.1 COMMIT

    commit,提交最近一次未提交事务,这里注意,commit  transaction = commit work = commit tran [name]。每次commit,都须要把当前的@@trancount减去1。
   
 1 begin tran yu1
 2 select @@TRANCOUNT
 3        begin tran yu2
 4        insert into tbxin(name,age) select '第2层tran',100;
 5        select @@TRANCOUNT
 6              
 7                   begin tran yu3
 8                      insert into tbxin(name,age) select '第3层tran',100;
 9                      select @@TRANCOUNT
10                      commit tran --等同于 commit tran anyname
11                      select @@TRANCOUNT
12                      commit tran --等同于 commit tran anyname
13                      select @@TRANCOUNT
14                      commit tran --等同于 commit tran anyname
15                      select @@TRANCOUNT

1.1.2 ROLLBACK

    rollback,回滚事务,有2中语法:
  • 第一个,回滚当前全部未结束事务
    • rollback = rollback tran = rollback transaction = rollback work
    • 不管嵌套了多少事务,@@trancount为多少,执行 rollback则直接回滚全部嵌套事务,设置@@trancount为0
    • 常见错误案例:EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。
  • 第二个,回滚到某个保存点的位置
    • rollback tran savepoint_name
    • 不影响@@trancount计算,回滚到 某个 save tran savepoint_name的位置
 错误案例:
 
 1 CREATE PROC p_count
 2 AS
 3 begin transaction
 4 insert into tbxin(name,age) select '第一层tran',200;
 5 rollback transaction
 6 GO
 7 
 8 BEGIN TRAN
 9 EXEC p_count
10 select @@TRANCOUNT
11 
12 消息 266,级别 16,状态 2,过程 p_count,第 0 行 13 EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。

1.1.3 SAVE TRANSACTION

   save transaction,定义在按条件取消某个事务的一部分后,该事务能够返回的一个位置。 若是将事务回滚到保存点,则根据须要必须完成其余剩余的 Transact-SQL 语句和 COMMIT TRANSACTION 语句,或者必须经过将事务回滚到起始点彻底取消事务。 若要取消整个事务,使用窗体 ROLLBACK TRANSACTION transaction_name。 这将撤消事务的全部语句和过程。
  • save transaction [savepoint_name] 提供 用户 在事务内设置保存点或标记,因此 save transaction 只能在事务内部执行;
  • save transaction [savepoint_nmae] 不影响 @@trancount 计数;
  • 注意 save transaction [savepoint_name] 只有对应 rollback transaction [savepoint_name],并无对应 commit transaction [savepoint_name],强调下:对应的rollback transaction [savepoint_name] 是有带 保存点名字的,若是没有带名字,则会回滚整个最外部的事务;
  • save transaction 对应的 rollback transaction [savepoint_name]并不须要一一对应,能够多个save tran 对应0到多个rollback tran
  • 事务内,能够有多个 save transaction [savepoint_name],可以使用 rollback transaction [savepoint_name] 回滚到任意一个保存点
  • 支持savepoint_name重复命名,可是不建议。在事务中容许有重复的保存点名称,但指定保存点名称的 ROLLBACK TRANSACTION savepoint_name 语句只将事务回滚到使用该名称的最近的 SAVE TRANSACTION savepoint_name;
  • 在使用 BEGIN DISTRIBUTED TRANSACTION 显式启动或从本地事务升级的分布式事务中,不支持 SAVE TRANSACTION。
 
 1 begin tran
 2 select @@TRANCOUNT
 3 
 4        save tran yu1
 5        insert into tbxin(name,age) select '第1层save',100;
 6        select @@TRANCOUNT
 7              
 8                   save tran yu2
 9                      insert into tbxin(name,age) select '第2层save',100;
10                      select @@TRANCOUNT
11                          
12                             save tran yu3
13                           insert into tbxin(name,age) select '第3层save',100;
14                           select @@TRANCOUNT
15 
16                                  save tran yu4
17                                insert into tbxin(name,age) select '第4层save',100;
18                                select @@TRANCOUNT
19 
20                             rollback tran yu3
21                             select @@TRANCOUNT
22 
23               commit tran yu2
24         select @@TRANCOUNT
25 
26 rollback tran

1.1.4 XACT_ABORT

    xact_abort用于设置环境属性,默认为关闭状态。在关闭的状态下,嵌套事务中,若某个嵌套事务异常,不影响整个事务的进行,需手动写明错误后的处理方式(commit or rollback);启动状态下,当某个嵌套事务异常,回滚整个嵌套事务。
 
 

1.2 隐式事务

    为链接将隐性事务模式设置为打开以后,当数据库引擎实例首次执行下列任何语句时,都会自动启动一个事务:当链接以隐式事务模式进行操做时,数据库引擎实例将在提交或回滚当前事务后自动启动新事务。无须描述事务的开始,只需提交或回滚每一个事务。隐性事务模式生成连续的事务链。经过 API 函数或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 语句,将隐性事务模式设置为打开。
  • ALTER TABLE
  • CREATE
  • DROP
  • OPEN
  • FETCH
  • GRANT
  • REVOKE
  • SELECT
  • UPDATE
  • DELETE
  • INSERT
  • TRUNCATE TABLE 

1.3 自动提交事务

--例子
INSERT INTO ...
--数据库默认的事务管理模式,在没有被显式事务及隐式事务覆盖的状况下,自动在每一个Tsql完成时,提交或者回滚

1.4 手动提交事务

 
--例子
BEGIN TRAN
 
INSERT INTO ...
 
COMMIT / ROLLBACK
 
--数据库默认的事务管理模式,显式事务在完成时,手动指定SQL,说明提交或者回滚。

1.5 批范围事务

   只能应用于多个活动结果集 (MARS),在 MARS 会话中启动的 Transact-SQL 显式或隐式事务变为批处理级事务。当批处理完成时没有提交或回滚的批处理级事务自动由 SQL Server 进行回滚。
 

1.6 分布式事务

    分布式事务跨越两个或多个称为资源管理器的服务器,有同构分布式及异构分布式。在MSSQL中,能够经过  BEGIN DISTRIBUTED TRANSACTION 命令开启分布式事务。
 
    这里有一点须要注意一下:当事务内部操做跨越了多个服务器,可是并无使用 BEGIN DISTRIBUTED TRANSACTION 命令开头的事务,也会自动转化为分布式事务!
 
    假设A服务器上的数据库 Orders 用于记录订单,B服务器上的 Stock 用于记录库存(不考虑程序创建两个DB链接,按照仅创建一个 DB连接到 A 上)。当 Stock有库存,并减去一个库存后,Orders 正常能够下一个订单,则这个下单事务内,操做了两个服务器,属于分布式事务,简要操做以下:
 
BEGIN DISTRIBUTED TRANSACTION
SELECT ... FROM Stock... WHERE ...
UPDATE Stock ...
INSERT INTO ORDERS ...
COMMIT

 

    在SQL SERVER中,若是没有配置服务器的DTC服务,使用分布式事务的时候,会报错以下:html

(1 行受影响)
消息 8501,级别 16,状态 3,第 4 行 服务器 'XINYSU\MSSQL' 上的 MSDTC 不可用。
 
(1 行受影响)
连接服务器"test_xinysu"的 OLE DB 访问接口 "SQLNCLI11" 返回了消息 "该伙伴事务管理器已经禁止了它对远程/网络事务的支持。"。 消息 7391,级别 16,状态 2,第 4 行 没法执行该操做,由于连接服务器 "test_xinysu" 的 OLE DB 访问接口 "SQLNCLI11" 没法启动分布式事务。

 

    若是实例须要支持分布式事务,则须要在双方的服务器其上开启DTC服务,运行XA事务。这里注意一点,若是连接服务器是MySQL数据库,由于mysql的odbc不支持 XA事务,因此,会报错 没法启动分布式事务。官网解释以下:mysql

MySQL OLE DB driver does not support MSDTC because it doesn’t support auto-enlistment in the ambient COM+ transaction. If you really want to, you can write your own XA.DLL to wrap MySQL OLEDB driver in an XA transaction.
 
windows启动DTC服务 ,配置以下:
 
"管理工具" -> "组件服务" ,按照下图操做,打开本地的DTC配置,启动网络DTC,启动XA事务,以下图。
 
      配置结束后点击应用,则会提示MSDTC服务会被重启,同时,依赖DSDTC的应用程序可能须要重启才能使用新的配置,这个时候就须要重启 SQL SERVER的服务了。
    
 
      若是是正在跑的数据库,建议先从库配置,切换主从,主库配置的顺序来启用。最好是在搭建服务器一开始,就了解程序层面是否会使用到这个功能。正常状况下,都是在程序中配置多个DB链接,由程序来控制分布式事务。
 
     这里多说一个平常须要注意的事项,在使用连接服务器对另一个服务器上的DB作增删改操做的时候,都是一行一行传递过去作操做的。好比:
 
1 INSERT INTO mssql_xinysu.dbname.dbo.tbid(id) select id from sys.sysobjects
2 或者
3 INSERT INTO openquery([mysql_xinysu],'select id from tbid') select id from sys.sysobjects

    假设 select id from sys.sysobjects 的结果有2k行,则在 第一个SQL的 ODBC 是这么处理的:sql

  • 分为2000个INSERT 语句 
  • (@Param000004 int)INSERT [dbname].[dbo].[tbid]([id]) VALUES(@Param000004)
  • 一条一条INSERT
    第二个SQL的ODBC是这么处理的:
  • 分为2000个INSERT 语句 
  • INSERT INTO tbid ([id]) VALUES(单个的ID值)
  • 一条条INSERT
    经过这个操做说明,可想而知,链接服务器操做是很是慢的过程,不建议在有性能要求的业务上进行这样的使用操做。

2 ACID特性

2.1 Atomicity

    简称为A,事务的原子性。要求 在同个事务中,单个SQL或者多个SQL对数据进行的修改操做,要么一块儿执行提交,要么所有回滚不提交。存储引擎中,经过undo log 来实现事务的原子性。
    例子:
    

2.2 Consistency

    简称为 C,事务的一致性。事务在完成时,必须使全部的数据都保持一致状态 。在相关数据库中,全部规则都必须应用于事务的修改,以保持全部数据的完整性。事务结束时,全部的内部数据结构(如 B 树索引或双向链表)都必须是正确的。 
 
    一致性,能够分为两个层次来讲明。一个是存储引擎层,一个是业务层。
     在存储引擎层,当某个表格发现了数据修改,那么修改的行数据应该符合表格的约束条件、外键条件,涉及到索引页数据也要作相应修改,保证数据修改后的完整性。
     在业务层,事务的一致性更多的在于程序设计,好比在一个事务内,帐号A给帐号B转帐100元,那么这个事务结束后,A的帐号须要少100元,B的帐号须要增长100元。    

2.3 Isolation

    简称为 I,事务的隔离性。由并发事务所作的修改必须与任何其余并发事务所作的修改隔离。在SQL SERVER中,经过设置隔离级别来保证。事务识别数据时数据所处的状态,要么是另外一并发事务修改它以前的状态,要么是第二个事务修改它以后的状态,事务不会识别中间状态的数据。这称为可串行性,由于它可以从新装载起始数据,而且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。
 
    根据业务的实际状况和需求,能够对不一样的业务选择不一样的隔离级别 来 控制 该事务 与其余并发事务之间的 隔离关系,隔离级别越高,并发能力就相应越低。    

2.4 Durability

    简称为D,事务的持久性。完成彻底持久的事务以后,它的影响将永久存在于系统中。该修改即便出现系统故障也将一直保持。存储引擎中,经过redo log 来实现事务的持久性。    

3 协议

    在事务内,除了保持ACID的特性外,在MSSQL中,会遵循相应的两阶段锁跟XA协议。

3.1 2PL

    两阶段锁2PL,这个在以前的博文  http://www.cnblogs.com/xinysu/p/7260227.html 中有所说起。
 
    2-PL,也就是两阶段锁,锁的操做分为两个阶段:加锁、解锁。先加锁,后解锁,不相交。加锁时,读操做会申请并占用S锁,写操做会申请并占用X锁,若是对所在记录加锁有冲突,那么会处于等待状态,知道加锁成功才惊醒下一步操做。解锁时,也就是事务提交或者回滚的时候,这个阶段会释放该事务中全部的加锁状况,进行一一释放锁。
 
     假设事务对记录A和记录B都有操做,那么,其加锁解锁按照逐行加锁解锁顺序,以下:
     
BEGIN
LOCK A
READ A
A:A+100
WRITE A
UNLOCK A
LOCK B
READ B
UNLOCK B
COMMIT

     两阶段锁还有几种特殊状况:conservative(保守)、strict(严格)、strong strict(强严格),这三种类型在加锁和释放锁的处理有些不同。数据库

  • conservative
    • 在事务开始的时候,获取须要的记录的锁,避免在操做期间逐个申请锁可能形成的锁等待,conservative 2PL 能够避免死锁
  • strict 
    • 仅在事务结束的时候(commit or rollback),才释放全部 write lock,read lock 则正常释放
  • strong strict
    • 仅在事务结束的时候(commit or rollback),才释放全部锁,包括write lock 跟 read lock 都是结束后才释放。

3.2 XA

    说XA协议,不得不提2PC、3PC协议,而提这两个协议,又不得不说说CAP理论。

3.2.1 CAP理论

     CAP分别是Consitency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),在分布式存储系统里边,最多只能同时知足其中二者。
  • Consitency
    • 一致性,在分布式存储系统中,对于每一次的读操做,对于读到的数据,要么都是最新的,要么则返回一个错误
    • 这里的CAP的C跟ACID的C虽然是一个单词,可是含义不同哦,记得区分
    • 在关系型数据库里边,一般优先考虑到是一致性
  • Availability
    • 可用性,保证每次请求都正常,但不要求返回的结果是最新的数据
  • Partition tolerance
    • 分区容错性,当各个分区之间由于网络发生消息丢失或者延迟是,分布式存储系统仍能正常运行。
    • 则是在操做 涉及多个服务器的事务 过程当中,

3.2.2 2PC

    两阶段提交,全程是 Two Phase Commitment Protocol。也就是将分布式事务分为了两个阶段:Prepare 跟 Commit/Rollback。
 
    在分布式系统中, 若是其中一个服务器上的操做失败,则其余服务器上的操做也须要回滚,即在一个事务内,多个跨服务器的操做中,要么全部都成功,要么全部都失败。可是呢,实际上每一个节点均可以对自身的操做作控制,可是却不能控制同个分布式系统的其余节点的操做,这也就致使了,同个事务中,若是A节点操做成功,可是B节点操做失败,若是没有相应的处理方式,则会出现,某些操做成功,某些操做失败,形成数据不一致的状况。
 
     如何处理这个问题呢?这就须要引入一个第三方组件来统一接收各个节点的操做状况,而后再根据各个操做的结果来判断各个节点的操做是否提交或者回滚。这个就是 2PC的雏形了。
 
 
    2PC的简要处理说明以下:
  • Prepare
    • 事务协调器coordinator 对 涉及到的节点 发起 操做申请;
    • 各个节点获取到 操做后,直接在 数据库中执行,并存放相关的日志到redo / undo log中,注意注意,这里仅是操做,并无提交或者回滚该操做;
    • 各节点将处理日志写入磁盘;
    • 返回信息给coordinnator
      • 若是节点能够正常执行,则返回 Ready 通知 coordinator;
      • 若是节点不能够正常执行,则该节点本地回滚该操做,并返回 Not Ready 通知 coordinator
  • Comiit/Rollback
    • coordinator 根据各个节点 的反馈信息,来决定 该事务操做的结果
    • coordinator将 操做结果记录到日志中
    • 反馈操做结果给各个节点
      • 若是出现一个或者一个以上的节点 反馈回来 Not Ready的通知,则coordinator会通知 正常执行操做的节点 回滚事务
      • 若是没有出现 Not Ready 的反馈,则coordinator会通知全部节点 COMMIT 操做。 
2PC如何处理异常?
  • 事务协调器宕机
    • 这里须要引入一个新角色:coordinator watchdog,事务协调器看门狗
    • 不管是coordinator 仍是分布式系统的各个节点,在操做过程当中,都会记录当前操做的状态日志。当出现异常或者恢复时,能够经过日志来判断当前的状况。
    • 当 coordinator 发起提议后宕机,而此时各个节点开始操做,而后反馈给 coordinator,可是 迟迟没有接收到 coordinator 的回应,那么各个节点的操做就没法回滚或者提交,处于堵塞状况。而 coordinator watchdog 则能够解决这个堵塞现象,当coordinator宕机必定时间后,看门狗会自动 担任 coordinator 的工做,接收各个节点的 反馈状况,而后再根据反馈结果传递 COMMIT/ROLLBAK给各个节点。
  • 节点宕机
    • prepare阶段宕机,则coordinator接收到事务后发送给各个节点须要作的 操做时,节点发生宕机,这个时候,则该节点没法返回 Ready 的消息,coordinator则默认接受该节点发出的 abort 信息,coordinator通知其余各个节点 Rollback 操做;
    • Comiit/Rollback阶段宕机,因为各个节点及coordinator都有日志记录,coordinator会记录这个事务是会提交仍是回滚,当 节点宕机后,其余节点根据coordinator的通知执行ROLLBACK或者COMMIT,而宕机节点本地会记录该事务操做未执行提交或者回滚,节点恢复后,会从 coordinator 日志中读取日志,从新处理该操做。

3.2.3 XA协议

    XA协议是  X/Open DTP Group 定义的两阶段提交协议,规定了事务管理器跟资源管理器的接口。
 
    事务管理器指的是 二阶段协议中的 coordinator ,而资源管理器则指的是各个数据库系统。
    
    通常状况下,各个数据库系统并不知道彼此之间作了什么,这个时候,就须要一个第三方来作信息的接受跟传达,由它通知和协调相关数据库的提交或回滚。XA就是用来定义这个第三方的协议,详细定义了交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。
 
    XA接口函数由数据库厂商提供。一般状况下,交易中间件与数据库经过XA 接口规范,使用两阶段提交来完成一个全局事务,XA规范的基础是两阶段提交协议。注意,XA事务的性能相对较差。
     
   
 
 参考文档:
相关文章
相关标签/搜索