这2天我们处理了一个关于程序锁的bug,本来还不想写blog,因为并没有完全清楚V1,V2和锁的机制,不过兴伟说写一个,那还是记录一个吧。
场景:
LES系统每天会把现场投入5个车间的生产物料,传给SAP系统。SAP系统收到这些投料数据后,会有程序批量的把它们投料到生产工单上,完成物料在SAP系统中的消耗。
最初在1000工厂,我们开展的整车业务,随后,在1010工厂,我们开展了自制件业务。把整车业务的投料程序拷贝了一份,并对订单类型做了限制,就成了自制件业务的投料程序。
因为这个程序会有几个业务部门的人员同时操作,而每一次批量操作时,调用系统BAPI(系统标准功能)消耗物料(投料)的程序执行有几分钟才能处理完成。为了防止这个程序同时多次去读取了同一份LES的投料数据,然后在SAP系统中重复投料。所以我们给这个程序加了锁。
症状:
在自制件业务的物料投料凭证看,有很多重复投料产生,即一份LES的数据,在SAP系统中被执行了2次,产生了2个物料凭证。从最近的几次重复投料物料凭证时间看,间隔时间大概是2分钟。
即用户1对A物料产生投料凭证,2分钟后用户2又对A物料产生了投料凭证。
原因:
投料程序的逻辑如下:
1、读取LES数据,显示给用户
2、按用户的选择,处理投料数据,先把寄售数据转自有
3、再把物料按订单投料,完成物料消耗
我们在第1步程序中,加锁:
但程序在第2步中,锁被释放掉了,这并不是我们想要的,因为第3步才是长耗时程序,我们加锁是需要这把锁可以在1、2、3步都锁住数据。经过调试,我们发现在第2步调用系统做411K寄售转自有的BAPI后,锁被释放掉了。
解决:
因为使用了标准的锁功能及函数,默认的SCOPE参数中是给的是2,就是出现上面的问题,当我们使用SCOPE = 1时,这把锁可以锁住数据,在1、2、3程序真正执行完成后才解锁。金圣俊的书上对V1、V2更新及锁也有专门的章节讲解,但其实还是没有讲得很清楚。
目前我能够理解的就是SCOPE = 1是V1锁,是程序层面的锁,程序层面的锁,是整个程序结束后才释放;而SCOPE = 2是V2锁,是针对更新事件的锁。,更新层面的锁是程序中有数据库更新发生后就释放。
--------2022.1.17 后记----------------
今天有时间测试了一下锁的_SCOPE参数,可以总结一下,_SCOPE 遭遇到如下物料BAPI,
BAPI_GOODSMVT_CREATE,BAPI_TRANSACTION_COMMIT的情况如下:
一、SE11创建的对象锁:
如果 _SCOPE = 1,锁不会被释放。
如果 _SCOPE = 2,锁在物料BAPI执行后会自动释放。
二、程序锁 ENQUEUE_ES_PROG:
如果 _SCOPE = 1,锁不会被释放。
如果 _SCOPE = 2,锁在物料BAPI执行后会自动释放。
三、关于说明的翻译:
_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.
_SCOPE控制锁范围的传递:
1、当为1时,锁不能传给更新程序。锁会在程序结束后才移除。
2、当为2时,锁被传递给了更新程序,更新程序会负责移除锁。(有可能移除锁,也有可能不移除锁)
四、测试程序如下,不好找,所以放这里吧:
REPORT ZJAMES_LOCK.
CALL FUNCTION 'ENQUEUE_ES_PROG'
EXPORTING
MODE_TRDIR = 'E'
NAME = 'ZJAMES_LOCK'
X_NAME = ' '
_SCOPE = '2'
_WAIT = ' '
_COLLECT = ' '
EXCEPTIONS
FOREIGN_LOCK = 1
SYSTEM_FAILURE = 2
OTHERS = 3 .
IF SY-SUBRC <> 0.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.
DATA:lit_return LIKE STANDARD TABLE OF bapiret2, "bapi return
lwa_return TYPE bapiret2, "bapi return
lit_movs LIKE STANDARD TABLE OF bapi2017_gm_item_create, "mat doc item
lwa_movs TYPE bapi2017_gm_item_create, "mat doc item
lwa_header LIKE bapi2017_gm_head_01, "mat doc hdr
lwa_code LIKE bapi2017_gm_code, "mmim code
lwa_header_ret TYPE bapi2017_gm_head_ret. "mat hdr return
lwa_code = '04'. "MB1B
lwa_header-pr_uname = sy-uname.
lwa_header-pstng_date = sy-datum.
lwa_header-doc_date = sy-datum.
REFRESH:lit_movs.
CLEAR:lwa_movs.
lwa_movs-material = '000000000050580071'.
lwa_movs-plant = '8800'.
lwa_movs-stge_loc = '8813'.
lwa_movs-move_type = '311'.
lwa_movs-stck_type = ''.
lwa_movs-move_stloc = '8814'.
lwa_movs-spec_stock = 'E'.
lwa_movs-val_sales_ord = '0640003056' .
lwa_movs-val_s_ord_item = '000200'.
lwa_movs-entry_qnt = 1.
CLEAR:lwa_movs-entry_uom.
APPEND lwa_movs TO lit_movs.
CALL FUNCTION 'BAPI_GOODSMVT_CREATE'
EXPORTING
goodsmvt_header = lwa_header
goodsmvt_code = lwa_code
TABLES
goodsmvt_item = lit_movs[]
return = lit_return[].
LOOP AT lit_return INTO lwa_return WHERE type = 'E'
OR type = 'A'
OR type = 'X'.
write:/ lwa_return-type && ': ' && lwa_return-MESSAGE.
ENDLOOP.
IF sy-subrc = 0.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
ENDIF.
五、关于对象锁和程序锁的功能
1、程序锁:
比较好理解,程序开始加一把锁,程序结束后,这把锁自动释放。
2、对象锁:
我们想对更细的数据加锁,比如细到某表中的一行数据,我们可以在SE11中按表的关键字建一个锁对象,然后我们使用这个对象锁时,传入一行主键数据;那么这一行数据就有了锁,如果其它程序也有这个锁对象,也传入了相同的主键,就可以知道前面已经有锁了。
如下面EZ_JAMES_OBJECT这个锁对象,它的主键是GUID,我们对A10001236这行数据加锁。
CALL FUNCTION 'ENQUEUE_EZ_JAMES_OBJECT'
EXPORTING
MODE_ZESBTEST = 'X'
MANDT = SY-MANDT
GUID = 'A10001236'
X_AUTONUMBER = ' '
X_GUID = ' '
_SCOPE = '2'