ABAP数据锁定
数据库锁定:与DB LUW机制类似,数据库本身一般也提供数据锁定机制。数据库将当前正在执行修改操作的所有数据进行锁定,该锁定将随着数据库的LUW的结束而被重置,因而每数据库提交或者回滚之后该锁定就被释放。因为SAP的LUW概念独立于数据库LUW和对话步骤,出于同样的原因,SAP还需要定义自己的数据锁定机制。
SAP锁定:SAP LUW要求数据库对象的锁定在SAP LUW结束释放,并且该数据库锁要求对所有SAP程序都可见。 SAP提供了一个逻辑数据锁定机制,该机制基于系统特定的锁定服务应用服务器中的中心锁定表(即将加锁的信息记入数据库表)。一个ABAP程序在访问数据之前,将希望锁定的数据表关键字发送给该表,因而所有的程序在访问一个数据库表之前必须首先判断该表是否已经被锁定了。
SAP锁定与数据库物理锁定是不同的,它是一种业务逻辑上的锁定。它不会在物理表上进行加锁,而是将关键字传递加锁函数,加锁函数会在特定表中进加锁信息登记。
SAP LUW在结束时(提交或回滚),SAP锁定将会隐式解除。
在SE11里创建锁对象,自定义的锁对象都必须以EZ或者EY开头来命名。一个锁对象里只包含一个PRIMARY TABLE,可以包含若干个SECONDARY TABLE。
在设定一个SAP锁定,必须在数据字典中创建一个锁定对象。一或多个数据库表或关键字段可以被指定为锁定对象中的一或多行。激活该对象后,系统自动生成两个功能函数,名称为ENQUEUE_<LOCK OBJECT>和DENQUEUE_<LOCK OBJECT>,其中<LOCK OBJECT>是锁对象的名称。
l Shared lock(Read Lock)S锁
Several users can read the same data at the same time, but as soon as a user edits the data, a second user can no longer access this data. Requests for further shared locks are accepted, even if they are issued by different users, but exclusive locks are rejected.同一时间内允许多个用户读取同一数据,但是一旦某个用户修改数据后,其它用户将不能再访问该数据。只要是加的共享锁,即使是来自不同用户请求,都是可以加上的(即同一时间内可以允许多个不同的用户加共享锁),但只要是已加了共享锁,其他排它类型的锁不能再加了。
l Exclusive lock(Write Lock )E锁
The locked data can be read or processed by one user only. A request for another exclusivelock or for a shared lock is rejected.可重入(可重入即可针对同一数据进行多次加锁)类型的排它锁(独占锁)。该类型的锁住的数据在同一时间内,只能被一个用户读取和修改,但同一时间内其他非同一事务内的排他锁或共享锁的加锁请求都会被拒绝,但在同一事务内的E、S锁还是可以加的。
l Exclusivebut not cumulativelock(Exclusive, not cumulative)X锁
Exclusive locks can be requested by the same transaction more than once and handled successively, but an exclusive but not cumulative lock can only be requested once by a giventransaction. All other lock requests are rejected.排他锁可以在同一事务内多次加,但是不可累计的排他类型锁在同一时间内只能加一次(即使是在同一个事务内),其他所有的加锁请求都会被拒绝。
允许第二次加锁模式 |
|||
第一次加锁模式 |
S |
E |
X |
S |
是(是) |
否(是) |
否(否) |
E |
否(是) |
否(是) |
否(否) |
X |
否(否) |
否(否) |
否(否) |
括号内为同一程序(即同一事务内)内,括号外为非同一程序内
当调用设置锁函数时,LOCK PARAMETERS如果没有指明,系统会锁定整个表。
有些情况下,程序中设置的逻辑锁会隐式的自己解锁。比如说程序结束发生的时候(MESSAGE TYPE为A或者X的时候),使用语句LEAVE PROGRAM,LEAVE TO TRANSACTION,或者在命令行输入/n回车以后。
在程序的结束可以用DEQUEUE FUNCTION MODULE来解锁(当然如果你不写这个,程序结束的时候也会自动的解锁),这个时候,系统会自动从LOCK TABLE把相应的记录删除。使用DEQUEUE FUNCTION MODULE来解锁的时候,不会产生EXCEPTION(不需要对系统返回码sy-subrc进行判断)。如是要解开你在程序中创建的所有的逻辑锁,可以用函数:DEQUEUE_ALL.
system_failure = 2 .
_scope:1 表示程序内有效; 2 表示 update module 内有效; 3 全部
_wait 表示如果对象已经被锁定,是否等待后再尝试加锁,最大的等待时间有系统参数 ENQUE/DELAY_MAX控制
_COLLECT 参数表示是否收集后进行统一提交,COLLECT 是一种缓存与批处理方法,即如果指定了Collect,加锁信息会放到Lock Container 中,Lock Container实际上是一个funciton Group控制的内存区域,如果程序中加了很多锁,锁信息会先放到内存中,这样可以减少对SAP锁管理系统访问,若使Lock Container中的锁生效,需执行FLUSH_ENQUEUE 这个Funciton,将锁信息更新到锁管理系统中,此时加锁操作生效,使用函数RESET_ENQUEUE可以清除Lock Container中的锁信息
.
MODIFY zspfli FROM it_zspfli."修改数据
ENDIF.
BREAK-POINT.
CALL FUNCTION 'DEQUEUE_EZ_ZSPFLI'"解锁
EXPORTING
mode_zspfli = 'E'
mandt = sy-mandt
carrid = 'AA'
connid = '0011'
* X_CARRID = ' '
* X_CONNID = ' '
* _SCOPE = '3'
* _SYNCHRON = ' '
* _COLLECT = ' '.
SM12锁查看与维护
当使用程序加锁时,在可加锁的情况下,会即时的产生一条锁信息数据,可以通过SM12查看所产生的锁信息数据。
通过SM12,还可以删除锁记录,让数据不再锁定
SM12还可以查看SAP运行时所产生的锁信息,比如通过SE38编辑某个程序时,也会产生一条锁信息(同一程序不能在同一时间内多次修改),因为程序本身也是存储在表里的,比如下面在修改 ZJZJ_TEXT_EDITOR时,会产生一条锁数据信息:
通用加锁与解锁函数
通用加锁与解锁函数不需要创建表的锁对象就可以直接使用
system_failure =
.
CALL FUNCTION 'DEQUEUE_E_TABLE'
EXPORTING
* MODE_RSTABLE = 'E'
tabname = 'SFLIGHT'
* VARKEY =
* X_TABNAME = ' '
* X_VARKEY = ' '
* _SCOPE = '3'
* _SYNCHRON = ' '
* _COLLECT = ' '.
ABAP程序锁定
除了数据库有锁定外,ABAP程序本身也有锁定。
SAP提供了两个函数来解决程序运行时的同步锁定问题:ENQUEUE_ES_PROG和DEQUEUE_ES_PROG。
CALL FUNCTION 'ENQUEUE_ES_PROG'
* EXPORTING
* MODE_TRDIR = 'E'
* NAME =
* X_NAME = ' '
* _SCOPE = '2'
* _WAIT = ' '
* _COLLECT = ' '
* EXCEPTIONS
* FOREIGN_LOCK = 1
* SYSTEM_FAILURE = 2
* OTHERS = 3
.
Prevents the parallel execution of a program.可防止程序的并行执行。
Description
This function creates a lock in a program that should not be processed more than once, simultaneously.该加锁函数会在程序里创建一把锁阻止同一时间只有一个运行。
The lock remains in place until either the DEQUEUE_ES_PROG function module
is called or the transaction is completed (with an implicit DEQUEUE_ALL call).
Parameters该锁会保持直到DEQUEUE_ES_PROG函数调用,或者事务(程序)执行完毕(执行完毕后会隐式调用DEQUEUE_ALL)才会释放
EXPORTING输出参数
NAME Program name to lock需上锁的程序名
_SCOPE Controls how the lock is passed to the update program:锁的传播特性??
Value Meaning
1 The lock is not passed to the update program. The lock
is removed when the transaction ends.
2 (default) The lock is passed to the update program. The update
program is responsible for removing the lock.
3 The lock is passed to the update program. The lock must
be removed in both the interactive(交互) program and in the
update program.
CALL FUNCTION 'DEQUEUE_ES_PROG'
* EXPORTING
* MODE_TRDIR = 'E'
* NAME =
* X_NAME = ' '
* _SCOPE = '3'
* _SYNCHRON = ' '
* _COLLECT = ' '
.
该函数释放由ENQUEUE_ES_PROG.设置的程序锁
使用时需要注意下列问题:
1、 ENQUEUE_ES_PROG函数只是尝试去锁定,如果锁已经被其他程序获取,并不会阻塞,要在调用后通过sy-subrc来判断是否获取成功。可以在循环里通过 WAIT UP TO xx SECONDS. 语句来等待锁被获取到
2、 ABAP工作台开发程序时,不能同时编辑同一个程序,第一个打开程序的用户会上程序锁,程序锁可以使用SM12来查看当前的程序锁,如现在编辑z_jzj_program_lock程序时:
使用SM12查看:
此时如果再试着打开并编辑该程序时,状态栏会报错:
该程序在处于编辑状态下,再运行时,也会报错:
,这是因为程序处于编辑状态获取的锁与在程序里使用代码获取的锁是同一个锁,所以上面代码在获取锁时出错,如果处于只读状态时,则可以正常运行,但也只能一个窗口运行,不能有两个或多个只读状态下的该程序运行。
3、 程序在锁定后,解锁的动作不是必须的,程序退出或者完成后会自动解锁。
4、 程序的锁定只对前台程序有用,如果运行的是后台服务,则需要其他的解决方案。可以考虑使用如下方法:一种方式是将应用服务器配置为同一程序只允许同时运行一个JOB;第二种方式是通过判断表TBTCO的JOB状态字段来解决。
5、 锁定的功能函数在不同的事件中运行效果不同,请根据具体的需求来确定该在何种事件后锁定。
从中我们可以看到,程序锁于字段锁都是有相同的地方,都需要在程序中先锁,之后要解锁,
数据库锁
锁的分类和兼容性
按照*程度,锁可以分为:共享锁、独占锁和更新锁,下面分别介绍它们的用法。
1. 共享锁(S)
共享锁用于读数据操作,它是非独占的,允许其他事务同时读取其锁定的资源,但不允许其他事务更新它。一旦已经读取数据,便立即释放资源上的共享 (S) 锁,除非将事务隔离级别设置为可重复读或更高级别,或者在事务生存周期内用锁定提示保留共享 (S) 锁。获取共享锁的事务只能读数据,不能修改数据。共享锁具有以下特征。
l 加锁的条件:当一个事务执行select语句时,数据库系统会为这个事务分配—把共享锁,来锁定被查询的数据。
l 解锁的条件:在默认情况下,数据被读取后,数据库系统立即解除共享锁:例如,当一个事务执行查询“SELECT * FROM ACCOUNTS”语句时,数据库系统首先锁定第一行,读取之后,解除对第一行的锁定,然后锁定第二行。这样,在一个事务读操作过程中,允许其他事务同时更新ACCOUNTS表中未被锁定的行。
l 与其他锁的兼容性:如果数据资源上放置了共享锁,还能再放置共享锁和更新锁。
l 并发性能:具有良好的并发性能,当多个事务读相同的数据时,每个事务都会获得一把共享锁,因此可以同时读锁定的数据。
2. 独占锁(X)
独占锁也叫排他锁,适用于修改数据的场合。它所锁定的资源,其他事务不能读取也不能修改。获取排他锁的事务既能读数据,又能修改数据。独占锁具有以下特征。
l 加锁的条件:当一个事务执行insert、update或delete语句时,数据库系统会自动对SQL语句操纵的数据资源使用独占锁。如果该数据资源已经有其他锁存在时,无法对其再放置独占锁。
l 解锁的条件:独占锁一直到事务结束才能被解除。
l 与其他锁的兼容性:独占锁不能和其他锁兼容,如果数据资源上已经加了独占锁,就不能再放置其他的锁。同样,如果数据资源上已经有了其他的锁,就不能再放置独占锁。
l 并发性能:并发性能比较差,只允许有一个事务访问锁定的数据,如果其他事务也需要访问该数据,就必须等待,直到前一个事务结束,解除了独占锁,其他事务才有机会防问该数据
3. 更新锁
更新锁在更新操作的初始化阶段用来锁定可能要被修改的资源,这可以避免使用共享锁造成的死锁现象。例如,对于以下的update语句:
update ACCOUNTS set BALANCE=900 where ID=l;
如果使用共享锁,更新数据的操作分为两步。
1) 获得一个共享锁,读取ACCOUNTS表中ID为1的记录。
2) 将共享锁升级为独占锁,再执行更新操作。
如果同时有两个或多个事务同时更新数据,每个事务都先获得一把共享锁,在更新数据的时候,这些事务都要先将共享锁升级为独占锁。由于独占锁不能与其他锁兼容,因此每个事务都进入等待状态,等待其他事务释放共享锁,这就造成了死锁。
如果使用更新锁,更新数据的操作分为以下两步。
1) 获得一个更新锁,读取ACCOUNTS表中ID为1的记录。
2) 将更新锁升级为独占锁,再执行更新操作。
更新锁具有以下特征。
l 加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。
l 解锁的条件:当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁(如果事务修改资源,则更新锁转换为排它锁。否则,锁转换为共享锁)。
l 与其他锁的兼容性:更新锁与共享锁是兼容的,也就是说,一个资源可以同时放置更新锁和共享锁,但是最多只能放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁(这样也就只有一个锁能升级为独占锁),其他事务必须等到前一个事务结束后,才能获得更新锁,这就避免了死锁。
l 并发性能:允许多个事务同时读锁定的资源,但不允许其他事务修改它。
并发性与锁的权衡
许多数据库系统都有自动管理锁的功能,它们能根据事务执行的SQL语句,自动在保证事务间的隔离性与保证事务间的并发性之间做出权衡,然后自动为数据库资源加上适当的锁,在运行期间还会自动升级锁的类型,以优化系统的性能。对于普通的并发性事务,通过系统的自动锁定管理机制墓本可以保证事务之间的隔离性,但如果对数据安全、数据库完整性和一致性有特殊要求,也可以由事务本身来控制对数据资源的锁定和解锁。
锁的*粒度大,事务间的隔离性就越高,但是事务问的并发性就越低。数据库系统根据事务执行的SQL语句,自动对访问的数据资塬加上合适的锁。假设某事务只操纵—个表中的部分行数据,系统可能只会添加几个行锁或页锁,这样可以尽可能多地支持多个事务的并发操作。但是,如果某个事务频繁地对某个表中的多条记录操作,将导致对该表的许多记录行都加上了行级锁,数据库系统中锁的数日会急剧增加,这就加重了系统负荷,影响系统性能。因此,在数据库系统中,一般都支持锁升级。锁升级是指调整锁的粒度,将多个低粒度的锁替换成少数更高粒度的锁,以此来降低系统负荷。例如,当一个事务中的锁较多,达到锁升级门限时,系统自动将行级锁和页面级锁升级为表级锁。
数据库的事务隔离级别
脏读:如果第二个事务查询到了第一个事务未提交的更新数据,第一个事务依据这个查询结果继续执行相关的操作,但是接着第一个事务撤销了所做的更新,这会导致第二个事务操纵脏数据。
不可重复读:指在一个事务内,多次读取同一数据,在这个事务还没有结束时,另外一个事务也访问同一数据,那么在第一个事务中两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了一个事务中两次读到的数据是不一样的,因此称为是不可重复读。不可重复读是由于一个事务查询到了另—事务已提交的对数据的更新而引起的。
虚(幻)读:是由于一个事务查询到了另—事务已提交的新插入(或删除)引起的。
尽管数据库系统允许用户在事务中显式地为数据资源加锁,但是首先应该考虑让数据库系统自动管理锁,它会分析事务中的SQL语句,然后自动为SQL语句所操纵的数据资源加上合适的锁,而且在锁的数目太多时,数据库系统会自动进行锁升级,以提高系统性能。锁机制能有效地解决各种并发问题,但是它会影响并发性能。并发性能是指数据库系统同时为各种客户程序提供服务的能力。当一个事务锁定数据资源时,其他事务必须停下来等待,这就降低了数据库系统同时响应各种客户程序的速度。
为了能让用户根据实际应用的需要,在事务的隔离性与并发性之间做出合理的权衡,数据库系统提供了四种事务隔离级别供用户选择。
l Serlalizable:串行化
l RepeatableRead:可重复读
l ReadCommited:提交读
l ReadUncommited:未提交读
数据库系统采用不同的锁类型来实现以上四种隔离级别,具体的实现过程对用户是透明的。用户应该关心的是如何选择合适的隔离级别。在四种隔离级别中,Serializable的隔离级别最高,ReadUncommited的隔离级别最差,下表列山了各种隔离级别所能避免的并发问题。
隔离级别 |
是否出脏读 |
是否出现虚读 |
是否出现不可重复读 |
ReadUncommited |
是 |
是 |
是 |
ReadCommited(Oracle默认级别) |
否 |
是 |
是 |
RepeatableRead(Mysql默认级别) |
否 |
是 |
否 |
Serlalizable |
否 |
否 |
否 |
1) Serializable(串行化)
一个事务在执行过程中完全看不到其他事务对数据库所做的更新。当两个事务同时操纵数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行:因此这两个事务实际上以串行化方式运行:
2) RepeatableRead(可重复读)
一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
3) ReadCommitted(读已提交数据)
一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新。
4) ReadUncommitled(读末提交数据)
—个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。
隔离级别越高,越能保证数据的完整性和—致性,但是对并发性能的影响也越大,
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为ReadCommitted,它能够避免脏读,而月具有较好的并发性能。尽管它会导致不可重复读、虚读并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
在JDBC应用程序中设置隔离级别
JDBC数据库连接使用数据库系统默认的隔离级别。可以通进java.sql.Connection类的void setTransactionIsolation(int level)方法来设置
l Connection.TRANSACTION_READ_UNCOMMITTED
l Connection.TRANSACTION_READ_COMMITTED
l Connection.TRANSACTION_REPEATABLE_READ
l Connection.TRANSACTION_SERIALIZABLE
intTRANSACTION_NONE = 0;
intTRANSACTION_READ_UNCOMMITTED = 1;
intTRANSACTION_READ_COMMITTED = 2;
intTRANSACTION_REPEATABLE_READ = 4;
intTRANSACTION_SERIALIZABLE = 8;
在应用程序中采用悲观锁和乐观锁
当数据库系统采用Read Committed隔离级别时,会导致不可重复读和虚读并发问题。在可能出现这种问题的场合,可以在应用程序中采用悲观锁或乐观锁来避免这类问题。
从应用程序的角度,锁可以分为以下几类。
l 悲观锁:指在应用程序中显式地为数据资源加锁。悲观锁是假定当前事务操纵数据资源时,肯定还会有其他事务同时访问该数据资源,为了避免当前事务的操作受到干扰,先锁定资源。尽管悲观锁能够防止丢失更新和不可重复读这类并发问题(但不能防止虚读),但是它会影响并发性能,因此应该很谨慎地使用悲观锁。
l 乐观锁:乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此完全依靠数据库的隔离级别来自动管理锁的工作。应用程序采用版本控制手段来避免可能出现的并发问题。
由数据库系统独占锁实现悲观锁
如果是select语句时,数据库默认使用共享锁,当在读取出数据后共享锁会释放掉,这样如果在读取数据后再基于此读出的数据进行业务操作,就可能会有问题,比如在读取出来后某个数据库操作修改了这条数据,则后面基于此数据的业务操作就会有问题,这就是所会的不可重复读问题,我们可以在查询时明确指定独占锁的方式来锁定读取出来的数据,等业务操作完成后再释放锁:
select * for update
以上语句显式指定采用独占锁来锁定查询的记录。执行该查询语句的事务持有这把锁,直到事务结束才会释放锁。在执行事务过程中,其他事务如果要查询、更新或删除这些被锁定的记录,必须等到第一个事务执行结束,才能有机会操纵这些记录。
在Hibernate应用中,在默认情况下,Session的get()和load()方法的锁定模式为LockMode.NONE,如果设为LockMode.UPGRADE,就表示采用悲观锁。对于Oracle数据库,还可以用LockMode.UPGRAD_NOWAIT来表明使用悲观锁。
利用版本控制实现乐观锁
乐观锁是由应用程序提供的一种机制,这种机制既能保证多个事务并发访问数据,又避免不可重复读的问题。在应用程序中,可以利用Hibernate提供的版本控制功能来实现乐观锁(如果不是在Hibernate中,则可以自建version字段)。对象—关系映射文件中的<Version>元素和具有版本控制功能。<version>元素利用一个递增的整数来跟踪数据库表中记录的版本。
乐观锁通过版本控制功能来实现,它比悲观锁具有更好的并发性,所以应该优先考虑使用乐观锁。
SELECT SINGLE [FOR UPDATE]...
FOR UPDATE 选项不是将 SAP 锁定机制与 ENQUEUE/DEQUEUE 功能模块一起使用的替代品。例如,显示一个新的屏幕时,所有用 FOR UPDATE 锁定的行都将自动解锁。要保证在显示新屏幕时锁定的行保留锁定状态,就必须使用 SAP 锁定机制。这是使锁定的行一直到事务的结束都保留其锁定状态的唯一方法。
阻塞
4个常见的dml语句会产生阻塞
INSERT
UPDATE
DELETE
SELECT…FOR UPDATE
INSERT:Insert发生阻塞的唯一情况就是用户个的会话同时试图向表中插入相同的数据时,其中的一个会话将被阻塞,直到另外一个会话提交或会滚。一个会话提交时,另一个会话将收到主键重复的错误。回滚时,被阻塞的会话将继续执行。
UPDATE、DELETE:当执行Update和delete操作的数据行已经被另外的会话锁定时,将会发生阻塞,直到另一个会话提交或会滚。
Select …for update:当一个用户发出select..for update准备对返回的结果集进行修改时,如果结果集已经被另一个会话锁定,就是发生阻塞。需要等另一个会话结束之后才可继续执行。可以通过发出 select… for update nowait的语句来避免发生阻塞,如果资源已经被另一个会话锁定,则会返回以下错误:Ora-00054:resource busy and acquire with nowait specified.
死锁
例子:
1:用户1对A表进行Update,没有提交。
2:用户2对B表进行Update,没有提交。
此时双方不存在资源共享的问题,但如果继续如下操作时:
3:如果用户2此时对A表作update,则会发生阻塞,需要等到用户1的事物结束。
4:如果此时用户1又对B表作update,则产生死锁。此时Oracle会选择其中一个用户进行会滚,使另一个用户继续执行操作。