不同的数据库产品在实现事务隔离级别时可能会采用不同的方法,目前主要有两类方法:一种是采用"悲观"方法,这种方法主要采用锁定技术(locking)来实现不同的隔离级别(例如:SAP Sybase ASE);另一类方法主要采用MVCC(即多版本并发控制,是一种“乐观”方法)技术再辅之以少量的锁实现(例如:Oracle)。
SAP Sybase采用的是"悲观"的锁技术实现,不同的隔离级别下,SQL语句的加锁情况和锁的持续时间会有不同。对于ASE数据库的开发人员和DBA来说,掌握ASE事务原理和并发控制技术(锁定技术)是非常重要的;同时,由于ASE采用了较为复杂的"悲观锁"技术实现并发控制,对于ASE数据库的初学者(甚至一些已经有过使用经验的人)来说可能会是难点。本文和后续的文章中将尽量采用通俗易懂的语言,并结合具体的例子向大家介绍ASE数据库是如何做到事务隔离级别所规定的控制的。
ANSI SQL定义的事务隔离级别是1(READ_UNCOMMITTED)、2(READ_COMMITTED)、3(REPEATABLE_READ)、4(SERIALIZABLE);而在ASE数据库中,对应隔离级别的编号分别为0、1、2、3。本文后面的内容将使用ASE的命名编号。
ASE中锁定基础知识
在介绍ASE中隔离级别1(READ_COMMITED)实现的基本原理之前,先向大家介绍一下ASE中基本的锁定知识,这些知识来自于SAP Sybase ASE官方手册中的内容,在此做了一些简化以便读者能够更好的理解它们。
1. 锁定技术简介
锁定(locking)是一种并发控制机制,ASE通过锁定技术对活动事务中访问到的数据行、数据页或数据表加锁来保护它们,从而在并发环境下确保数据在事务中和事务之间保持一致性(即事务ACID属性中的C)和隔离性(即事务ACID属性中的I)。
2. 锁定粒度
锁定粒度是指一次可锁定多少数据。ASE支持行、页和表级锁定。一般来说,锁定的粒度越小,开销越大(即处理锁定请求的工作量和资源代价),并发能力越高;锁定的粒度越大,开销越小,并发能力越低。
3. 锁定方案
ASE提供了以下锁定方案:
* 数据行锁定(datarows locking): 仅锁定数据行.
* 数据页锁定(datapages locking): 仅锁定数据页.
* 所有页锁定(allpages locking): 锁定数据页和索引页.
目前用户使用比较多的是“行锁定”,本文中的例子也使用的是这种方式,下面简单地向大家介绍一下ASE的数据行锁定知识:
"数据行锁定"是在事务所访问到的相应记录行上加"行级锁",而相应索引页面中的索引行(索引条目)和数据页不锁定(即不加事务型的锁)。当更改数据页上的某一行时,ASE将在相应的数据页上获取非事务型闩锁(Latch)。也就是说,ASE虽然不在数据页上加事务级的锁,但是需要在数据页上加非事务级的内部"Latch (闩锁)"。闩锁在对数据页进行物理更改期间被获得和持有,更改完后立即被释放。而数据行上加的锁一直被持有到事务结束。此外,如果事务更新的表上有索引的话,在更新索引行时也会在索引页上使用闩锁,但并不会在索引行上加事务型的锁。
4. 锁的种类
ASE在加锁时通常会采用如下几种类型的锁:
(1) 共享锁 (Shared locks)
ASE 对读取操作使用共享锁。如果对某个数据行应用了共享锁,那么即使最先获得共享锁的事务仍在运行,其它事务仍然可以获取共享锁。但是,在数据行上的所有共享锁释放前,任何其他事务都不能在该数据行上获取排它锁(Exclusive lock)。
这就是说,很多事务可同时读取相同数据行,但是当共享锁存在时,任何其它事务都不能更改(update或delete)相应的记录行。这些写事务需要等待(阻塞),直到持有相应共享锁的事务释放它们之后才能继续运行。
缺省情况下,ASE 将在数据行扫描完成后立即释放共享行锁(在隔离级别1下的select查询或者隔离级别2或3下使用noholdlock关键字的select查询),而不会将共享行锁一直持有到语句完成或者事务结束,除非用户要求如此(由设置参数"read committed with lock"控制)。
(2) 排它锁 (Exclusive locks)
ASE 对数据修改操作应用排它锁。如果一个事务具有排它锁,则在该事务结束并释放排它锁之前,其它事务将不能在该事务所访问的相应数据行上获取任何类型的锁,其它事务需要等待(阻塞),直到排它锁释放。
(3) 更新锁 (Update locks)
ASE 在执行update、delete 或 fetch (对于定义时使用了“for update”子句的游标) 操作的初始阶段(即搜索满足条件的记录行),在读取数据行时,ASE 会应用更新锁。更新锁允许其它事务对相同数据行加"共享锁",但不允许其他事务对相应数据行加"更新锁"或"排它锁"。
更新锁有助于避免死锁和锁争用。如果更改数据行时,当相应的数据记录上没有其它"共享锁"时,ASE会立即将"更新锁"提升为"排它锁"。
需要注意:
对于delete或update操作,仅当有可用索引时(比如,where条件中的搜索参数使用到的列是索引的一部分),ASE才应用行级排它锁或更新锁。如果没有可用索引的话,ASE将获取表级排他锁或更新锁。
ASE还有其它类型的锁,这些锁将在介绍其它隔离级别的实现方法时加以介绍。
隔离级别1(READ_COMMITTED)实现的基本原理
运行在隔离级别1(READ_COMMITED)下的事务可以防止脏读。ASE数据库是通过什么样的技术做到的呢?下面将结合一些例子对ASE所采用的实现方式加以说明。
让我们看一个例子,假设事务TA执行如下操作:
begin transaction
.....
update accounts set balance=balance+100 where actno='1111111'
.....
commit
在事务TA运行期间数据库中有一个事务TB在运行,TB执行如下操作:
begin transaction
.....
select balance from accounts where actno='1111111'
.....
commit
假设TA中的update语句首先执行;在其执行完后TB中的select语句执行,在它执行时,事务TA还没有提交。TA和TB都运行在非链式模式、隔离级别1下,表accounts采用"datarows"锁定机制。
在隔离级别1下,事务TB中的select语句应当读取已经提交的数据修改,为了保证这种行为,ASE会在TA执行update语句时获得"排它锁",由于accounts表在actno字段上有索引,因此ASE会为update语句命中的、需要修改的记录行获得"排它行锁(Ex_row)",否则将获得"排它表锁(Ex_table)"。TB执行select语句时,ASE会为查询所命中的记录行获得"共享行锁(Sh_row)",由于actno='1111111'的记录行已经被TA加了"排他行锁",因此TB对于该记录行的"共享行锁"请求不能成功,被"阻塞"。TB中的select语句由于"阻塞"而不能执行,直到拥有"排他行锁"的事务TA提交或者回滚。ASE就是通过这样的锁定技术防止了脏读。
阅读(2923) | 评论(0) | 转发(0) |