事务
事务的概念
事务是一个用户定义的完整的工作单元,一个事务内的所有语句被作为整体执行,要么全部执行,要么全部不执行。遇到错误时,可以回滚事务,取消事务内所做的所有改变,从而保证数据库中数据的一致性和可恢复性。
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作。
数据库事务的一个例子是将钱从一个银行账号中转到另外一个银行账号中。此时通常包含两步操作:一条UPDATE 语句负责从一个银行账号的总额中减去一定的钱数,另外一条UPDATE 语句负责向另外一个银行账号中增加相应的钱数。
操作流程:
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
・ 更新客户所购商品的库存信息
・ 保存客户付款信息--可能包括与银行系统的交互
・ 生成订单并且保存到数据库中
・ 更新用户相关信息,例如购物数量等等正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息
也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。
数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术
事务的特性
原子性(atomic)(atomicity)
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。
一致性(consistent)(consistency)
事务在完成时,必须使所有的数据都保持一致状态。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。
隔离性(insulation)(isolation)
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,
应用程序降低隔离级别以换取更大的吞吐量。防止数据丢失
持久性(Duration)(durability)
事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
事务与批的区别:
编程时,一定要区分事务和批的差别。
批是一组整体编译的SQL 语句,事务是一组作为单个逻辑工作单元执行的SQL 语句。
批语句的组合发生在编译时刻,事务中语句的组合发生在执行时刻。
当在编译时,批中某个语句存在语法错误,系统将取消整个批中所有语句执行,而在运行时刻,如果事务中某个数据修改违反约束、规则等,系统将回滚整个事物。
如果批内产生一个运行时刻错误,系统默认只回退到产生该错误的语句。一个事务中也可以拥有多个批,一个批里可以有多个SQL 语句组成的事务,事务内批的多少不影响事务的提交或回滚操作
事务对并发控制和保障数据完整的重要性
事务与并发控控制的关系
数据库系统一个明显的特点是多个用户共享数据库资源,尤其是多个用户可以同时存取相同数据。
串行控制:如果事务是顺序执行的,即一个事务完成之后,再开始另一个事务并行控制:如果DBMS 可以同时接受多个事务,并且这些事务在时间上可以重叠执行事务是并发控制的基本单位,保证事务ACID 的特性是事务处理的重要任务,而并发操作有可能会破坏其ACID 特性。
DBMS 并发控制机制的责任:
对并发操作进行正确调度,保证事务的隔离性更一般,确保数据库的一致性。如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。由于并发操作带来的数据不一致性包括:丢失数据修改、读‖脏‖数据(脏读)、不可重复读、产生幽灵数据。
(1)丢失数据修改
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。再例如,两个编辑人员制作了同一文档的电子复本。每个编辑人员独立地更改其复本,然后保存更改后的复本,这样就覆盖了原始文档。最后保存其更改复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改,则可以避免该问题。
(2)读�D脏‖数据(脏读)
读�D脏‖数据是指事务T1 修改某一数据,并将其写回磁盘,事务T2 读取同一数据后,T1 由于某种原因被除撤消,而此时T1 把已修改过的数据又恢复原值,T2 读到的数据与数据库的数据不一致,则T2 读到的数据就为�D脏‖数据,即不正确的数据。例如:一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该复本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前任何人都不能读取更改的文档,则可以避免该问题。
(3)不可重复读
指事务T1 读取数据后,事务T2 执行更新操作,使T1 无法读取前一次结果。不可重复读包括三种情况:
事务T1 读取某一数据后,T2 对其做了修改,当T1 再次读该数据后,得到与前一不同的值。
(4)产生幽灵数据
按一定条件从数据库中读取了某些记录后,T2 删除了其中部分记录,当T1 再次按相同条件读取数据时,发现某些记录消失T1 按一定条件从数据库中读取某些数据记录后,T2 插入了一些记录,当T1 再次按相同条件读取数据时,发现多了一些记录。
事务对保障数据一致和完整性的作用
故障轻则造成运行事务非正常中断,影响数据库中数据的正确性,重则破坏数据库,使数据库中全部或部分数据丢失 。影响事务正常运行的故障有:
(1)事务内部的故障
(2)系统故障
(3)介质故障
(4)计算机病毒
事务分类及控制
事务的分类
SQL Server 的事务模式可分为显式事务、隐式事务和自动事务三种。
1). 显式事务
显式事务是指由用户执行T-sql 事务语句而定义的事务,这类事务又称做用户定义事务。定义事务的语句包括:
BEGIN TRANSACTION:标识一个事务的开始,即启动事务。
COMMIT TRANSACTION、COMMIT WORK:标识一个事务的结束,事务内所修改的数据被永久保存到数据库中。
ROLLBACK TRANSACTION、ROLLBACKWORK:标识一个事务的结束,说明事务执行过程中遇到错误,事务内所修改的数据被回滚到事务执行前的状态。
2). 隐式事务
在隐式事务模式下,在当前事务提交或回滚后,SQL Server自动开始下一个事务。所以,隐式事务不需要使用BEGIN TRANSACTION 语句启动事务,而只需要用户使用ROLLBACKTRANSACTION、ROLLBACK WORK、COMMIT TRANSACTION、COMMIT
WORK 等语句提交或回滚事务。在提交或回滚后,SQL Server自动开始下一个事务。执行SET IMPLICIT_TRANSACTIONS ON 语句可使SQL Server进入隐式事务模式。在隐式事务模式下,当执行下面任意一个语句时,可使SQL Server重新启动一个事务:
所有CREATE 语句 ALTERTABLE 所有DROP 语句
TRUNCATE TABLE GRANT REVOKE
INSERT UPDATE DELETE
SELECT OPEN FETCH需要关闭隐式事务模式时,调用SET 语句关闭IMPLICIT_TRANSACTIONS OFF 连接选项即可。
3). 自动事务模式
在自动事务模式下,当一个语句被成功执行后,它被自动提交,而当它执行过程中产生错误时,被自动回滚。自动事务模式是SQL Server的默认事务管理模式,当与SQL Server建立连接后,直接进入自动事务模式,直到使用BEGINTRANSACTION 语句开始一个显式事务,或者打开IMPLICIT_TRANSACTIONS 连接选项进入隐式事务模式为止。
而当显式事务被提交或IMPLICIT_TRANSACTIONS被关闭后,SQL Server 又进入自动事务管理模式。
DEMO:
BEGIN TRAN
SELECT * FROM Student
INSERT INTO Student
VALUES(‘,‘张三‘, …)
SELECT * FROM Student
ROLLBACK -- 回滚整个事务
或: COMMIT -- 提交事务
事务控制
SQL Server 中有关事务的处理语句有:
命令名 作用 格式
BEGIN TRANSACTION 说明一个事务开始 BEGINTRANsaction [<事务名>]
COMMIT TRANSACTION
说明一个事务结束,它的作用是提
交或确认事务已经完成
COMMIT TRANsaction [<事务名>]
SAVE TRANSACTION
用于在事务中设置一个保存点,目的是在撤消事务时可以只撤消部
分事务,以提高系统的效率
SAVE TRANsaction <保存点>
ROLLBACK
TRANSACTION
说明要撤消事务,即撤消在该事务中对数据库所做的更新操作,使数
据库回退到BEGINTRANSACTION或保存点之前的状态
ROLLBACK TRANsaction [<事务名>| <保存
点>]
(1)事务的提交和回滚
要永久性地记录事务中SQL语句的结果,需要执行COMMIT语句,从而提交(commit)事务。要取消SQL 语句的结果,需要执行ROLLBACK 语句,从而回滚(rollback)事务,将所有行重新设置为原始状态
DEMO1:下面这个例子向orders 表中添加一行,然后执行COMMIT 语句,永久性地保存对数据库所进行的修改
BEGIN TRANsaction complete
INSERT INTO orders
VALUES ('p04','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
Commit TRANsaction complete
此查询显示添加的新行
select * from orders
DEMO2: 下面这个例子更新顾客P01 的内容,然后执行一条ROLLBACK 语句,取消对数据库所进行的修改:
begin transaction up_cust
update orders
set cust = 'oef'
where docno = 'p01'
Rollback transaction up_cust
此查询显示更改p01 的数据被rollback 到了之前的状态.
select * from orders
(2) 事务的开始与结束
事务是用来分割SQL 语句的逻辑工作单元。事务既有起点,也有终点;
当下列事件之一发生时,事务就开始了:
连接到数据库上,并执行一条DML 语句(INSERT、UPDATE或DELETE)。
前一个事务结束后,又输入了另外一条DML 语句。当下列事件之一发生时,事务就结束了:
执行COMMIT 或ROLLBACK语句。
执行一条DDL 语句,例如CREATE TABLE 语句;在这种情况下,会自动执行COMMIT语句。
执行一条DCL 语句,例如GRANT 语句;在这种情况下,会自动执行COMMIT 语句。
断开与数据库的连接。在退出SQL*Plus 时,通常会输入EXIT 命令,此时会自动执行COMMIT 语句。如果SQL*Plus 被意外终止了(例如运行SQL*Plus 的计算机崩溃了),那么就会自动执行ROLLBACK 语句。这适用于任何可能访问数据库的程序。例如,如果编写了一个可以访问数据库的Java 程序,而这个程序崩溃了,那么就会自动执行ROLLBACK 语句。
执行了一条DML 语句,该语句却失败了;在这种情况中,会为这个无效的DML 语句执行ROLLBACK 语句。
(3) 保存点
在事务中的任何地方都可以设置一个保存点(savepoint),这样可以将修改回滚到保存点处。如果有一个很大的事务,这将非常有用,因为这样如果在保存点后进行了误操作,并不需要将整个事务一直回滚到最开头。但是保存点不能肆意乱用;最好是重新调整一下事务的结构,将其转换为较小的事务。
DEMO:下面将给出一个使用保存点的例子
在开始之前,首先来检查一下产品p01 和p02的当前价格:
select docno,rate
from orders
where docno in('p01','p02')
P01 的价格是5000,p02 的价格是6000,现将p01 产品价格提高20%,p02 产品价格提高30%
创建一个事务,更新p01 产品的价格,并设置为保存点.
begin transaction up_rate
update orders
set rate = rate * 1.2
where docno = 'p01' --更新p01产品价格,增加20%
save transaction up_p01 --设置为保存点
update orders
set rate = rate * 1.3
where docno = 'p02' --更新p02 产品价格,增加30%
--此时查看查询结果:
--selectdocno,rate
--fromorders
--wheredocno in('p01','p02')
未修改前结果:
可以看到,通过上面事务语句,已将产品p01 及p02 的价格做了更新
下面这条语句用于使事务回滚到保存点的位置:
ROLLBACKtransaction up_p01
此时执行一条回滚语句到保存点,查看返回结果:
--selectdocno,rate
--fromorders
--wheredocno in('p01','p02')
未修改前结果:
可以看到,此时仅对产品p01 做了修改,对p02 所做的修改回滚到了保存点的位置.下面这条语句用于使事务回滚到事务开始前的位置.
rollbacktransaction up_rate
此时执行一条回滚语句到保存点,查看返回结果:
--selectdocno,rate
--fromorders
--wheredocno in('p01','p02')
可以看到,此时回滚到了未修改前的结果.
说明:
在定义一个事务时,BEGIN TRANSACTION 语句应与COMMITTRANSACTION 语句或ROLLBACK TRANSACTION 成对出现。在SQL Server中,事务定义语句可以嵌套,但实际上只有最外层的BEGINTRANSACTION 语句和COMMIT TRANSACTION 语句才能建立和提交事务;在回滚事务时,也只能使用最外层定义的事务名或存储点标记,而不能使用内层定义的事务名。事务嵌套常用在存储过程或触发器内,它们可以使用BEGINTRANSACTION…COMMIT TRANSACTION 对来相互调用。编写有效事务的建议
A、编写有效事务的指导原则
不要在事务处理期间输入数据
浏览数据时,尽量不要打开事务
保持事务尽可能的短
灵活地使用更低的事务隔离级别
在事务中尽量使访问的数据量最小
B、避免并发问题
为了防止并发问题,应该小心地管理隐性事务。在使用隐性事务时,COMMIT 或ROLLBACK 之后的下一个Transact-SQL语句会自动启动一个新事务。这可能在应用程序浏览数据时,甚至在要求用户输入时,打开新的事务。所以,在完成保护数据修改所需要的最后一个事务之后和再次需要一个事务来保护数据修改之前,应该关闭隐性事务。
事务处理DEMO 分析
DEMO1:使用事务的三种模式进行表的处理,分批执行,观察执行的过程。
use mydb
go
selecttimes=0,* from orders --检查当前表中的结果
--1、SQL Server 首先处于自动事务管理模式
INSERTINTO orders
VALUES('p05','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
selecttimes=1,* from orders �C显示p05 被插入
go
--再次执行此插入语句
INSERTINTO orders
VALUES('p05','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
返回:
--消息2627,级别14,状态1,第1 行
--违反了PRIMARY KEY 约束'PK_orders'。不能在对象'dbo.orders' 中插入重复键。
--语句已终止。
selecttimes=2,* from orders �C-显示数据没有变化
go
--2、进入显示事务模式
BEGINTRANSACTION in_order --进入显式事务模式
INSERTINTO orders
VALUES('p06','2011-09-10 00:00:00.000','ded','EN','4000',convert(varbinary(256),
pwdencrypt('passw0rd')))
SELECTtimes=3,* FROM orders --显示'p06'被插入
ROLLBACKTRANSACTION in_order
GO
SELECTtimes=4,* FROM orders --因为执行了回滚,插入的'p06'被撤消
--3、进入隐式事务模式