免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 11303 | 回复: 3
打印 上一主题 下一主题

隔离级别的学习和使用 [复制链接]

论坛徽章:
1
2017金鸡报晓
日期:2017-01-10 15:19:56
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-08-01 18:29 |只看该作者 |倒序浏览
   事务指定的隔离级别定义一个事务必须与其他事务所修改的资源或数据的隔离程度。隔离级别的确定标准,是允许哪些并发操作的副作用,如脏读或幻读。选择事务隔离级别不影响保护数据修改所需的锁定。事务始终对其修改的任何数据进行独占锁定,并在事务完成前保留该锁定,而不管为该事务设置的隔离级别。对于读取操作,事务隔离级别主要定义保护级别,以使其不受其他事务所作修改的影响。


一、隔离级别对锁定的影响
SQL 标准为 SQL 事务定义了四种隔离级别。每种隔离级别指定当前事务执行时所不允许的交互作用的类型
-即事务间是否相互隔离,或它们是否可以读取或更新被另一事务所使用的信息。较高隔离级别包括由较
低级别所施加的限制。

编号        名称说明
0        读取未提交的允许事务读取未提交的数据更改
1        读取已提交的仅允许事务读取已提交的数据更改
2        可重复读取事务可重复同一查询,事务读取过的任何行都不会被更新或删除
3        可串行化读取事务可重复同一查询,且得到完全相同的结果。不能插入任何将出现在结果集中的行


可以在会话期间为所有的 select 查询选择隔离级别,或者也可以在事务中为特定查询或表选择隔离级别。
在所有隔离级别上,所有更新都会获取排它锁,并在事务持续期间一直持有。
以下表作为例子。
Create table T_sample
(id char(4)
val int
)lock datarows

create index T_sample_idx on T_sample(id);
insert into T_sample(‘100’,100);
insert into T_sample(‘200’,200);
insert into T_sample(‘300’,300);
insert into T_sample(‘400’,400);
insert into T_sample(‘500’,500);
insert into T_sample(‘600’,600);



1、隔离级别 0,读取未提交的
级别 0 (又称读取未提交的)允许任务读取数据库中未提交的数据更改。这也称为脏读,
原因是任务会显示以后被回退的结果。下表显示了一个执行脏读的 select 查询。

序列      事务T1               事件                   事务T2
1      Begin tran               T1,T2开始事务            Begin tran
2    Update T_sample        T1修改Val值
    set val=val-100
      Where id=’1’        
3                                   T2在隔离级别0            select * from T_sample where id=’1’
                                     读取锁定数据                           at isolation 0
4                                    T2结束                    Commit tran
5     Rollback tran              T1结束         

如果 T2在 T1 更新表后但在回退更改前查询该表, T2所计算的数量会少 100。
事务 T1 中的 update 语句在 T_sample 上获取一个排它锁。但是事务 T2 在查询
T_sample 前并不试图获取共享锁,因此它并没有被 T1 阻塞。反过来也一样。
如果 T1 在 T2 开始之前开始以隔离级别 0 查询T_sample,在 T2 执行时,
T1 仍然能够在 T_sample 上获取其排它锁,原因是 T2 在它所读取的页上不持有任何锁。

2、隔离级别 1,读取已提交的
级别 1 (又称读取已提交的)可防止脏读。级别 1 的查询只读取已提交的数据更改。
在隔离级别 1,如果事务需要读取被另一会话中未完成的事务修改过的行,该事务将等待,
直到第一个事务完成(提交或回退)
序列    事务T3                   事件                 事务T4
1       Begin tran            T3,T4开始事务          Begin tran
2      Update T_sample    T3修改Val值
     set val=val-100
      Where id=’1’                                       
3                                 T4试图获得共享锁       select * from T_sample where id=’1’
                                   以查询帐户,但是
                                   必须等到 T3 释放其锁。
        
4      Rollback tran           T3结束并释放锁
5                                  T4获得共享锁,查       Commit tran  
                                    询,并结束
        

3、隔离级别 3,可串行化读取
级别 3 可防止幻像。在一个事务读取了满足搜索条件的一组行,而第二个事务修改了数据
(通过 insert、delete 或 update 语句)时,便会产生幻像行。如果第一个事务按相同的
搜索条件重复读取,它将获得一组不同的行。在下表中,在隔离级别 1 运行的事务 T6 在
第二个查询中发现了一个幻像行。

        事务中的幻像(在隔离级别1的条件下)
序列     事务T5                 事件                         事务 T6
1      Begin tran              T5,T6开始事务            Begin tran
2                                 T6读取数据                   Select val from T_sample where id=’1’
3   Update T_sample       T5修改数据  
    set val=0
      Where id=’1’        
4    Commit tran             T5结束
5                                 T6查询获得了一个新值     Select val from T_sample where id=’1’
6                                 T6结束                        Commit tran

4、避免事务中的幻像(在隔离级别3的条件下)
序列         事务T7             事件                        事务 T8
1          Begin tran        T7,T8开始事务                Begin tran
2                                T7读取数据,并         Select val from T_sample where id=’1’
                                 获取共享锁        
3     Update T_sample      T8试图修改数据,
         set val=0               但排它锁被T7的
        Where id=’1’        共享锁阻塞
4                                T8查询获得了一个       Select val from T_sample where id=’1’
                                 相同的值
5                              T8结束,并释放共享锁          Commit tran
6      Commit tran        T7获取排它锁完成修改,结束
        

5、Adaptive Server 缺省隔离级别
Adaptive Server 的缺省隔离级别是 1,即防止脏读。可以用
set transaction isolation level *来指定隔离级别。
可通过在 select 或 readtext 命令中将 holdlock、noholdlock 和 shared
选项应用到单个表来替换某个会话的锁定级别:
使用级别   关键字            作用
1         noholdlock       直到事务结束再持有锁;在级别 3 使用以强制执行级别 1
2,3     holdlock          持有共享锁直到事务完成;在级别 1使用以强制执行级别 3
无        shared            在为更新操作打开游标时,对 select语句应用共享锁而非更新锁

6、使用 at isolation 子句
可通过使用带 select 或 readtext 命令的 at isolation 子句,更改查询中所有
表的隔离级别。at isolation 子句中的选项有:

使用级别     选项                   作用
0         read uncommitted     读取未提交的更改;使用级别 1、2或 3 查询执行脏读(级别 0)
1         read committed        只读取已提交的更改;等待锁被释放;在级别 0 使用,仅读取已提交的更改,但不持有锁
2         repeatable read        持有共享锁直到事务完成;使用级别 0 或级别 1 查询以强制执行级别2
3          serializable             持有共享锁直到事务完成;使用级别 1 或级别 2 查询以强制执行级别3

二 esql/c中隔离级别应用
1、esql/c缺省的配置项目
[DEFAULT]
;This is the default section loaded by applications that use the
;external configuration feature, but which do not specify their
;own application name. Initially this section is empty.Defaults
;from all properties will be the same as earlier releases of
;Open Client libraries.
[ANSI_ESQL]
;This section defines configuration which an ANSI conforming
;Embedded SQL application should use to get ANSI-defined
;behavior from SQL Servers and Open Client libraries. This set of
;configuration ;properties matches the set which earlier
;releases of Embedded SQL (version 10.0.x) automatically set for
;applications duringexecution of a CONNECT statement.
CS_CAP_RESPONSE=CS_RES_NOSTRIPBLANKS
CS_EXTRA_INF=CS_TRUE
CS_ANSI_BINDS=CS_TRUE
CS_OPT_ANSINULL=CS_TRUE
CS_OPT_ANSIPERM=CS_TRUE
CS_OPT_STR_RTRUNC=CS_TRUE
CS_OPT_ARITHABORT=CS_FALSE
CS_OPT_TRUNCIGNORE=CS_TRUE
CS_OPT_ISOLATION=CS_OPT_LEVEL3
CS_OPT_CHAINXACTS=CS_TRUE
CS_OPT_CURCLOSEONXACT=CS_TRUE
CS_OPT_QUOTED_IDENT=CS_TRUE
;End of default sections

在esql/c的开发中,应用缺省的隔离级别为3。利用上面的各种隔离级别所做的分析,可以在应用中加以灵活的使用。

2、案例一:应用读取T_sample表中相应ID的val值,根据输入的val值等情况对val进行修改。

exec sql select val into :wk_val from T_sample where id =:wk_id;
If (wk_val<wk_input_val)
{
  perform_a();
  wk_val=wk_val + wk_input_val;
}
else
{
  perform_b();
  wk_val=wk_val + wk_input_val;
}
exec sql update T_sample set val=:wk_val where id=:wk_id;

假设我们手工用set transaction isolation level 1的语句将程序的隔离级别置成了1。
2.1、假设两个程序串行执行。
序列     事务T9               事件                           事务 T10
1         Begin tran           T9,T10开始事务        Begin tran
2   select val into            T9
  :wk_val from T_sample   id=’1’
  where id =:wk_id;         wk_input_val=50         
   wk_val的值为100        
3 wk_val=wk_val +          T9 wk_val=150
   wk_input_val               
4  Update T_sample         T9
   Set val=150                 执行修改,将val值
  Where id=’1’              改成了150
5                                  T10                         select val into :wk_val from
                                    id=’1’,                     T_sample where id =:wk_id;
                                    wk_input_val=150            wk_val的值为150         
6                                   T10 wk_val=300          wk_val=wk_val + wk_input_val
7                                    T10                         Update T_sample
                                执行修改,将val值           Set val=300
                                     改成了300                  Where id=’1’

2.2、考虑两个程序并行处理的可能。

序列           事务T9                   事件                             事务 T10
1       Begin tran                 T9,T10开始事务                  Begin tran
2       select val into            T9
   wk_val from T_sample      id=’1’
  where id =:wk_id;            wk_input_val=50        
     wk_val的值为100        
3                                    T10                                 select val into :wk_val
                                        id=’1’,                       from T_sample where id =:wk_id;
                                      wk_input_val=150               wk_val的值为100
4   wk_val=wk_val +           T9 wk_val=150
      wk_input_val               
5                                     T10 wk_val=250                wk_val=wk_val + wk_input_val
6   Update T_sample           T9
      Set val=150                 执行修改,将val值改成了150
      Where id=’1’        
7                                      T10                                     Update T_sample
                                        执行修改,将val值改成了250          Set val=250
                                                                                  Where id=’1’  

2.3、也存在可能由于perform_a的耗时笔perform_b耗时长,导致T10先进行Update。那最后的结果将变成。
序列           事务T9                   事件                             事务 T10
6                                          T10                             Update T_sample
                                       执行修改,将val值改成了250        Set val=250
                                                                                  Where id=’1’   
7    Update T_sample            T9
      Set val=150               执行修改,将val值改成了150
      Where id=’1        
同一个程序,由于执行顺序的不同造成了不同的结果,显而易见这个结果并不是程序想要真正获得的,
而是由于出现了幻像导致结果不真实。

2.4 在缺省的隔离级别为3的情况下,串行执行的情况不会出现错误,
考虑并行执行的情况,看sybas是如何避免这种幻像的。
序列          事务 T9                   事件                              事务 T10
1            Begin tran               T9,T10开始事务                      Begin tran
2           select val                  T9
                into :wk_val            id=’1’
             from T_sample            wk_input_val=50
              where id =:wk_id;     并在T_sample表上加共享锁。
           wk_val的值为100        
3                                          T10                                       select val into :wk_val
                                           id=’1’,                               from T_sample where id =:wk_id;
                                            wk_input_val=150                 wk_val的值为100
                                            并在T_sample表上加共享锁         
4             wk_val=wk_val +       T9 wk_val=150
               wk_input_val               
5                                          T10 wk_val=250                     wk_val=wk_val + wk_input_val
6             Update T_sample       T9  
               Set val=150             但T9上的排它锁将        
          Where id=’1’             被T10上的共享锁所阻塞
7                                          T10                                      Update T_sample
                                            试图在T_sample表上排它锁,      Set val=250
                                            从而产生死锁。                        Where id=’1’
                                            系统会自动会退一个事务来解锁。        
2.6 当然,上面的例子也可以通过手工对表加锁的方法来避免幻像。
exec sql update T_sample set val=val where id=:wk_id;
exec sql select val into :wk_val from T_sample where id =:wk_id;
If (wk_val<wk_input_val)
{
  perform_a();
  wk_val=wk_val + wk_input_val;
}
else
{
  perform_b();
  wk_val=wk_val + wk_input_val;
}
exec sql update T_sample set val=:wk_val where id=:wk_id;
通过第一个update val=val的语句在该行记录上产生排它的锁。从而阻塞其它的访问请求,
但这种情况下,其它共享锁在该记录上也不能获得,只读的请求也无法相应,系统的相应性
较差。

3、案例二。对表T_sample执行以下操作,更新id=’X’的记录的val值到执行的数,如果没有
id=’X’的记录就插入一条记录。
我们通常用下面的语句来执行

EXEC SQL UPDATE T_sample SET val=:wk_val WHERE id=:wk_id;
if (sqlca.sqlcode==100)
   {
                EXEC SQL INSERT T_sample VALUES(:wk_id,:wk_val);
        }

3.1、在缺省隔离级别为3的情况下,当执行 UPDATE T_sample SET val=100 WHERE id=’100’ ,
T_sample表有id=’100’的记录。sp_lock 显示:
fid    spid   loid        locktype                     table_id    page
         row    dbname          class
         context
------ ------ ----------- ---------------------------- ----------- -----------
         ------ --------------- ------------------------------
         ----------------------------
      0     29          58 Ex_intent                      735738288           0
              0 tempdb          Non Cursor Lock

      0     29          58 Ex_row                         735738288      104552
              0 tempdb          Non Cursor Lock
         Range
该sql会在表T_sample的104554的第0行加上Ex_row锁,在表上加Ex_intent锁。

当执行 UPDATE T_sample SET val=110 WHERE id=’110’ ,T_sample表中没有id=’110’的记录。
用sp_lock 显示:
fid    spid   loid        locktype                     table_id    page
         row    dbname          class
         context
------ ------ ----------- ---------------------------- ----------- -----------
         ------ --------------- ------------------------------
         ----------------------------
      0    361         722 Ex_intent                      735738288           0
              0 tempdb          Non Cursor Lock

      0    361         722 Sh_row                        735738288      104554
              1 tempdb          Non Cursor Lock
         Range
该sql语句会在104554页的第二行上加Sh_row锁。(“Range”表示范围锁,用于以事务隔离级别 3
执行的一些范围查询。)和前面的能查到记录时的Ex_row锁有所不同。这个不同也会给应用程序造成意想不到的结果。

3.2、当使用上述的程序id=200 val=400,和id=300,val=600 作为参数,分别带入进行测试。
序列            事务 T11                事件                          事务 T12
1             Begin tran              T11,T12开始事务             Begin tran
2          Update T_sample        T11 执行
         set val=200                  会在T_sample 104554
             where id=’200’       页的第1行上加一个Ex_row锁        
3                                         T12 执行                      Update T_sample set
                                           在T_sample 104554页     val=300 where id=’300’
                                           的第2行上加一个Ex_row锁        
4         Commit tran                 T11 提交
5                                          T12 提交                     Commit tran

3.3、将id=200 val=400,和id=150,val=300 作为参数分别带入进行测试。
序列           事务 T13                  事件                                 事务  T14
1              Begin tran               T13,T14开始事务                  Begin tran
2             Update T_sample      T13 执行
                 set val=200           会在T_sample 104554页
              where id=’200’      的第1行上加一个Ex_row锁        
3                                           T14 执行由于在表中              Update T_sample set
                                             没有id=’150’的记录,将      val=300 where id=’150’
                                             在T_sample 104554页
                                             的第1行上加一个Sh_row锁,
                                             但这个共享锁将被T13在该
                                             行上的排它锁阻塞        
4         Commit tran                   T13        提交
5                                            T14 获得锁                            Insert        
6                                            T14 执行insert                     Commit tran
                                                   并提交        

3.4、将id=310 ,val=620和id=320,val=640  这两个都不存的记录作为参数,分别带入进行测试。
序列          事务 T15                 事件                                事务 T16
1           Begin tran                 T15,T16开始事务                   Begin tran
2         Update T_sample          T15 执行由于没有id=’310’
            set val=620                的记录会在T_sample  
           where id=’310’          104554页的第2行
                                            上加一个Sh_row锁        
3                                         T16 执行由于在表中                 Update T_sample set
                                            没有id=’320’的记录,将      val=640 where id=’320’
                                           在T_sample 104554页的
                                           第2行上加一个Sh_row锁。         
4    Insert into T_sample            T15 执行,试图插入
   values(‘310’,620)                记录在104554的第2行
                                             上插入记录,但被T16的
                                               Sh_row阻塞。
5                                            T16 执行,也试图插入         Insert into T_sample
                                              记录在104554的第2行上        values(‘320’,640)
                                              插入记录,但被T15的阻
                                              塞产生死锁。        
                          

4、再在隔离级别为1的情况下来看上面案例二。
当我们执行 UPDATE T_sample SET val=100 WHERE id=’100’ ,T_sample表有id=’100’的记录。sp_lock 显示:
fid    spid   loid        locktype                     table_id    page
         row    dbname          class
         context
------ ------ ----------- ---------------------------- ----------- -----------
         ------ --------------- ------------------------------
         ----------------------------

0    229         458 Ex_intent                      735738288           0
              0 tempdb          Non Cursor Lock

0    229         458 Ex_row                         735738288      104552
              0 tempdb          Non Cursor Lock

当我们执行 UPDATE T_sample SET val=110 WHERE id=’110’ ,T_sample表中没有id=’110’的记录。
用sp_lock 显示:
fid    spid   loid        locktype                     table_id    page
         row    dbname          class
         context
------ ------ ----------- ---------------------------- ----------- -----------
         ------ --------------- ------------------------------
         ----------------------------

      0     94         188 Ex_intent                      735738288           0
              0 tempdb          Non Cursor Lock
和隔离级别为3的情况相比,没有那个Sh_row锁。
   4.1、将id=310 ,val=620和id=320,val=640  这两个都不存的记录作为参数,分别带入进行测试。考虑并行的情况。
序列     事务T17                  事件                         事务 T18
1         Begin tran              T17,T18开始事务        Begin tran
2  Update T_sample           T17 执行由于没有
   set val=620                   id=’310’的记录
   where id=’310’            只会在T_sample
                                      上产生表级的Ex_intent锁        
3                                   T18 执行由于在表中          Update T_sample set
                                    没有id=’320’的记录,     val=640 where id=’320’
                                    只会在T_sample上产生
                                    表级的Ex_intent锁
4Insert into T_sample        T17 执行,插入数据
  values(‘310’,620)                 
5                                   T18 执行,插入数据。      Insert into T_sample values(‘320’,640)
6 Commit tran                  T17完成。        
7                                   T18完成。                      Commit tran
在隔离级别为1的情况下,同样的例子就不会造成死锁。

[ 本帖最后由 chuxu 于 2007-8-28 11:13 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2007-08-06 08:57 |只看该作者
好文!够详细

论坛徽章:
0
3 [报告]
发表于 2007-09-05 16:16 |只看该作者
  详细的好文,对于编写多并发模式的应用,尤其具有指导意义

论坛徽章:
0
4 [报告]
发表于 2008-05-28 14:53 |只看该作者
太好了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP