SETIMPLICIT_TRANSACTIONS ON --进入隐式事务模式
INSERTINTO orders
VALUES('p07','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
SELECTtimes=5,* FROM orders --显示'p07'被插入
ROLLBACK
go
SELECTtimes=6,* FROM orders --因为执行了回滚,插入的'S060107'被撤消
go
DELETEFROM orders
WHEREdocno='p05' --删除第个插入
SELECTtimes=7,* FROM orders --显示'p05'不存在
ROLLBACK
GO
SELECTtimes=8,* FROM orders --因为回滚,使删除作废,所以'p05'又重新显示存在。
GO
SETIMPLICIT_TRANSACTIONS OFF --隐式事务模式结束,又进入自动模式
--4、关闭隐式事务模式后,进入自动模式
DELETEFROM orders
WHEREdocno='p05' --删除第个插入
SELECTtimes=9,* FROM orders
--自动模式执行成功被自动提交,显示'p05'被删除不存在。
DEMO2:定义事务,使事务回滚到指定的保存点,分批执行,观察执行的过程
use mydb
go
SELECTtimes=0, * FROM orders --检查当前表中的结果
go
begintransaction demo
INSERTINTO orders
VALUES('p05','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
go
SAVETRANSACTION save_demo
INSERTINTO orders
VALUES('p06','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
SELECTtimes=1, * FROM orders --显示'p05'和'p06'都被插入。
GO
ROLLBACKTRANSACTION save_demo --回滚部分事务
SELECTtimes=2, * FROM orders --显示'p06'被撤消不存在。
GO
ROLLBACKTRANSACTION demo --回滚整个事务
SELECTtimes=3, * FROM orders --显示'p05'被撤消不存在。
DEMO3:更新数据表orders,生成三个级别的嵌套时务,并提交该嵌套事务。观察变量@@TRANCOUNT的值的变化
use mydb --选择数据库必须单独在一个批中
go
select@@TRANCOUNT --变量@@TRANCOUNT的值为
begintransaction inside1
select@@TRANCOUNT --变量@@TRANCOUNT的值为
INSERTINTO orders
VALUES('p05','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
go
begintransaction inside2
select@@TRANCOUNT --变量@@TRANCOUNT的值为
INSERTINTO orders
VALUES('p06','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
go
begintransaction inside3
select@@TRANCOUNT --变量@@TRANCOUNT的值为
INSERTINTO orders
VALUES('p07','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
go
COMMITTRANSACTION inside3
SELECT@@TRANCOUNT --变量@@TRANCOUNT的值减为
GO
COMMITTRANSACTION inside2
SELECT@@TRANCOUNT --变量@@TRANCOUNT的值减为
GO
COMMITTRANSACTION inside1
SELECT@@TRANCOUNT --变量@@TRANCOUNT的值减为
GO
DEMO4:在orders表中先删除一条记录,然后再插入一条记录,通过测试错误值确定提交还是回滚
use mydb
go
DECLARE@del_error int,@ins_error int
--开始一个事务
BEGINTRAN del_or
--删除一个学生
DELETEorders WHERE docno = 'p07'
--为删除语句设置一个接受错误数值的变量
SELECT@del_error = @@ERROR
--@@ERROR以int类型返回最后执行的SQL语句的错误代码
--若为,表示语句执行成功
--再执行插入语句
INSERTINTO orders
VALUES('p08','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
--为插入语句设置一个接受错误数值的变量
SELECT@ins_error = @@ERROR
--测试错误变量中的值
IF @del_error = 0
AND@ins_error = 0
BEGIN
--成功,提交事务
COMMITTRAN del_or
END
ELSE
BEGIN
--有错误发生,回滚事务
IF @del_error <> 0
PRINT'错误发生在删除语句'
IF @ins_error <> 0
PRINT'错误发生在插入语句'
ROLLBACKTRAN del_or
END
GO
分布式事务
在大型应用领域,经常需要事务跨服务器进行数据操作,这样的事务被称作分布式事务。所以分布式事务要能够在多个服务器上执行。
按照关于分布式事务处理的X/Open XA规范,分布式事务的处理过程规定为两个阶段,就是通常说的两阶段提交。为了简化应用程序对分布式事务的处理工作,系统提供了一个事务管理器来协调各个不同服务器对事务的处理操作,它就是MS DTC( Distributed Transaction
Coordinator ),既事务管理协调器。
分布式事务的两阶段提交
(1)准备阶段:当分布式事务管理器接受到提交请求后,它向所有参与该事务的SQL Server服务器发出准备命令。每个服务器接受到准备命令后,做好接受处理事务的准备工作,并将准备工作状态返回给事务管理器。
(2)提交阶段:当事务管理器接受到所有服务器成功准备好的信息后,它向这些服务器发出提交命令。之后所有服务器进行提交。如果所有服务器均能成功提交事务,管理器向应用程序报告分布式事务成功提交,如若有任一个服务器未能提交,事务管理器将向所有服务器发出回滚事务命令,并向应用程序报告事务提交失败。
分布式事务的处理过程
(1)T-SQL 程序或应用程序执行BEGIN DISTRIBUTED TRANSACTION语句启动一个分布式事务。此后,该服务器就成为分布式服务器的管理服务器。
(2)应用程序对链接服务器执行分布式查询或执行远程服务器上的存储过程。
(3)分布式事务管理服务器自动调用MS DTC,使链接服务器或远程服务器参加分布式事务处理。
(4)T-SQL 应用程序执行COMMIT 或ROLLBACK语句时,分布式事务管理服务器通过调用MS DTC 来管理两阶段提交,使链接或远程服务器提交或回滚事务。
分布式事务处理DEMO 分析
(1)、语法:
BEGINDISTRIBUTED TRANSANCTION [transanctin_name|@ transanctin_variable]
参数说明:
transanctin_name|@transanctin_variable 事务名称或事务名变量。
(2)、分布式事务DEMO:
有两个服务器LinkServer1 和LinkServer2 。在LinkServer2 服务器上建立存储过程student_insert_new,其功能是向LinkServer1 上的教学管理数据库的student 表插入一个新行。
--1、先创建链接(远程)服务器
SQL Server Management Studio → 服务器 → 服务器对象 → 链接服务器 → 右键 →新建链接服务器
配置链接远程服务器的服务器名称及登录用户/密码
--在第一台运行SQL Server的服务器上运行下列代码:
EXECsp_addlinkedserver'LinkServer1', '','SQLOLEDB','本地服务器名或ip地址'
--例如'zufe-mxh'
EXECsp_addlinkedserver'LinkServer2', '','SQLOLEDB','远程服务器名或ip地址'
--例如'172.19.2.156'
EXECsp_configure 'remote access', 1
--系统默认是,一般不需要设置
RECONFIGURE
--设置'LinkServer1'的rpc输出属性,使得允许调用链接服务器上的存储过程。
EXECsp_serveroption'LinkServer1','rpc out','true'
GO
--停止并重新启动第一台SQL Server。
--确保使用SQL Server 身份验证登录。
--在第二台SQL Server 上运行下列代码。
EXECsp_addlinkedserver'LinkServer2', '','SQLOLEDB','本地服务器名或ip地址'
--例如'172.19.2.156'
EXECsp_addlinkedserver'LinkServer1', '','SQLOLEDB','远程服务器名或ip地址'
--例如'zufe-mxh'
EXECsp_configure 'remote access', 1
--系统默认是,一般不需要设置
RECONFIGURE
--设置'LinkServer2'的rpc输出属性,使得允许调用链接服务器上的存储过程。
EXECsp_serveroption'LinkServer2','rpc out','true'
GO
--在第二个服务器上添加新的远程登录ID(LinkServer1),以便允许远程服务器LinkServer1连接并执行
远程过程调用。
--假设登录LinkServer2和LinkServer1的用户都是'sa',并且有相同的口令。
EXECsp_addremoteloginLinkServer1, sa, sa
GO
--停止并重新启动第二台SQL Server
--2、在linkserver2 上创建存储过程
if exists
(select *
fromsys.sysobjects
whereid=object_id(N'pr_up_order')
andxtype='P')
dropproc pr_up_order
go
createproc pr_up_order --创建带输入参数的存储过程,定义变量,在执行存储过程时,输入数值则
插入一条记录
@docnochar(20),
@docdatedatetime,
@custnvarchar(200),
@carrencychar(3),
@ratenumeric(5),
@userpasswordvarbinary(256)
as
insertinto LinkServer1.mydb.dbo.orders
VALUES(
@docno,
@docdate,
@cust,
@carrency,
@rate,
@userpassword)
Go
--3、启用分布式事务
--在第一台服务器上启动DTC开始分布式事务
--使用sa 登录,现在就可以在第一台SQL Server 上执行第二台SQL Server 上的存储过程。
USE mydb
GO
BEGINDISTRIBUTED TRANSACTION insert_tran
--开始分布式事务
--在LinkServer1服务器上实行对表orders的插入
INSERTINTO orders
VALUES('p10','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
Go
--LinkServer1服务器自动调用MS DTC使得LinkServer2服务器执行存储过程student_insert_new对表
student的插入。
EXECUTELinkServer2.mydb.dbo. pr_up_order
COMMITTRANSACTION --提交事务
并发控制
在大型分布式数据库应用程序中,对数据库的并发访问操作是一个普遍存在的问题。SQL Server 使用资源锁定的方法管理用户的并发操作。
SQL Server 提供了两种并发控制方法:
乐观并发控制:该方法假想用户之间不太可能发生资源冲突(事实上不是不可能),所以允许用户在不锁定任何资源的情况下执行事务。只有当用户试图修改数据时才
检查资源是否冲突。该方法需要使用游标。
悲观并发控制:该方法根据需要在事务的持续时间内锁定资源,从而确保事务的完整性和数据库的一致性。这是SQL Server 默认的并发控制方法。下面予以介绍。
SQL Server 锁的粒度及模式
(1)、SQL Server 锁的粒度
○1RID:行标识符,锁定表中单行数据。
○2 键值:具有索引的行数据。
○3 页面:一个数据页面或索引页面。
○4 区域:一组连续的8 个数据页面或索引页面。
○5 表:整个表,包括其所有的数据和索引。
○6 数据库:一个完整的数据库。
可以根据事务所执行的任务来灵活选择所锁定的资源粒度。
(2)、资源锁定模式—基本锁
○1 共享锁(S 锁):用于只读数据操作,它允许多个并发事务对资源锁定进行读取,但禁止其他事务对锁定资源的修改操作。
○2 排它锁(X 锁) :它锁定的资源不能被其它并发事务再进行任何锁定,所以其它事务不能读取和修改。锁定的资源用于自己的数据修改。
一般更新模式是由一个事务组成,该事务先读取记录,要获取资源的共享锁,然后修改记录,此操作要求锁转换为排它锁。如果两个事务都获得了资源上的共享锁,然后试图同时更新数据,这样肯定有一个事务要将共享锁转化为排它锁,因为一个事务的排它锁与其它事务的共享锁不兼容,发生锁等待。另一个事务也会出现这个问题,由于两个事务都要转化为排它锁,并且都等待另一个事务释放共享锁,因此发生死锁。
(3)、资源锁定模式—专用锁
○1 更新锁(U 锁)
在修改操作的初始化阶段用于锁定可能被修改的资源。一个数据修改事务在开始时直接申请更新锁,每次只有一个事务可以获得资源的更新锁。
使用更新锁可以避免上述死锁,因为一次只有一个事务可以获得更新锁,之后当需要继续修改数据时,将更新锁转换为排它锁,否则将更新锁转换为共享锁。
○2 意向锁:
意向锁表示如果获得一个对象的锁,说明该结点的下层对象正在被加锁。例如放置在表上的共享意向锁表示事务打算在表中的页或行上加共享锁。
意向锁可以提高性能,因为系统仅在表级上检查意向锁而无须检查下层。
意向共享锁(IS 锁):对一个对象加意向共享锁,表示将要对它的下层对象加共享锁。
意向排它锁(IX 锁) :对一个对象加意向排它锁,表示将要对它的下层对象加排它锁。
意向排它共享锁:对一个对象加意向排它共享锁,表示对它加共享锁,再在它的下层对象加排它锁。
○3 架构锁
架构修改锁(Sch-M 锁):执行表的数据定义语言(DDL)操作时使用。
架构稳定锁(Sch-S 锁):编译查询时使用。它不阻塞任何事务锁,包括排它锁。
○4 大容量更新锁(BU 锁)
当数据大容量复制到表的时候使用。
查看锁:
sp_lock
封装协议
在运用X锁和S 锁对数据对象加锁时,需要约定一些规则:封锁协议(LockingProtocol)何时申请X 锁或S 锁持锁时间、何时释放
1 级封锁协议+事务T 在读取数据R 前必须先加S 锁,读完后即可释放S 锁
2 级封锁协议可以防止丢失修改和读―脏‖数据。
3 级封锁协议可防止丢失修改、读脏数据和不可重复读。容易造成比较多的死锁
封装协议小结:
事务隔离
为了避免产生并发访问问题,SQL Server 使用不同类型的锁对资源进行锁定,从而可重复读
限制在一个事务读取数据期间其他事务锁执行的操作类型,即对事务进行隔离。不同的并发访问问题可以通过设置不同的事务隔离级别加以解决。事务的隔离级别控制一个事务与其他事务的隔离程度,它决定该事务在读取数据时对资源所使用的锁类型。
(1)、事务隔离级别
未提交读:这是4 种隔离级别中限制最低的级别,它仅能保证SQL Server 不读取物理损坏的数据。在这种隔离级别下,不发出共享锁,也不接受排它锁,事务可以对数据执行未提交读或脏读;在事务结束前可以更改数据集内的数值,行也可以出现在数据集中或从数据集消失。
提交读:它要求在读取数据时控制共享锁以避免发生脏读,但数据可在事务结束前更改,这可能产生不能重复读或幻影读问题。
可重复读:锁定查询中使用的所有数据以防止其他用户更新,但是其他用户可以将新的幻影行插入到数据集中,新插入的幻影行将出现在当前事物的后续读取结果集中。可重复读
能够避免产生脏读和非重复读问题,但仍可能导致幻影读问题。
可串行读:这是事务隔离的最高级别,它使事务之间完全隔离,所以将导致并发级别较低。
在这种隔离级别下,SQL Server 在数据集上放置一个范围锁,以防止其他用户在事务完成之前更新数据集或向数据集内插入数据行,从而避免出现脏读、非重复读或幻影读等并发问题。
(2)、事务隔离级别对不同类型的行为
(3)、设置事务隔离级别
调用T-SQL中的SET TRANSACTION INOLATION LEVEL 语句可以调整事务的隔离
级别,以控制由该连接所发出的所有SELECT 语句的默认事务锁定行为。该语句的语法格式为:
SET TRANSACTIONISOLATION LEVEL{
READ UNCOMMITTED
|READ COMMITTED
|REPEATABLE READ
|SERIALIZABLE
}
例如:将事务隔离级别设置为REPEATABLE READ
SET TRANSACTIONISOLATION LEVEL REPEATABLE READ
注意:一旦设定,系统就会按这种隔离级别自动进行并发处理。
(4)、设置表级锁
表级锁是由程序员进行的,可以对SELECT、INSERT、UPDATE、DELETE 语句进行精确控制。表级锁定不是系统自动完成,而是需要程序员自己根据事务的要求进行锁定,然后,系统按程序员在程序中的锁定予以执行。
一般来说,读操作需要共享锁,写操作需要排它锁。如果需要更精确,还需要一些其它专用锁。具体设置有:
设置共享锁
设置排它锁
设置专用锁
○1 、设置共享锁
共享锁用于读操作。一个资源可获得多个共享锁。
使用HOLDLOCK设置共享锁。
DEMO:
USE mydb
GO
BEGINtransaction T1
SELECT * FROMOrders
WITH (HOLDLOCK)
SELECTCOUNT(docno) FROM Orders
COMMITtransaction t1
○2 、设置排它锁
对于INSERT、UPDATE 和DELETE 语句使用排它锁。在并发事务中,只有一个事务能够获得资源的排它锁。
使用TABLOCKX 设置排它锁。
DEMO:
USE mydb
GO
BEGINtransaction T1
INSERTINTO Orders
WITH(TABLOCKX)
VALUES('p09','2011-09-10','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
COMMIT
○3 、设置其它专用锁
语法同上
NOLOCK:不要发出共享锁和排它锁。仅适用SELECT 语句。
READPAST:跳过已经锁定的行。仅适用SELECT 语句。
TABLOCK:使用大容量更新锁。
PAGLOCK:使用页级锁。
ROWLOCK:使用行级锁,不使用页级和表级锁。
UPDLOCK:读取表时使用更新锁。
XLOCK:适用于不同粒度的排它锁。
(5)、死锁处理
SQL Server 能够自动定期搜索和处理死锁问题。当检测到有死锁时, SQL Server 回滚被中断的事务,并向应用程序返回1205 号错误信息,未被中断的事务则继续执行。在数据库应用程序捕捉到1205 号错误,可以对死锁现象做后续处理。为减少死锁次数,在设计应用程序时:
u 尽量使一个事务在一个批内,并且要短;
u 不要在事务处理期间和用户交互;
u 灵活使用较低级别的事务隔离;
u 在事务处理期间要使访问数据量最小。