需求场景
最近收到用户反馈,发现同一个托运单生成了两个不同的服务订单以及根据同一个送货单生成了两个托运单,经过排查,发现原因都是由同样的问题导致的,多窗口或者多用户同时对一条数据操作,就会出现这种现象。这个时候就需要用到锁对象。
背景知识
- 数据库锁是指为防止多人同时操作数据库导致数据不一致而采取的一种机制,在每次数据库提交或者回滚之后该锁定就被释放。
- SAP ABAP 语言的锁是在语言层面设计的一种锁机制,它是一种逻辑锁,独立于数据库锁。该机制基于系统特定的锁定服务应用服务器中的中心锁定表(即将加锁的信息记入数据库表)。一个ABAP程序在访问数据之前,将希望锁定的数据表关键字发送给该表,因而所有的程序在访问一个数据库表之前必须首先判断该表是否已经被锁定了。
- SAP锁定与数据库物理锁定是不同的,它是一种业务逻辑上的锁定。它不会在物理表上进行加锁,而是将关键字传递加锁函数,加锁函数会在特定表中进加锁信息登记。
创建锁对象
- 自定义的锁对象都必须以EZ或者EY开头来命名。一个锁对象里只包含一个PRIMARY TABLE,可以包含若干个SECONDARY TABLE。
- 激活该对象后,系统自动生成两个功能函数,名称为ENQUEUE_LOCK OBJECT和DENQUEUE_LOCK OBJECT,其中LOCK OBJECT是锁对象的名称。
- 当调用设置锁函数时,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.
锁模式
- 独占锁
锁定的数据仅可由一个用户进行显示或编辑。对另一独占锁或共享锁的请求均将遭到拒绝。 - 共享锁
多个用户可同时读取访问相同的数据。然而,一旦任何一个用户在处理数据,第二个用户就不能再访问此数据。接受对其它共享锁的请求,即使这些共享锁来自不同的用户。拒绝对独占锁的请求。 - 独占但不累计锁
鉴于独占锁可由相同的事务请求多次并逐个释放,因此独占非累计锁只能由相同的事务请求一次。所有的其它锁请求都将被拒绝。
SM12锁查看与维护
- 当使用程序加锁时,在可加锁的情况下,会即时的产生一条锁信息数据,可以通过SM12查看所产生的锁信息数据。
- 通过SM12,还可以删除锁记录,让数据不再锁定.
- 锁参数是由锁对象的关键字组成的。
锁函数实现代码
*_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中的锁信息
“加锁
CALL FUNCTION 'ENQUEUE_EZ_LIKP'
EXPORTING
mode_likp = 'E'
mandt = sy-mandt
vbeln = lt_dn-vbeln
* X_VBELN = ' ' "是否使用初始值填充参数
* _SCOPE = '2'
* _WAIT = ' '
* _COLLECT = ' '
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
l_err = 'X'.
CLEAR: lv_lock_msg.
CALL FUNCTION 'MESSAGE_TEXT_BUILD'
EXPORTING
msgid = sy-msgid
msgnr = sy-msgno
msgv1 = sy-msgv1
msgv2 = sy-msgv2
msgv3 = sy-msgv3
msgv4 = sy-msgv4
IMPORTING
message_text_output = lv_lock_msg.
MESSAGE lv_lock_msg TYPE 'I'.
EXIT.
ENDIF.
"解锁,解锁前先判断是否存在锁,在加锁后,程序出现返回操作或保存操作处都需要解锁
REFRESH:lt_enq.
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gname = 'LIKP'
guname = ''
TABLES
enq = lt_enq[]
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
REFRESH:lt_tyd_item.
MOVE-CORRESPONDING g_it_item[] TO lt_tyd_item[].
SORT lt_tyd_item BY vbeln.
DELETE ADJACENT DUPLICATES FROM lt_tyd_item COMPARING vbeln.
LOOP AT lt_tyd_item.
LOOP AT lt_enq WHERE garg CS lt_tyd_item-vbeln .
CALL FUNCTION 'DEQUEUE_EZ_LIKP'
EXPORTING
mode_likp = 'E'
mandt = sy-mandt
vbeln = lt_tyd_item-vbeln
* X_VBELN = ' '
* _SCOPE = '3'
* _SYNCHRON = ' '
* _COLLECT = ' '
.
ENDLOOP.
ENDLOOP.