ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

048、锁对象与锁机制

048、锁对象与锁机制 048、锁对象与锁机制半夜两点被电话吵醒客户说那张采购订单保存时卡死了整个物料主数据都锁住业务员在电话里急得骂人。我远程一看SM12里躺着一个大锁owner是一个早就下线的后台作业锁了三天没释放。那会儿我刚从C#转ABAP没多久第一反应是“这锁怎么还能超时”后来才明白ABAP的锁不是数据库锁它压根不防并发它防的是你自己人。先讲一个最坑的场景你以为写了CALL FUNCTION ‘ENQUEUE_EZPO’数据就安全了结果两个用户同时点保存第二个居然也进去了数据照样被覆盖。为什么因为锁对象只是你程序里的一个约定别人不遵守锁就是个摆设。你得让所有写这张表的程序都去加同一把锁否则就是裸奔。那锁到底锁什么锁参数里那个表字段的赋值决定了这把锁的影响范围。比如锁对象里定义了WERKS工厂和MATNR物料作为锁字段你传了工厂1000、物料A001进去那别的会话想锁同样工厂1000、物料A001时就会被拒绝。如果你只传工厂1000、物料留空那等于把1000工厂下所有物料都锁住这跟拿大炮打蚊子没区别。好多新手喜欢把键值全填上觉得锁得越细越好其实未必你锁的粒度越细锁记录越多SM12里密密麻麻全是你的小锁管理成本反而高。锁对象在SE11里创建激活后自动生成两个函数ENQUEUE_和DEQUEUE_。创建时注意那个“Lock Mode”下拉框有E独占、S共享、X排他三种。E是默认的别人不能再加E或X但可以加SS是共享锁别人还能加S但不能加E或XX是排他锁谁都不能再加任何锁。实际业务里用到X的极少除非你真的要“我锁了别人连读都不行”。大多数场景用E就够了别一上来就X容易把整个系统锁成瘫痪。加锁调用很简单但坑都在参数里。那个_SCOPE参数默认是1意思是在当前会话内有效提交工作COMMIT WORK时自动释放。如果你设成2则是提交后仍然保留直到显式调用DEQUEUE或者会话结束。我见过有人把_SCOPE设成2然后忘了释放第二天SM12里全是他留下的僵尸锁背了生产事故。记住除非你有跨LUW逻辑工作单元的需求否则永远用默认1。还有那个_COLLECT参数默认是空表示每条ENQUEUE都是独立锁记录。如果你设为X则相同锁参数的调用会合并成一条重复使用。这在循环里加锁时特别有讲究——如果循环里反复对同一把锁调用ENQUEUE_COLLECT设成X能减少锁表记录数但如果你忘了在循环结束后DEQUEUE_ALL那锁会一直攒着直到会话结束。我建议循环里不要频繁加锁把锁提到循环外面一次搞定这才是正解。解锁函数DEQUEUE_参数和加锁几乎一样但有个容易忽视的_BUSTIMESTAMP只在系统启用RFC安全性检查时才需要填日常开发全留空就行。DEQUEUE_ALL是Disconnect时自动执行的但你不能指望系统帮你兜底程序里异常分支必须自己写CLEANUP否则一旦程序发生运行时错误锁还在别人就干瞪眼。锁的等待行为如果你加锁时发现已有冲突锁函数会直接返回SY-SUBRC 4不会自动重试也不会阻塞。这跟数据库锁不一样数据库锁是阻塞等待ABAP锁是“打招呼失败就撤”。所以你得自己写重试逻辑或者干脆告诉用户“数据正被别人改稍后再试”。这里踩过坑很多新手以为ENQUEUE会像数据库UPDATE一样卡住其实它秒回失败你判断SY-SUBRC等于0才继续往下走否则直接EXIT别傻傻地去查数据。那锁和数据库锁之间是什么关系ABAP锁是应用层的锁锁的对象是逻辑上的业务数据比如一笔采购凭证。真正写数据库时数据库自己还有行锁。ABAP锁解决的是“多个对话在多台应用服务器上”的冲突而数据库锁只在你真正UPDATE INSERT DELETE那一刻生效。如果你不加ABAP锁A用户读了数据进界面B用户也读了数据进界面两人同时保存后保存的覆盖先保存的这就是丢失更新。ABAP锁的作用就是让第二个用户压根进不了编辑界面从源头掐断冲突。有一次生产系统出现死锁我查SM12发现两个锁互相等程序P1锁了表A想锁B程序P2锁了表B想锁A。ABAP锁没有死锁检测机制不会自动回滚只能人工去SM12里删除一个锁才能解开。所以写程序时加锁的顺序一定要全公司统一比如先锁主数据后锁单据别这程序先锁单据再锁主数据那程序先锁主数据再锁单据早晚要撞车。这个约定得写进开发规范里靠人自觉不如靠规则强制。再说锁对象的创建细节。SE11里创建锁对象维护表时注意选择“Lock Table Entries”选项通常你只想锁表里的某些行那就选这个。如果你选了“Lock Entire Table”那等于整个表被锁动静太大除非是配置表一次性重写否则别这么干。添加表后要勾选“Lock Parameter”字段这些字段才是锁的主键。字段顺序很重要第一个字段会生成到ENQUEUE函数的IMPORT参数里作为必填后面的可选。如果你把MANDT客户端字段也设成锁参数那你就把整个客户端的数据全锁了妥妥的生产事故。新建锁对象时系统会自动带上MANDT但默认不在锁参数里你千万别手贱去勾。调试问题切入一次用户报错说“物料主数据被用户张三锁定”但张三明明下班了。我SM12一看张三的锁还在锁创建时间是上午十点张三的会话中午就断了。原来张三的程序里有一个更新函数UPDATE TASK它的锁是独立于主程序会话的_SCOPE参数如果设成2或者更新函数里又调用了ENQUEUE锁就会挂到更新任务里。更新任务执行完会自动释放但更新任务如果失败重试锁就一直留着。后来我们检查发现是更新函数里逻辑错误导致更新任务不停重试锁就死守不放。从那之后我要求所有涉及更新任务的锁必须在更新函数里显式解锁并且更新失败时用ROLLBACK WORK来让系统清理锁别指望后台自动释放。写到最后给点个人经验。第一锁对象是团队契约不是个人工具新增一个锁对象前先查一下已有的锁对象和表是否重复别整出两把锁来管同一个表那样会互相冲突而且别人根本不知道用哪把。第二加锁和解锁必须成对出现在同一个方法里最好用TRY EXPANDING CATCH把DEQUEUE放在CLEANUP中这样无论中间怎么异常都能释放锁。第三不要在更新功能模块里传锁参数那玩意跟锁机制是两条线更新任务有自己的锁管理乱传容易锁残留。第四测试锁时用两个SAPGUI会话一个设置断点停在ENQUEUE之后另一个去加同一把锁看返回码是否为4这招比你自己瞎想靠谱。第五也是最实在的遇到锁问题先查SM12看看是谁的锁、锁的是什么对象、锁了多久别急着杀进程先通知owner确认那些跨天的锁多半是后台作业异常可以在SM37里检查作业状态再清理。锁机制这东西不复杂但恶心就恶心在它跟业务逻辑纠缠在一起。你把锁加宽了影响别人加窄了自己漏风。每把锁背后都是用户体验和生产安全。别以为调通一个ENQUEUE就完事了你得多想想如果程序崩溃怎么办如果用户中途关屏怎么办如果更新任务失败怎么办把这几个问题想清楚你的锁才是真的锁。
返回列表