1. 后台作业的本质与适用场景
1.1 什么是后台作业,为什么要用它
在SAP系统里,“启用后台作业”绝对是日常开发和运维逃不开的话题。说白了,后台作业就是让ABAP程序在系统后台自动执行,不需要用户一直守着屏幕、点着按钮等结果。你提交完以后,该干嘛干嘛去,系统自己的后台工作进程会按设定的时间或者事件把程序跑起来,把结果写进日志、数据库,或者通过输出设备打出来。
为什么要折腾后台作业?三个字:省资源。比如你要跑一个全月销售数据重算,几百张凭证,算上ALV展示和交互可能要二十分钟。前台跑的话,这二十分钟用户终端就死锁在那儿了,什么也干不了。扔到后台,程序照跑,界面照退,对普通业务用户几乎零感知。再比如做日终、月结,或者和外部系统对账,根本不可能让业务人员每天凌晨三点去系统里点按钮,就必须靠后台作业自动触发。
从系统架构层面看,后台作业对应的是一组特殊的系统工作进程,通过进程类型里的“后台”队列来调度。一个后台作业可以包含多个步骤,每个步骤可以是程序、外部命令、或者一个业务方法(比如调用BAPI)。这些步骤按顺序执行,前一步成功才跑下一步,哪一步失败,整个作业的状态就会停在“已完成”之前的某个异常状态,方便你去SM37里追查。
1.2 适合后台作业的常见业务场景
不是所有程序都适合后台跑。我自己总结了几类最适合扔后台的活儿:
- 批量数据加工和导入导出:比如从外部系统导入物料主数据、批量更新价格、冲销大批量凭证。像热搜里提到的BAPI_MATVAL_PRICE_CHANGE,更改物料价格,如果一次要改几千条,前台一屏一屏跳肯定不现实,后台循环调用才是正路。
- 定期报表生成与分发:日报、周报、月报,财务对账表,库存周转分析。程序算完结果直接存表、生成PDF、推到对方系统,或者挂到一个共享目录,业务早上过来直接看结果。
- 接口数据收发与轮询:和MES、WMS、OA系统做接口时,通常就是后台作业每隔一段时间去读队列表,处理完更新状态。比如销售订单批创建,热搜里的BAPI_SALESORDER_CREATEFROMDAT2,就是后台从中间表读数据循环创建。
- 数据归档与删除:归档凭证、清理日志表,这类跑批通常要锁表、锁范围,前台跑容易把操作会话堵死。
- 长周期报表和计算:像工艺路线读取、批量算成本,跑个把小时很正常,不后台不行。
后台作业不是银弹,但凡是“定时、批量、耗时长、无需人工干预”的活儿,基本都是它的菜。
2. 启用后台作业前的关键准备
2.1 程序必须能“离线死跑”
这里踩过一个大坑:你把一个带ALV展示的程序直接丢到后台去跑,结果后台日志里提示“无法执行对话框程序”或者程序卡在某个需要用户输入的地方。为什么?因为很多报表程序写的时候根本没想到会被后台调用——选择屏幕要输入、ALV要弹屏幕、还要等用户点击事件。
所以启用后台作业之前,第一件事就是确认目标程序到底能不能后台跑。判断标准很简单:程序里所有输入是不是都能通过变式(Variant)传进去,所有输出是不是都丢到了数据库表、本地文件、或假脱机(Spool)里面,而不是非要弹个ALV或者对话框让用户看。
举个例子,热搜里那个 REUSE_ALV_GRID_DISPLAY 加 F4 是典型的交互式报表功能。这类程序直接后台跑等于判了死刑。要改造无非两条路:一是加参数,比如“输出模式”选“后台”,后台模式下不调 ALV,直接写内表数据到导出表;二是如果实在改不了,就做个前端壳子,把数据批量提交到一个存储表,再由后台作业去读表执行,绕开交互环节。
2.2 必须准备好变式和作业参数
后台作业调度时,系统给程序传参靠的是变式。没有变式,作业等于没有输入参数,大多数程序跑出来就是全量逻辑,轻则慢,重则直接报错。
创建变式的路径是事务代码 SA38 或 SE38 输入程序名后,点“变式”按钮,或者直接 SM36 里在作业步骤界面上新建变式。变式里该填哪些值,取决于程序的选择屏幕字段。建议把和业务相关的都固定下来,比如限定公司代码、物料类型、日期范围。
变式的坑主要集中在两类:
- 保存用户专属变式:只对自己可见,其他账号提交后台作业时根本找不到。
- 变式里日期用了硬编码:比如写死“20250101”,跑月结的时候上个月的数据全漏了。正确做法是变式里填系统变量,比如“当日”的含税值,或者留空让程序内部算日期。
类型上,变式分为“仅后台可见”、“标准变式”、“受保护变式”。后台作业调度时,一般用“仅后台可见”或“标准”即可。注意受保护变式的参数不可被运行作业的用户修改,适合生产环境锁死关键业务参数。
2.3 授权、输出设备与用户归属
后台作业虽然不占前台会话,但执行时依然需要一个“用户”身份。作业创建时指定的用户,不仅影响权限检查,还决定日志归属。一般建议用一个专门的批处理账号,比如 BACKGROUND_USER,账号密码永不过期、不踢出会话、限制只能执行特定事务。同时该用户要有足够的 S_USER、S_BTCH、S_JOB 授权,包括后台作业的创建、释放、监控都有对应权限对象。
另外,如果作业要输出打印或者生成PDF,必须指定一个输出设备,比如打印机名,或者虚拟设备如 LP01。后台作业跑的时候,输出会进假脱机请求表,而不是直接打印。设备不对、打印参数不对,作业就算程序执行成功了,输出步骤也会报错。
我还会在作业步骤里把“打印参数”里的页格式、行宽设置好,从一开始就避免后续产出乱码或者格式崩溃。
3. 创建后台作业的实操步骤
3.1 SM36 创建作业主界面
启用后台作业的入口是事务代码 SM36。进去以后,“作业名”这一项是必填的,名称要起得有业务含义,方便后面排障,别起一堆 JOB01、JOB02。我习惯的命名规则是:模块_业务_序号,比如 ZFI_MONTH_CLOSE_001。
适合放在“作业名”旁边的是“作业类”,也就是优先级。SAP后台作业分三个优先级:A(高)、B(中)、C(低)。系统在分配后台工作进程时,优先级高的会先抢到进程。日常的定时报表放B就够;涉及关键业务、影响后续作业依赖的,放A;跑批量大、又不着急的,比如数据清理,放C,错峰跑。
SM36界面最常见的操作路径是:先填作业名,然后点“步骤”去定义要跑的程序;再点“开始条件”定义什么时候跑;最后点“保存”。
有个细节:作业创建后,状态是“已计划”(Scheduled),只有当作业被“释放”(Release)以后,系统才会真正把它放进调度队列。好多新手创建完作业,等了半天没动静,就是因为忘了释放。
3.2 作业步骤怎么定义更稳
点“步骤”后,核心的选择项是“ABAP程序”,填程序名和变式名。注意“作业步骤编号”系统会自动分配,没必要手动改。
如果是调用BAPI或者函数,其实也是走ABAP程序,只不过这个程序内部去调用BAPI。还有一个常用选项是“外部命令”,可以执行操作系统层面的命令,比如调用Python脚本、清理本地临时文件,但生产环境一般不建议开这个口子,安全风险大,审批还麻烦。
定义步骤时有几个实用细节:
- 语言:默认“中文”或“DE”取决于系统登录语言。建议显式设置,否则步骤执行时取不到语言环境,文本消息会显示成乱码。
- 如果程序需要“仅后台执行”属性:在SE38的程序属性里,有一个“仅在后台执行”的勾选项。勾选后,前台跑不了,只能后台。反过来,如果程序里写了大量交互式语句,就必须在后台步骤中提前考虑交互问题的规避。
- 一个作业多个步骤:当步骤之间要做连锁——比如“先导入临时表,再启动汇总程序,最后发邮件”——就在SM36里多次添加步骤。系统默认步骤间是串行执行,步骤2在上一步完成后才开始。如果需要“上一步失败了就跳过后续步骤”,可以通过ABAP程序内的返回码判断,但作业本身不支持条件分支,这是后台作业的一个局限。
3.3 开始条件与周期的设置陷阱
后台作业的开始条件分三大类:立即、按日期/时间、按事件。日常用得最多的是按日期/时间加周期。
“按日期/时间”这里,如果只是单次执行,填日期和时间就行;需要周期调度的话,要勾“周期作业”,然后选择每隔多少分钟/小时/天执行一次。还可以做“高级周期选择”,比如每周一、周三早上六点跑,或者每月最后一个工作日跑。这个“最后一个工作日”表达式很容易配错,因为和其他系统的日历算法不一致,SAP底层用是功能模块算出来的,遇到系统有节假日的时候甚至会失效。生产环境里月结作业如果配了“每月最后一天”,遇到月末是周末的情况大概率要出事——跑批应该在最后一个工作日跑,不是自然月末。
“按事件”比较特殊,作业不会定时触发,而是当一个事件被抛出时触发。事件由“触发者”和“事件ID”组成。例如,系统事件“启动”本身可以绑一些作业;也可以自定义一个事件ID,由某个ABAP程序把RAISE EVENT语句抛出来,相关作业就跟着启动。这种做法的好处是两个作业之间不用掐点,一个跑完抛事件,另一个马上接上。坏处是排障难度大,事件到底有没有被抛出来,要到 SM62 事件历史里查。
3.4 释放作业前的检查逻辑
作业定义完以后,一定要点“信息”里面看一遍作业的当前状态。我一般会检查这几个地方:
- 作业步骤里的程序是否存在、有没有激活版本
- 变式是否存在、参数是否完整
- 用户是否有效
- 开始条件是否合理、与现有作业是否冲突
确认无误后,点“释放/调度”。这里系统会再做一次输入检查,有错会直接报出来。没有报错,就是正式启用了。之后去 SM37 就能看到作业状态从“已计划”变成“已释放”,到点以后变“正在运行”,最后“已完成”。
注意:如果作业执行时间非常密集,比如每5分钟跑一次,建议检查“默认运行时间”和作业重量,否则进程池很容易被占满,其他作业全堵在后面排队。
4. 后台作业的实际业务结合:BAPI批创建和审批增强场景
4.1 实战:后台调用BAPI_SALESORDER_CREATEFROMDAT2批量建销售订单
光会建作业还不够,真正实用的是把业务代码写成能被后台调度的结构。很多人一开始写批量程序,习惯用ALV把结果慢慢显示,然后手工操作。到了需要后台化的时候,发现程序完全没法拿出去见人。
我这边说一个最常见的场景:批量创建销售订单。
中间表 ZSALES_ORD_IMPORT 存了外部系统传过来的数据,字段包括销售组织、分销渠道、产品组、售达方、物料编码、数量、单价等。后台作业要做的事情,看似是循环读取中间表,调 BAPI_SALESORDER_CREATEFROMDAT2 创建一张张订单。
核心ABAP逻辑大概长这样:
SELECT * FROM zsales_ord_import INTO TABLE @DATA(lt_orders) WHERE status = 'P'. "P=待处理 LOOP AT lt_orders INTO DATA(ls_order). CLEAR: lv_order_number, lt_return. " 组装BAPI_SALESORDER_CREATEFROMDAT2的输入结构 ls_order_header_in = VALUE bapisdhd1( doc_type = 'OR' sales_org = ls_order-sales_org distr_chan = ls_order-distr_chan division = ls_order-division ). " 行项目 APPEND VALUE bapisditm( itm_number = '00010' material = ls_order-material target_qty = ls_order-quantity ) TO lt_order_items_in. CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2' EXPORTING salesorder_header_in = ls_order_header_in IMPORTING salesorder_ex = lv_order_number TABLES return = lt_return order_items_in = lt_order_items_in. READ TABLE lt_return TRANSPORTING NO FIELDS WITH KEY type = 'E'. IF sy-subrc = 0. " BAPI没有真正提交,回滚当前订单 CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'. UPDATE zsales_ord_import SET status = 'E', errmsg = lv_error_string WHERE guid = ls_order-guid. ELSE. CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'. UPDATE zsales_ord_import SET status = 'S', vbeln = lv_order_number WHERE guid = ls_order-guid. ENDIF. ENDLOOP.这段代码放到后台作业里跑,效果和前台一模一样,产品经理根本察觉不到差异。区别在于:前台跑的时候,如果报错了你当场能看到,能改数据重来;后台跑,中间的异常必须靠日志和中间表的错误代码字段反馈。
所以我在中间表里固定放两个字段:处理状态(P待处理、S成功、E失败)、错误消息。程序每处理一张单子就更新状态,后台作业跑完以后,看中间表就知道哪些成功了、哪些失败,失败还能直接看到报错号。这个设计比啥日志都直观。
4.2 后台作业里做审批增强校验(按ME55思路结合)
再来一个场景:采购信息系统有个审批的增强校验逻辑,原本是 ME55 前台点击审批的时候触发。后来业务量上来,每天审批的单据成百上千,IT就想把这些放到后台统一校验、统一处理。
思路是:把原在 BADI 或 Enhancement 里写的校验逻辑抽出来,单独做一个后台可执行程序,程序读还需要审批的采购订单,循环调 BAPI 或者直接更新审批表。校验失败的,打错误标记;通过的自动审批,写入日志。
这种改造要注意一个很现实的问题:审批增强通常绑定了当前登录用户、当前会话的一些上下文,比如用户名、日期、权限。到了后台作业里,执行用户统一是批处理账号,那校验里如果混了“当前用户必须拥有XX岗位权限”,就会莫名失败。我的经验是把用户上下文参数显式传到程序里,比如变式里指定“审批用户组”,或者作业执行前通过一段代码去取真实业务归属人,别依赖系统当前用户。
4.3 后台作业跑批里的字符串判断和语法兼容
最后提一下热搜里的“ABAP开发新语法”和“ABAP判断字符串是否是数字”。这两个东西在后台作业改造里经常冒出来。
旧系统里的存量报表,习惯用循环加 IF 判断字符。比如:
DATA: lv_input TYPE string. lv_input = '12345'. IF lv_input CO '0123456789'. " 是纯数字 ELSE. " 含非数字字符 ENDIF.CO 运算符—— "仅包含",是ABAP里判断字符串是否全由指定字符集组成的老办法。新语法时代更推荐用类 CL_ABAP_MATCHER 或者直接用内置函数:
IF matches( val = lv_input regex = '^[0-9]+$' ). ... ENDIF.但新的正则表达式在某些SAP版本对中文和全角字符处理有怪癖,如果用全文正则,后台日志报“非法字符”,多半是编码问题。稳妥做法是,生产环境里存量程序尽量少动语法,新的后台程序再全面用新语法。毕竟后台作业和语法版本绑定的是底包和内核,改动测试不充分,生产上出问题可不是开玩笑的。
5. 后台作业的监控、排查与日志管理
5.1 SM37 状态机与常见状态解读
后台作业一旦启用,后面就是无穷无尽的监控和救火。监控必经之地就是 SM37,进去能看到所有历史作业和当前在跑的作业。需要提一句,SM37默认显示的是”当前用户的作业“,要看到系统所有作业,需要把所有用户前面的勾都选上,或者设置成显示全局作业。否则你会觉得“作业根本没跑”,其实只是你挡在了自己账号范围里面。
状态列表里的常见值:
- 已计划:作业还没到开始时间,调度合法。只要没按“释放”,它不会动。
- 已释放:作业已经被调度器接管,等待后台进程。
- 正在运行:跑着呢,可以双击进去看作业步骤执行状态。
- 已完成:正常结束。注意“已完成”不代表业务没问题,只代表程序没崩溃。
- 已取消:被人为终止,或者程序死锁、内存溢出。
- 故障/计划中:出现异常,通常是调度器有问题,或者开始条件配置有误。
我在实际项目里见过很多次:作业状态第二天早上看是“已计划”而不是“已释放”,原因就是创建完之后没有点释放。另一个高频原因是作业计划用户被锁了,或者作业执行用户过期,导致调度器判定“不可执行”,状态直接回退成计划。
5.2 作业日志里查什么
后台作业的日志通常由两块组成:作业步骤的返回码,以及应用日志 SLG1 或者程序自定义日志。步骤返回码里,0 表示成功,4 表示有警告,8 表示有错误。很多老程序不会主动维护返回码,那建议在作业步骤上加一层“包装程序”,包装程序里先调用目标报表,然后根据目标报表运行结果设置返回码。否则日志好看,跑了等于白跑。
另外,日志里最容易骗人的是:作业状态“已完成”,但业务数据完全没变化。这种多半是程序自己内部处理了异常——比如抓到了一个数据库异常,但没传递给调用者,程序最后正常退出。所以我现在的习惯是,每个后台核心程序都要养成写自建日志表的习惯。自定义一张日志表,字段包括:作业名、开始时间、结束时间、处理记录数、失败记录数、错误消息。后台跑完,无论对错,都往这张表插记录。这样追踪起来比翻 SM37 里干巴巴的 spool 信息好太多。
5.3 作业重试、告警与人肉值班
后台作业不是跑完就不管了。生产建议必做两件事:一是作业失败后的自动重试机制,二是作业状态变化后的告警通知。
自动重试有一个巧办法:写一个监控程序,定时跑(比如每10分钟),扫描中间表中“失败状态”的数据,把连续重试次数少的重新触发一遍,前提是程序本身是幂等的。什么是幂等?同一张单子重复处理,不会生成两张销售订单。上面实战里那段代码就有幂等风险:如果BAPI创建订单成功,但COMMIT之前系统崩了,中间表状态还没来得及改成S,下次重试就会再创建一次订单。所以架构设计时分两步:先检查是否存在未完成的处理记录,再决定要不要并发处理,这比单纯日志兜底靠谱得多。
告警这块,后台作业错了得能推出去。常见做法是作业失败后,步骤里加一个“发送邮件”的步骤——只要上一步返回错误码8,该步骤就执行,把SM37里的错误信息拼到邮件正文发给IT和业务负责人。硬编码邮件地址是不推荐的,最好配一个收件人组配置表,让业务侧自己维护。
6. 后台作业的进阶玩法与避坑指南
6.1 作业之间如何串行执行
新建一个后台作业时,如果有多个步骤,它们天然是串行的。但如果你有多个独立的作业,想让它们在时间线上有依赖关系,比如“先跑完库存,再跑成本”,就需要在作业步骤中使用“后续作业”功能。
具体做法是在 SM37 里选中前置作业,点“后续作业”标签页,添加下一个作业名。前置作业正常完成(返回码0)后,系统自动释放后续作业。这里有个坑:如果前置作业完成了但返回码不是0,后续作业默认不被触发。你需要评估这个依赖是“即使前置有警告也要继续”,还是“必须无错误才继续”。系统不支持按返回码精细地去决定后续作业触发逻辑,所以有时候必须在程序代码里自己抛事件,或者用一个“总控程序”按照顺序一条条去提交作业。
6.2 并行处理与进程池上限
并行跑批是后台作业提效的重要手段。但SAP后台工作进程池是有上限的,一般系统几十个后台对话进程。作业配成A类高优先级,很容易抢占大量后台进程,把其他普通作业堵死。所以并行作业设计时,务必要考虑作业“并发度”。常见做法是控制同一时间只放行少数几个作业,比如每批最多运行5个并发作业,利用作业调度时间错开。
另一个方案是使用“外部调度工具”作为统一入口,后台作业只负责最终执行段,调度和依赖逻辑放到上层工具去。这在大型集团ERP里已经是标配了,减少对SAP内部调度器的过度依赖。
6.3 作业属性:权限、账号和生命周期管理
最后说一个特别容易被忽视的事:作业的主人是会变的。员工离职、岗位调整,账号被锁定,他之前创建的作业全都跑不起来了。所以必须建立作业台账,每季度清理一遍“僵尸作业”——从未运行过或者老是报错、没有业务归属人的作业。
台账里至少要有:作业名、业务描述、执行用户、调度频率、处理数据范围、最后成功/失败时间、运维负责人。没有台账,后台作业一多,基本就是黑盒,出故障了到处找人。
权限对象这一层,前面提过要建专用批处理账号,它的权限最好收敛到“仅后台处理”,不允许交互式登录,不允许改业务主数据,只允许执行作业和查看日志。防止某个后台程序在批处理账号下误改数据,还没有人肉审计痕迹。
6.4 常见问题速查表
我自己把这几年项目里后台作业的老毛病整理成一个速查表,基本覆盖了80%的现场事故:
| 现象 | 可能原因 | 快速处理 |
|---|---|---|
| 作业创建后不运行 | 没释放作业 | SM37选中作业后点“释放” |
| 作业到点没跑,状态已计划 | 调度器未释放,用户被锁或变式丢失 | 检查用户有效性,检查变式 |
| 作业卡在“正在运行”很久 | 数据库锁、后台进程全忙 | SM50/SM66查看进程,必要时终止进程 |
| 作业报错找不到变式 | 变式存成了个人变式,作业看不到 | 重建为通用变式 |
| 日志没输出 | 程序没有写日志,或spool设备错误 | 先查程序本身逻辑,再查输出设备 |
| 作业返回码0但数据没变 | 程序内部异常被静默吞掉 | 在程序内强制设置返回码,添加日志落库 |
| 作业跑到一半报内存不足 | 程序大数据量跑批,内存参数不够 | 调整内存参数或分批处理 |
| 作业处理数据双份 | 缺少幂等控制,重复触发 | 中间表加处理状态,加唯一索引 |
6.5 夜间批量窗口的优化建议
最后分享一个实战优化思路。夜间批量窗口是后台作业的主战场,优化前先看“作业持续时间分布图”,把每个作业的预计耗时列出来。通常你会发现20%的作业消耗了80%的窗口时间,这20%的基本都是逻辑复杂、数据全量的程序。优化方法就三步:
- 把处理逻辑从“全量扫描”改成“只处理增量”,比如按时间戳、按状态字段增量向后拉数据。
- 把大程序拆成若干小时段小批,而不是一个巨型作业一次搞定,降低单点失败的影响面。
- 关键作业步骤上做性能监控——用SAT、ST05或代码里打时间点,找到程序里最耗时的SQL,而不是凭感觉乱改。
我这边的真实感受是,后台作业本身不难,难的是看不到全局、没有台账、没有监控意识。把这三点补上,后台作业基本上就能替团队扛下大量重复性强、还得保证时效的苦活。
个人实际体会是,后台作业真正跑得稳,靠的不是单个程序写得完美,而是统一调度、分层监控和清晰的故障响应机制。每次把一个新的程序纳入后台作业之前,先跑几遍前台的试运行,确认所有交互都绕开了、变式里参数完整、执行用户正确,再提交到生产,就不容易出大乱子。