MSSQL并发控制原先打算分为两个部分写:隔离级别及锁,写的过程当中,发现须要说起下事务的相关内容,故加多一篇博文,共3篇。
并发控制,在于控制每个事务的操做过程以及它们对资源的占用状况,同时要保证事务的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 分布式事务
这里有一点须要注意一下:当事务内部操做跨越了多个服务器,可是并无使用 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 来实现事务的持久性。
2-PL,也就是两阶段锁,锁的操做分为两个阶段:加锁、解锁。先加锁,后解锁,不相交。加锁时,读操做会申请并占用S锁,写操做会申请并占用X锁,若是对所在记录加锁有冲突,那么会处于等待状态,知道加锁成功才惊醒下一步操做。解锁时,也就是事务提交或者回滚的时候,这个阶段会释放该事务中全部的加锁状况,进行一一释放锁。
两阶段提交,全程是 Two Phase Commitment Protocol。也就是将分布式事务分为了两个阶段:Prepare 跟 Commit/Rollback。
在分布式系统中, 若是其中一个服务器上的操做失败,则其余服务器上的操做也须要回滚,即在一个事务内,多个跨服务器的操做中,要么全部都成功,要么全部都失败。可是呢,实际上每一个节点均可以对自身的操做作控制,可是却不能控制同个分布式系统的其余节点的操做,这也就致使了,同个事务中,若是A节点操做成功,可是B节点操做失败,若是没有相应的处理方式,则会出现,某些操做成功,某些操做失败,形成数据不一致的状况。
如何处理这个问题呢?这就须要引入一个第三方组件来统一接收各个节点的操做状况,而后再根据各个操做的结果来判断各个节点的操做是否提交或者回滚。这个就是 2PC的雏形了。
通常状况下,各个数据库系统并不知道彼此之间作了什么,这个时候,就须要一个第三方来作信息的接受跟传达,由它通知和协调相关数据库的提交或回滚。XA就是用来定义这个第三方的协议,详细定义了交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。
XA接口函数由数据库厂商提供。一般状况下,交易中间件与数据库经过XA 接口规范,使用两阶段提交来完成一个全局事务,XA规范的基础是两阶段提交协议。注意,XA事务的性能相对较差。