分享

数据库中事务机制的进阶使用(整理)

 CevenCheng 2011-08-04
数据库中事务机制的进阶使用(整理)-转载
2010-01-27 15:52
在前面的两篇blog中,我写了些关于数据库中的锁方面的一些内容,实际上锁是和事务紧密联系的,在数据库中事务这一块是很重要,也是比较复杂的,而且各个数据库产品的实现也不太相同,所以有必要在这里详细描述一下,虽然这些很细致的内容我们在实际编程时用到的概率不是很大,但毕竟知道一些还是比较好的,万一有用的到的时候就能解决大问题了。下面是一些google到的内容的整理和总结。 

一 事务的属性 

事务具有ACID属性 
即 Atomic原子性, Consistent一致性, Isolated隔离性, Durable永久性 

原子性 

就是事务应作为一个工作单元,事务处理完成,所有的工作要么都在数据库中保存下来,要么完全 
回滚,全部不保留 

一致性 
事务完成或者撤销后,都应该处于一致的状态 

隔离性 

多个事务同时进行,它们之间应该互不干扰.应该防止一个事务处理其他事务也要修改的数据时, 
不合理的存取和不完整的读取数据 

永久性 
事务提交以后,所做的工作就被永久的保存下来 

二 事务并发处理会产生的问题 

丢失更新 

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题、 
每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。 

脏读 
当第二个事务选择其它事务正在更新的行时,会发生未确认的相关性问题。 
第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。 

不可重复读 

当第二个事务多次访问同一行而且每次读取不同的数据时,会发生不一致的分析问题。 
不一致的分析与未确认的相关性类似,因为其它事务也是正在更改第二个事务正在读取的数据。 
然而,在不一致的分析中,第二个事务读取的数据是由已进行了更改的事务提交的。而且,不一致的分析涉及多次(两次或更多)读取同一行,而且每次信息都由其它事务更改;因而该行被非重复读取。 

幻像读 

当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。 
事务第一次读的行范围显示出其中一行已不复存在于第二次读或后续读中,因为该行已被其它事务删除。同样,由于其它事务的插入操作,事务的第二次或后续读显示有一行已不存在于原始读中。 

三 事务处理类型 

自动处理事务 

系统默认每个T-SQL命令都是事务处理 由系统自动开始并提交 

隐式事务 

当有大量的DDL 和DML命令执行时会自动开始,并一直保持到用户明确提交为止,切换隐式事务可以用SET IMPLICIT_TRANSACTIONS 
为连接设置隐性事务模式.当设置为 ON 时,SET IMPLICIT_TRANSACTIONS 将连接设置为隐性事务模式。当设置为 OFF 时,则使连接返回到自动提交事务模式 

用户定义事务 

由用户来控制事务的开始和结束 命令有: begin tran commit tran rollback tran 命令 

分布式事务 
跨越多个服务器的事务称为分布式事务,sql server 可以由DTc microsoft distributed transaction coordinator 
来支持处理分布式事务,可以使用 BEgin distributed transaction 命令启动一个分布式事务处理 


四 事务处理的隔离级别 

使用SET TRANSACTION ISOLATION LEVEL来控制由连接发出的所有语句的默认事务锁定行为 

从低到高依次是 

READ UNCOMMITTED 

执行脏读或 0 级隔离锁定,这表示不发出共享锁,也不接受排它锁。当设置该选项时,可以对数据执行未提交读或脏读;在事务结束前可以更改数据内的数值,行也可以出现在数据集中或从数据集消失。该选项的作用与在事务内所有语句中的所有表上设置 NOLOCK 相同。这是四个隔离级别中限制最小的级别。 

举例 

设table1(A,B,C) 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 

新建两个连接 
在第一个连接中执行以下语句 
select * from table1 
begin tran 
update table1 set c='c' 
select * from table1 
waitfor delay '00:00:10' --等待10秒 
rollback tran 
select * from table1 

在第二个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
print '脏读' 
select * from table1 
if @@rowcount>0 
begin 
waitfor delay '00:00:10' 
print '不重复读' 
select * from table1 
end 

第二个连接的结果 

脏读 
A B C 
a1 b1 c 
a2 b2 c 
a3 b3 c 

'不重复读' 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 


READ COMMITTED 

指定在读取数据时控制共享锁以避免脏读,但数据可在事务结束前更改,从而产生不可重复读取或幻像数据。该选项是 SQL Server 的默认值。 

在第一个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL READ COMMITTED 
begin tran 
print '初始' 
select * from table1 
waitfor delay '00:00:10' --等待10秒 
print '不重复读' 
select * from table1 
rollback tran 

在第二个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL READ COMMITTED 

update table1 set c='c' 

第一个连接的结果 

初始 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 

不重复读 
A B C 
a1 b1 c 
a2 b2 c 
a3 b3 c 


REPEATABLE READ 

锁定查询中使用的所有数据以防止其他用户更新数据,但是其他用户可以将新的幻像行插入数据集,且幻像行包括在当前事务的后续读取中。因为并发低于默认隔离级别,所以应只在必要时才使用该选项。 

在第一个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ 
begin tran 
print '初始' 
select * from table1 
waitfor delay '00:00:10' --等待10秒 
print '幻像读' 
select * from table1 
rollback tran 

在第二个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ 
insert table1 select 'a4','b4','c4' 

第一个连接的结果 

初始 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 

幻像读 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 
a4 b4 c4 

SERIALIZABLE 

在数据集上放置一个范围锁,以防止其他用户在事务完成之前更新数据集或将行插入数据集内。这是四个隔离级别中限制最大的级别。因为并发级别较低,所以应只在必要时才使用该选项。该选项的作用与在事务内所有 SELECT 语句中的所有表上设置 HOLDLOCK 相同。 

在第一个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
begin tran 
print '初始' 
select * from table1 
waitfor delay '00:00:10' --等待10秒 
print '没有变化' 
select * from table1 
rollback tran 

在第二个连接中执行以下语句 
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
insert table1 select 'a4','b4','c4' 

第一个连接的结果 

初始 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 

没有变化 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3 

五 事务处理嵌套的语法和对@@TRANCOUNT的影响 

BEGIN TRAN @@TRANCOUNT+1 
COMMIT TRAN @@TRANCOUNT-1 
ROLLBACK TRAN 

下面再给一些具体的例子 

--建立测试表的语句 
CREATE TABLE 帐户表 

账号 CHAR(4), 
余额 INT 

GO 
INSERT 帐户表 
SELECT 'A',100 
UNION ALL 
SELECT 'B',200 

1、排它锁测试 

--在第一个连接中执行以下语句 
BEGIN TRAN 
UPDATE 帐户表 SET 余额=300 WHERE 账号='A' 
WAITFOR DELAY '00:00:10' --等待10秒 
COMMIT TRAN 

--在第二个连接中执行以下语句 
BEGIN TRAN 
SELECT * FROM 帐户表 WHERE 账号<>'A' 
COMMIT TRAN 

/**说明: 
若同时执行上述两个语句,只要第一句更新的行数大于零,则第二句必须等待10秒。 
疑问:如果帐户A的余额本来就是300,第二个连接也会立即返回 
如果连接二中把SELECT * FROM 帐户表 改成 SELECT * FROM 帐户表 WITH ( NOLOCK),读取任何记录,都不需要等待 
或者在连接二使用READ UNCOMMITTED隔离级别也可 

执行语句一时,系统会在帐户表上使用TABLOCKX排它锁。该锁可以防止其它事务读取或更新表,并在语句或事务结束前一直持有。 
NOLOCK选项:该选项不发出共享锁,并且不要提供排它锁。当此选项生效时,可能会读取未提交的事务或一组在读取中间回滚的页面。有可能发生脏读。仅应用于 SELECT 语句。 
**/ 


2、 
--在第一个连接中执行以下语句 
BEGIN TRAN 
SELECT * FROM 帐户表 WHERE 账号='A' 
WAITFOR DELAY '00:00:10' 
COMMIT TRAN 

--在第二个连接中执行以下语句 
--A句 
UPDATE 帐户表 SET 余额=10 WHERE 账号='A' --要等待10秒,从而避免非重复读 
--B句 
UPDATE 帐户表 SET 余额=10 WHERE 账号='B' --不需要等待,立即执行 

--如果想在连接一中锁住整个表,不允许其他事务更新表中任何记录,但可以读取记录,可使用HOLDLOCK选项,即(HOLDLOCK 等同于 SERIALIZABLE) 
BEGIN TRAN 
SELECT * FROM 帐户表 WITH(HOLDLOCK) WHERE 账号='A' 
WAITFOR DELAY '00:00:10' 
COMMIT TRAN 

--如果想在连接一中锁住整个表,不允许其他事务更新表中任何记录甚至读取表中任何记录,可使用TABLOCKX选项,即(SET TRANSACTION ISOLATION LEVEL SERIALIZABLE做不到) 
BEGIN TRAN 
SELECT * FROM 帐户表 WITH(TABLOCKX) WHERE 账号='A' 
WAITFOR DELAY '00:00:10' 
COMMIT TRAN 


--如果想在连接一中不锁定表,允许其他事务更新表中任何行,使用NOLOCK选项,即 
BEGIN TRAN 
SELECT * FROM 帐户表 WITH(NOLOCK) WHERE 账号='A' 
WAITFOR DELAY '00:00:10' 
COMMIT TRAN 


SET TRANSACTION ISOLATION LEVEL REPEATABLE READ 
BEGIN TRAN 
SELECT * FROM 帐户表 WITH (ROWLOCK) WHERE 账号='A' 
WAITFOR DELAY '00:00:10' 
COMMIT TRAN 



3、死锁测试 
--增设 帐户表_2 
CREATE TABLE 帐户表_2 

账号 CHAR(4), 
余额 INT 

GO 
INSERT 帐户表_2 
SELECT 'C',100 
UNION ALL 
SELECT 'D',200 


--在第一个连接中执行以下语句 
BEGIN TRAN 
UPDATE 帐户表 SET 余额=3 WHERE 账号='A' 
WAITFOR DELAY '00:00:10' 
UPDATE 帐户表_2 SET 余额=3 WHERE 账号='C' 
COMMIT TRAN 

--在第二个连接中执行以下语句 
BEGIN TRAN 
UPDATE 帐户表_2 SET 余额=4 WHERE 账号='C' 
WAITFOR DELAY '00:00:10' 
UPDATE 帐户表 SET 余额=4 WHERE 账号='A' 
COMMIT TRAN 

--删除测试表 
DROP TABLE 帐户表,帐户表_2 

--同时执行,系统会检测出死锁,第一个连接的事务可能正常执行,SQL Server 终止第二个连接的事务(不涉及超时)。 
--如果没有出现死锁,则在其它事务释放锁之前,请求锁的事务被阻塞。 
--LOCK_TIMEOUT 设置允许应用程序设置语句等待阻塞资源的最长时间。 

4、更新数据时候允许进行插入 
5、插入数据时不允许更新\读取 

上面我们是以sql server2000为蓝本讲解的,实际上其和oracle在这方面还是有些不同的。 


一.事务设置及类型的区别 

在SQL Server中有三种事务类型,分别是:隐式事务、显式事务、自动提交事务,缺省为自动提交。 

自动提交,是指对于用户发出的每条SQL语句,SQL Server都会自动开始一个事务,并且在执行后自动进行提交操作来完成这个事务,也可以说在这种事务模式下,一个SQL语句就是一个事务。 

显式事务,是指在自动提交模式下以Begin Transaction开始一个事务,以Commit或Rollback结束一个事务,以Commit结束事务是把事务中的修改永久化,即使这时发生断电这样的故障。例如下面是SQL Server中的一个显式事务的例子。 

Begin Tran 

Update emp Set ename=’Smith’ Where empno=7369 

Insert Into dept Values(60,’HR’,’GZh’) 

Commit 

隐式事务,是指在当前会话中用Set Implicit_Transactions On命令设置的事务类型,这时任何DML语句(Delete、Update、Insert)都会开始一个事务,而事务的结束也是用Commit或Rollback。 

在Oracle中没有SQL Server的这些事务类型,缺省情况下任何一个DML语句都会开始一个事务,直到用户发出Commit或Rollback操作,这个事务才会结束,这与SQL Server的隐式事务模式相似。 

二.事务隔离级别 

在SQL92标准中,事务隔离级别分为四种,分别为:Read Uncommitted、Read Committed、Read Repeatable、Serializable,其中Read Uncommitted与Read Committed为语句级别的,而Read Repeatable与Serializable是针对事务级别的。 

在Oracle和SQL Server中设置事务隔离级别的语句是相同的,都使用SQL92标准语法,即: 

Set Transaction Isolation Level Read Committed 

上面示例中的Read Committed可以被替换为其他三种隔离级别中的任意一种。 

1.SQL Server中的隔离级别及实现机制 

在SQL Server中提供了所有这四种隔离级别。 

下面我们讨论在SQL Server中,这几种隔离级别的含义及其实现方式。 

Read Uncommitted:一个会话可以读取其他事务未提交的更新结果,如果这个事务最后以回滚结束,这时的读取结果就可能是错误的,所以多数的数据库应用都不会使用这种隔离级别。 

Read Committed:这是SQL Server的缺省隔离级别,设置为这种隔离级别的事务只能读取其他事务已经提交的更新结果,否则,发生等待,但是其他会话可以修改这个事务中被读取的记录,而不必等待事务结束,显然,在这种隔离级别下,一个事务中的两个相同的读取操作,其结果可能不同。 

Read Repeatable:在一个事务中,如果在两次相同条件的读取操作之间没有添加记录的操作,也没有其他更新操作导致在这个查询条件下记录数增多,则两次读取结果相同。换句话说,就是在一个事务中第一次读取的记录保证不会在这个事务期间发生改变。SQL Server是通过在整个事务期间给读取的记录加锁实现这种隔离级别的,这样,在这个事务结束前,其他会话不能修改事务中读取的记录,而只能等待事务结束,但是SQL Server不会阻碍其他会话向表中添加记录,也不阻碍其他会话修改其他记录。 

Serializable:在一个事务中,读取操作的结果是在这个事务开始之前其他事务就已经提交的记录,SQL Server通过在整个事务期间给表加锁实现这种隔离级别。在这种隔离级别下,对这个表的所有DML操作都是不允许的,即要等待事务结束,这样就保证了在一个事务中的两次读取操作的结果肯定是相同的。

2.Oracle中的隔离级别及实现机制 

在Oracle中,没有Read Uncommitted及Repeatable Read隔离级别,这样在Oracle中不允许一个会话读取其他事务未提交的数据修改结果,从而避免了由于事务回滚发生的读取错误。Oracle中的Read Committed和Serializable级别,其含义与SQL Server类似,但是实现方式却大不一样。 

在Oracle中,存在所谓的回滚段(Oracle9i之前版本)或撤销段(Oracle9i版本),Oracle在修改数据记录时,会把这些记录被修改之前的结果存入回滚段或撤销段中,就是因为这种机制,Oracle对于事务隔离级别的实现与SQL Server截然不同。在Oracle中,读取操作不会阻碍更新操作,更新操作也不会阻碍读取操作,这样在Oracle中的各种隔离级别下,读取操作都不会等待更新事务结束,更新操作也不会因为另一个事务中的读取操作而发生等待,这也是Oracle事务处理的一个优势所在。 

Oracle缺省的设置是Read Committed隔离级别(也称为语句级别的隔离),在这种隔离级别下,如果一个事务正在对某个表进行DML操作,而这时另外一个会话对这个表的记录进行读取操作,则Oracle会去读取回滚段或撤销段中存放的更新之前的记录,而不会象SQL Server一样等待更新事务的结束。 

在Serializable隔离级别(也称为事务级别的隔离),事务中的读取操作只能读取这个事务开始之前已经提交的数据结果。如果在读取时,其他事务正在对记录进行修改,则Oracle就会在回滚段或撤销段中去寻找对应的原来未经更改的记录(而且是在读取操作所在的事务开始之前存放于回滚段或撤销段的记录),这时读取操作也不会因为相应记录被更新而等待。 

三.DDL语句对事务的影响 

1.Oracle中DDL语句对事务的影响 

在Oracle中,执行DDL语句(如Create Table、Create View等)时,会在执行之前自动发出一个Commit命令,并在随后发出一个Commit或者Rollback命令,也就是说,DDL会象如下伪码一样执行: 

Commit; 

DDL_Statement; 

If (Error) then 

Rollback; 

Else 

Commit; 

End if; 

我们通过分析下面例子来看Oracle中,DDL语句对事务的影响: 

Insert into some_table values(‘Before’); 

Creaate table T(x int); 

Insert into some_table values(‘After’); 

Rollback; 

由于在Oracle执行Create table语句之前进行了提交,而在Create table执行后也会自动发出Commit命令,所以只有插入After的行被回滚,而插入Before的行不会被回滚,Create table命令的结果也不会被回滚,即使Create table语句失败,所进行的Before插入也会被提交。如果最后发出Commit命令,因为插入Before及Create table的操作结果已经在之前提交,所以Commit命令影响的只有插入After的操作。 

2.SQL Server中DDL语句对事务的影响 

在SQL Server中,DDL语句对事务的影响与其他DML语句相同,也就是说,在DML语句发出之前或之后,都不会自动发出Commit命令。 

在SQL Server 2000中,对于与上面Oracle同样的例子,最后发出Rollback后,数据库会回滚到插入Before之前的状态,即插入Before和After的行都会被回滚,数据表T也不会被创建。 

如果最后发出Commit操作,则会把三个操作的结果全部提交。 

四.用户断开数据库连接对事务的影响 

另外,对应于Oracle的管理客户端工具SQL*Plus,在SQL Server 2000中是osql,两种管理工具都是命令行工具,使用方式及作用也类似,但是在SQL*Plus中,用户退出连接时,会自动先发出Commit命令,然后再退出,而在osql中,如果用户退出连接,会自动发出Rollback命令,这对于SQL Server的自动提交模式没有什么影响,但如果处于隐式事务模式,其影响是显而易见的。对于两种数据库产品的其他客户端管理工具也有类似的不同之处。 



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mshust2006/archive/2007/01/26/1495088.aspx 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多