`

事务隔离级别

阅读更多
测试表脚本:

SQL SERVER

CREATE TABLE 
[ Customer ](

    [
CustID ] [ int NOT NULL ,

    [
Fname ] [ nvarchar ]( 20 ),

    [
Lname ] [ nvarchar ]( 20 ),

    [
Address ] [ nvarchar ]( 50 ),

    [
City ] [ nvarchar ]( 20 ),

    [
State ] [ nchar ]( 2 ) DEFAULT ( 'CA' ),

    [
Zip ] [ nchar ]( 5 NOT NULL ,

    [
Phone ] [ nchar ]( 10 )

)

insert into customer values ( 1 'Gary' 'Mckee' '111 Main' 'Palm Springs' 'CA' 94312 7605551212 )

insert into customer values ( 2 'Tom' 'Smith' '609 Geogia' 'Fresno' 'JP' 33045 5105551212 )

insert into customer values ( 3 'Jams' 'bond' 'ST Geogie 21' 'Washington' 'NY' 20331 4405551864 )



ORACLE

CREATE TABLE Customer
(

    
CustID int NOT NULL ,

    
Fname nvarchar2 ( 20 ),

    
Lname nvarchar2 ( 20 ),

    
Address nvarchar2 ( 50 ),

    
City nvarchar2 ( 20 ),

    
State nchar ( 2 ) DEFAULT  'CA' ,

    
Zip nchar ( 5 NOT NULL ,

    
Phone nchar ( 10 )

);

insert into customer values ( 1 'Gary' 'Mckee' '111 Main' 'Palm Springs' 'CA' 94312 7605551212 );

insert into customer values ( 2 'Tom' 'Smith' '609 Geogia' 'Fresno' 'JP' 33045 5105551212 );

insert into customer values ( 3 'Jams' 'bond' 'ST Geogie 21' 'Washington' 'NY' 20331 4405551864 );



1。Sqlserver与oracle单条语句处理对比

SQL SERVER单条语句默认自动提交,即单条语句自动作为一个事务处理;而oracle的原则是尽量延后提交,除非遇到显式提交命令或者DDL语句。

SQL SERVER

打开事务1
:

运行:select  from customer

可以看到表有3条记录

运行:insert into customer values
( 4 'Hello' 'world' 'paradise road 01' 'heaven' 'XY' 00001 1234564321 )

转到事务2:

运行:select 
from customer

可以看到事务1中刚插入的custid为4的记录。



ORACLE

打开事务1,运行:

select 
from customer ;

可以看到表有3条记录,运行:

insert into customer values
( 4 'Hello' 'world' 'paradise road 01' 'heaven' 'XY' 00001 1234564321 );

转到事务2,运行:

select 
from customer ;

能看到的还是3条记录,事务1中刚插入的一条记录未自动提交,看不到。

转到事务1,运行:

commit
;

转到事务2,运行:

select 
from customer ;

现在能看到4条记录了。





2. 丢失更新

Sqlserver完全兼容ANSI 92标准定义的4个隔离级别。它的默认隔离级别是提交读(read committed),在该级别下,可能会有丢失更新的问题。Oracle的默认情形也一样。故不再重复。

SQL SERVER

打开事务1运行:

set transaction isolation level read committed

begin tran

select 
from customer         -- 看到3条记录

现在切换到事务2,此时事务1还未结束。在事务2中运行:

set transaction isolation level read committed

begin tran

select 
from customer         -- 看到3条记录,和事务1中相同

现在假设事务1事务继续运行,修改数据并提交:

update customer set state 
'TK'  where CustID  3

commit

回到事务2,事务2根据先前查询到的结果修改数据:

update customer set Zip 
99999 where state  'NY'

commit

结 果因为事务1已经修改了事务2的where条件数据,事务2未成功修改数据(其实准确的说应该算是幻象读引起的更新失败。不过若满足条件的记录数多的话, 事务2的update可能更新比预期的数量少的记录数,也可算“丢失”了部分本应完成的更新。个人认为只要明白实际上发生了什么即可,不必过分追究字 眼)。丢失更新还可能有别的情形,比如事务2也是

update customer set state 
'KO'  where CustID  3

两个事务都结束后,事务2的结果反映到数据库中,但事务1的更新丢失了,事务2也不知道自己覆盖了事务1的更新。





3.脏读演示

sqlserver的默认隔离级别是提交读(read committed),当手工将其改为未提交读时,事务可以读取其它事务没提交的数据;oracle由于自身特殊实现机制,可以理解为自身基础的隔离级别就是可重复读(与ANSI标准还是有区别的,后面例子会说明)。

SQL SERVER

打开事务1,运行:

begin tran

select 
from customer

    update customer set state 
'TN'  where CustID  3

转到事务2,运行:

set transaction isolation level read uncommitted

begin tran

select 
from customer

此时看到的数据是事务1已经更新但还未提交的(3号记录state值TN)。而如果事务1发觉数据处理有误,转到事务1,进行回滚:

    Rollback

此时事务2如根据刚读取的数据进一步处理,会造成错误。它读取的数据并未更新到数据库,是“脏”的。



ORACLE

ANSI 定义未提交读(read uncommitted)级别本意不是为了故意引入错误,而是提供一种可能的最大并发程度级别,即一个事务的数据更新不影响其它 事务的读取。Oracle从内核层面实现了更新数据不阻塞读。可以说它提供未提交读级别的兼容,但没有脏读问题。(详情参考对应PPT文档)故 oracle没有手工设置read uncommitted级别的语句。





4.不可重复读

Sql server 的默认级别没有脏读问题,但存在不可重复读问题。Oracle默认级别也是提交读,不过它因为自身特殊机制,在语句一级不存在不可重复读问题。也就是说当 运行时间较长的查询时,查询结果是与查询开始时刻一致的(即使查询过程中其它事务修改了要查询的数据),而SQL SERVER就存在问题 (sql server 2005新特性提供了可选的语句一级一致性支持,叫做行版本机制,实际上可以说是照着oracle的多版本来的,大体原理差不 多)。

由于语句一级的事务一致性难以演示,下面例子是事务一级,提交读隔离级别下发生的不可重复读现象:

SQL SERVER

打开事务1,运行:

set transaction isolation level read committed

begin tran

select 
from customer where State  'CA'

可以得到1条记录,这个时候事务2中运行:

set transaction isolation level read committed

begin tran

update Customer set state 
'JP'  where state  'CA'

commit

事务2插入一条记录并提交。回到事务1,事务1继续运行,此时它再次相同的查询,并借此作进一步修改,却发现读取到的数据发生了变化。

select 
from customer where State  'CA'

-- 2次读取不一致,之后的数据处理应该取消。否则不正确

update Customer set city 
'garden'  where state  'CA'

commit

读取未能获得记录。也就是说在同一事务中两次相同的查询获得了不同的结果,产生读取不可重复现象。



ORACLE

尽管oracle在默认隔离级别下提供了语句级的事务读一致性,但在事务级仍然是会出现不可重复读现象。和sql server一样,故不再重复。





5.幻像读

当sqlserver的隔离级别设置为可重复读(repeatable read),可以解决上面例子出现的问题。其内部是通过事务期间保持读锁来实现的。

SQL SERVER

开始事务1,修改事务级别为可重复读,执行:

set transaction isolation level repeatable read

begin tran

select 
from customer where State  'CA'

和上例一样得到1条记录,这个时候事务2中运行:

set transaction isolation level repeatable read

begin tran

update Customer set state 
'JP'  where state  'CA'

commit

会发现事务2一直等待,并不结束。返回事务1,运行:

select 
from customer where State  'CA'         -- 2次读取结果一致

update Customer set city 
'garden'  where state  'CA'

commit

事务2成功结束后,再返回事务1,发现事务1也完成了。通过锁机制阻塞其它事务的修改,保持了事务期间读取的一致性。然而,如果是插入数据,则还是会出现问题:

开始事务1,修改事务级别为可重复读,执行:

set transaction isolation level repeatable read

begin tran

select 
from customer where State  'CA'

得到1条记录,这个时候事务2中运行:

set transaction isolation level repeatable read

begin tran

insert into customer values
( 4 'hellow' 'world' 'paradise 001' 'garden' 'CA' 00000 1119995555 )

commit

发现事务2立刻提交并正常结束了。返回事务1,运行:

select 
from customer where State  'CA'

会发现得到了2条记录。这种现象就叫做幻像读。



ORACLE

由于自身特殊的机制,oracle没有提供一致读隔离级别的选项,想要获得一致读的效果,实际上需要将事务提升到串行化等级,即serializable。





6.串行化级别不同数据库实现

在这个级别,可以认为事务中的数据无论何时都是一致的,此级别使它显得好像没有其它用户在修改数据,数据库在事务开始时候被“冻结”(至少,对于本事务涉及的数据如此)。然而在不同数据库中,其实现机制也不同。

SQL SERVER

开始事务1,运行:

set transaction isolation level serializable

begin tran

select 
from customer where State  'CA'

会得到1条记录,这时事务2开始运行:

set transaction isolation level serializable

begin tran

insert into customer values
( 4 'hellow' 'world' 'paradise 001' 'garden' 'CA' 00000 1119995555 )

commit

会发现事务2挂起,它在等待事务1结束。回到事务1,继续:

select 
from customer where State  'CA'

update Customer set city  'garden'  where state  'CA'

commit

在片刻的等待以后,事务1得到类似以以下格式消息:

消息1205,级别13,状态56,第1 行

事务
( 进程ID 51 ) 与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品。请重新运行该事务。

而 事务2更新了数据并正常结束。这是因为两个事务都设置成了串行化级别,当遇到冲突时候,sql server根据一定的规则选择牺牲掉其中一个事务,来保 证事务的串行性。上面的例子,如果将事务2的隔离级别改为提交读,那么事务2会等待事务1完成,之后自己正常完成(因为事务2没有串行需求,不会有死 锁)。



ORACLE

在oracle中,通过多版本,可以在一定程度上避免死锁。

开始事务1,运行:

set transaction isolation level serializable
;

select  from customer where State  'CA' ;    -- set tran语句隐式开始事务

得到1条记录,然后事务2开始运行:

set transaction isolation level serializable
;

insert into customer values ( 4 'hellow' 'world' 'paradise 001' 'garden' 'CA' 00000 1119995555 );

commit ;

可以发现事务2立刻完成,没有阻塞。回到事务1继续:

select 
from customer where State  'CA' ;

update Customer set city  'garden'  where state  'CA' ;

commit ;

事务1中的第二次查询和事务开始时刻一致,就好像事务2已经完成的提交不存在。事务最终正常更新完毕,并保持了“事务开始”时刻的数据一致性。

然而,如果事务1,2修改同样的数据行,也会有错误,

开始事务1,运行:

set transaction isolation level serializable
;

select  from customer where State  'CA' ;    -- set tran语句隐式开始事务

得到1条记录,然后事务2开始运行:

set transaction isolation level serializable
;

update customer set state  'KO'  where state  'CA' ;

commit ;

可以发现事务2立刻完成,没有阻塞。回到事务1继续:

select 
from customer where State  'CA' ;

update Customer set city  'garden'  where state  'CA' ;

commit ;

出现错误信息:

第 1 行出现错误
:

ORA - 08177 无法连续访问此事务处理

总的来说,oracle利用多版本的方式实现串行化级别更少造成死锁,除非两个事务修改了相同的数据行,一般也不会造成冲突。







7.不同隔离级别的相互影响

前 面的例子基本都是两个相同隔离级别事务的情况。如果不同隔离级别的事务发生冲突,会有什么不同吗?实际上,对于一个事务来说,其它事务的隔离级别对它来说 是未知的,更进一步,甚至数据库中有没有其它事务,有多少事务也不知道。影响事务访问数据就两方面因素:该事务自身的隔离级别,该事务要访问的数据上面锁 的状态。

SQL SERVER

开始事务1,运行:

set transaction isolation level serializable

begin tran

select 
from customer where State  'CA'

事务1的查询获得1条记录,转到事务2,运行:

set transaction isolation level read uncommitted

begin tran

select 
from customer

事务2查询获得3条记录,回到事务1,运行:

update Customer set city 
'garden'  where state  'CA'

切换到事务2,运行:

select 
from customer

update customer set state 
'KO'  where state  'CA'

commit ;

因为事务2隔离级别为未提交读,因此事务1中刚作的修改可以立刻从查询看到,即使事务1还未结束。进一步的update因为事务1对记录加了独占锁,因此事务2挂起。回到事务1将其提交:

Commit

事务1正常结束,独占锁释放,从而让事务2得以继续修改数据,并最终完成。



ORACLE

Oracle数据库的隔离级别设置语句只有read committed和serializable(read only暂不讨论),加上其特殊锁机制,不同隔离级别事务间的影响除了上例(例6)中两个都为serializable的情况,其它都可视为互不阻塞。



8.页锁与行锁(限sql server)

Sql server的锁可以到行一级。然而它又存在自动的锁定扩大,锁定转换。因此存在一些意想不到的情况。下面是演示:

开始事务1,运行:

set transaction isolation level read committed

begin tran

select 
from customer where State  'CA'

update Customer set city  'garden'  where state  'CA'

理论上来说,在提交读级别下,上面的update语句只是在state值为CA的数据行上加了独占锁,表中其它数据应该可以被其它事务更新,然而,如下事务2开始:

set transaction isolation level read committed

begin tran

select 
from customer

update customer set state 
'KO'  where state  'JP'

commit

发现事务2陷入阻塞状态。尽管它们更新的不是同一条记录。回到事务1,运行:

Commit

事务1结束后事务2才继续运行至结束。

如果我们在表上加入索引,如下:

CREATE NONCLUSTERED INDEX 
[ idx_state ON  [ dbo ].[ Customer ] (    [ State ])

再重复上面的步骤,会发现阻塞不再存在。

PS:这种现象应该和数据库默认加锁参数
/ 机制有关,应该可以调整,但目前手中没有进一步资料。故仅罗列了现象。



ORACLE

Oracle在数据一级只有一种数据行上的锁,因此不存在sql server中这些问题。



9.Set transaction语句的作用周期

前面所有的例子,都是在会话窗口中进行的演示。一旦使用了set transaction语句,会影响整个会话。除非再显式改变隔离级别,否则将保持到会话结束。例如:

开始事务1,假设会话一开始处于SQL SERVER的默认隔离级别(read committed):

begin tran

select 
from customer where State  'CA'

select  from sys . dm_tran_locks

系统视图sys
. dm_tran_locks可以查看当前的加锁情况,到目前位置,只有数据库级的锁。继续运行:

set transaction isolation level repeatable read

select 
from customer where State  'CA'

select  from sys . dm_tran_locks

commit

接下来的语句改变了隔离级别到可重复读,接下来的查询,会看到行级锁的记录。在上面事务提交后,运行:

begin tran

select 
from customer where State  'CA'

select  from sys . dm_tran_locks

commit

仍然会从视图sys
. dm_tran_locks看到行级锁记录。整个会话期间都受到影响。



但是,如果调用存储过程,函数,则过程
/ 函数中事务隔离级别的改变并不会对调用环境造成影响。可以通过以下例子说明,首先,创建一个存储过程脚本:

CREATE PROCEDURE 
[ dbo ].[ test_tran_level ]

AS

BEGIN

    BEGIN TRAN

    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

    SELECT 
FROM CUSTOMER

    UPDATE CUSTOMER SET STATE 
'SS'  WHERE CustID  3

    SELECT 
FROM sys . dm_tran_locks

    COMMIT

END

然后,在会话窗口调用该过程,会话窗口当前隔离级别为默认的提交读:

Exec test_tran_level

运行的结果可以看到读取锁信息,再在会话中运行:

begin tran

select 
from customer where State  'CA'

select  from sys . dm_tran_locks

commit

视图sys
. dm_tran_locks并未有读锁的记录,说明事务隔离级
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics