我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Kettle循环变量与作业循环执行:批量表统计实战

Kettle循环变量与作业循环执行:批量表统计实战 简介这份面向Kettle/ETL使用者的技术文档围绕循环作业中多表处理的变量设置问题展开适合已接触转换与作业、希望理清表名替换和循环控制思路的初中级数据集成人员。压缩包共1个docx文件约282KB内容以图文说明组织并配有示意图辅助对照关键步骤便于本地查阅和归档。资源已有2545人学习说明该主题在Kettle实践中具有较高关注度。文档结合User_Tables取表名、Trans脚本传递结果、循环控制器配合获取表行数与计数器累加等场景梳理变量TABLENAME复制、循环条件判断和表名动态替换的实现脉络并给出从多表名获取到循环执行的完整设计思路可帮助读者理解Kettle变量一次只能接收一个值时的处理方式减少在批量抽取、统计表行数等任务中反复配置的困惑。1. 从一张表清单出发Kettle 设置循环变量这件事的真实难点问题来自一个很具体的场景User_Tables里存着系统所有业务表的表名要做的事一句话就能说完——对清单里每张表跑一遍同样的统计 SQL比如SELECT COUNT(1) FROM A、SELECT COUNT(1) FROM B。语句除了表名完全一样人手动复制粘贴十张表还行一百张表就要出事。所以要用 Kettle 把「Kettle 设置循环变量」和「Kettle 作业步骤循环执行」这两件事做实一次配置循环跑完全部表失败能定位到具体哪一张。适合正在做数据仓库课程设计、ETL 毕设或者接手了一套需要批量抽取的历史作业的工程师。真正卡人的不是循环语法而是变量只能装一个值——表清单有 N 行变量却只有一个槽位这个矛盾不解决循环就是空转。2. 方案拆解两条脚本加三个变量的作业骨架设计先把结构定下来再谈细节。整套方案的核心是「作业管节奏、转换管干活」作业层负责循环计数和跳转转换层只做单次执行每次拿一个表名跑一次 SQL。这个分工看起来朴素但它决定了后面所有的排错方式——循环不退出就去看作业的计数器SQL 报错就去看转换里的变量替换两类问题永远不会混在一起。2.1 作业与转换的职责边界作业Job做三件事把表名清单取出来、维护循环下标、判断是否继续。转换Transformation做一件事接收TABLENAME变量执行SELECT COUNT(1) FROM ${TABLENAME}把结果写出去。之所以不把循环塞进一个转换里是因为 Kettle 的转换是「一次流一次跑」的模型在转换内部做循环要么靠 JavaScript 步骤里写 for 循环拼 SQL要么靠一堆复制记录到结果的技巧出了问题日志里只有一行「转换执行失败」根本看不出是第几张表挂的。放到作业层每一次循环都是一次独立的转换执行日志里清清楚楚是第几次、哪个表、耗时多少这种可观测性在批量任务里比省事重要得多。模块载体职责关键产出表名清单转换 get_tables从 User_Tables 取表名、编号、总数结果集每行带表名、序号、总数变量设置作业项把当前行的表名写入 TABLENAME根作用域变量循环判断作业项判断 IDX 是否超过总数布尔跳转循环体转换 count_rows用 ${TABLENAME} 执行统计行数结果计数器累加作业项IDX 自增跳回循环入口下一轮下标2.2 三个变量的命名与作用域整套方案只需要三个变量TABLENAME当前表名转换里用、TOTAL_ROWS表清单总行数控制循环上界、IDX当前第几张表从 1 开始。变量的作用域是新手最容易栽的地方Kettle 的变量有系统级、根作业级、父作业级几个层次子转换要读到变量必须把变量写在根作业作用域上。我一般统一在 JavaScript 步骤里用setVariable(IDX, value, r)第三个参数r就是根作用域这样不管这个步骤在哪一层子转换里被调用作业层都读得到。反过来如果用的是s系统级变量会污染整个 JVM 会话同一台机器上跑第二个作业时读到的可能就是上一个作业留下的残值这种脏数据非常难查翻车一次就记住了。命名上建议全部大写加下划线和 Kettle 内部变量如${Internal.Job.Filename.Directory}在视觉上区分开。另外要明确一点参数Parameter和变量Variable不是一回事。参数在转换启动时一次性传入、运行中不可改变量可以在循环过程中反复覆盖。循环场景必须用变量用参数的话第一轮之后就不动了。2.3 为什么不用一条 SQL 或外部脚本来解决有人会问这事用一条SELECT Table_Name, COUNT(1) FROM ...拼出来的动态 SQL或者干脆写个 Shell 脚本循环调kettle.sh不就行了三条路线我都试过结论是各有边界动态 SQL 适合表结构完全一致、跑一次就完的临时统计一旦要落不同的目标表、要做异常隔离就不行外部脚本循环调 Kettle 的缺点是启动开销大每次冷启动 JVM 几百毫秒一百张表就是几十秒的纯浪费而且变量传递要靠命令行参数日志分散在多个文件里。Kettle 作业内的循环恰好折中进程只启一次每轮循环的开销接近零同时保留了逐表失败隔离的能力。提示Kettle 4.4 这一代的作业项面板里循环是用「条件判断 跳转指向循环体首步」模拟出来的没有 for 语句这种语法糖。理解成「每执行完一轮把指针拉回入口再判断一次」就不会绕晕。还有一点值得提前说表名清单不建议在每轮循环里重新查一次。有些实现图省事把取表名的转换挂在循环体里结果是每轮都重新执行一遍清单查询理论上行得通实际上既慢又容易因为数据变化导致中途行数对不上、循环提前结束或者多跑几轮。正确做法是把清单一次性取完放进结果集循环期间只从结果集里按序取行。3. 表名池的构建从 User_Tables 取数并落到结果集循环的上游是「表名池」。这一步的目标很明确把User_Tables里的表名查出来并且让每条记录自带序号和总数这样循环体在任何一轮都能知道「我现在是第几个、一共几个」不需要额外再去查一次总数。3.1 表输入 SQL序号和总数一次取齐表输入步骤里写这么一条 SQL-- 取业务表清单同时算出序号和总行数供作业层循环使用 SELECT T.Table_Name, ROW_NUMBER() OVER (ORDER BY T.Table_Name) AS SEQ, -- 当前是第几张表 COUNT(1) OVER () AS TOTAL_ROWS -- 一共多少张表 FROM (SELECT DISTINCT Table_Name FROM User_Tables WHERE Table_Name LIKE T_% -- 只取业务表排除系统表 AND Status VALID) TROW_NUMBER()和COUNT(1) OVER ()是分析函数写法Oracle 9i 以后都支持如果目标库是老版本 MySQL 或者不支持窗口函数退化成「表输入 添加序列步骤 统计步骤 笛卡尔积」也能凑出来只是步骤多几个。这里坚持在 SQL 里算好是因为序号和总数必须在同一个快照里产生——分成两条 SQL 去查中间万一有人新增了一张表序号和总数就对不上了循环要么少跑一张要么最后多跑一轮报「结果集已空」。要注意ORDER BY的位置。上面这条 SQL 把排序放在ROW_NUMBER() OVER (ORDER BY ...)里而不是语句末尾加ORDER BY。原因很实在外层再写ORDER BY序号是按内层顺序给的两者一旦不一致序号和实际取到的表名就会错位循环跑出来的结果看着正常但统计的是别的表——这种 bug 没有报错只能靠对数的时候发现。3.2 复制记录到结果把清单交到作业层转换里最后一个步骤是「复制记录到结果」Copy rows to result。它的作用是把当前转换的行放进作业结果集供后续作业项读取。配置上有两个细节一是步骤必须连在主数据流末端不能挂在一个空分支上二是字段类型保持原样SEQ和TOTAL_ROWS用整数类型别让 Kettle 猜成字符串否则后面做数值比较时要额外转换。清单转换整体只有三步表输入 → 复制记录到结果外加一个可选的「写日志」步骤方便调试。我习惯在调试阶段挂一个「文本文件输出」把结果集落一份到本地看一眼表名对不对、序号连不连续正式跑的时候把这条分支断掉。3.3 在作业里把清单变成变量作业第一段Start → 转换 get_tables → 作业项「获取表数量」。「获取表数量」这一步的任务是把结果集的行数写进TOTAL_ROWS。Kettle 4.4 的作业里可以用 JavaScript 作业项读取结果集写法如下// 作业项获取表数量 // parent_job.getResult() 拿到上一步转换的结果getRows() 是结果集行列表 var rows parent_job.getResult().getRows(); var total rows.size(); // 写到根作用域供后续条件判断和子转换读取 parent_job.setVariable(TOTAL_ROWS, total); parent_job.setVariable(IDX, 1); // 循环下标初值从 1 开始 true; // 作业项返回 true 表示执行成功继续往下走getResult()拿到的就是上一个转换通过「复制记录到结果」交上来的数据。这里有个容易忽略的点setVariable的第二个参数必须是字符串Kettle 对变量值一律按字符串存直接传数字在某些版本上会被静默转成1.0之类后面parseInt解析出问题。统一 total转一次字符串省掉一类玄学问题。如果不想用 JavaScript也可以用「设置变量」作业项配合parent_job.getResult()的等价做法或者干脆在清单转换里用「统计」步骤算出一行总数单独复制到结果——但那样结果集里就混了两种行取行的时候要额外判断反而更绕。用一段 JavaScript 换来整个流程的清晰是划算的。注意IDX的初值一定要在这里显式设置。如果靠变量默认值或者上一轮残留第二次跑同一个作业时IDX可能还是上次结束时的值循环直接跳过或者一次都不跑。4. 循环体落地条件判断、计数器累加与 ${TABLENAME} 替换循环这一段是整个方案的重心。用 for 循环的视角对照着看最清楚条件判断是i size循环体是统计 SQL计数器累加是i。Kettle 里这三件事分别落在三个作业项上再加一个「把当前表名写进变量」的动作。4.1 条件判断IDX 与 TOTAL_ROWS 的比较作业项里放一个条件判断Kettle 4.4 里对应「Simple evaluation」/「检测条件」这类作业项具体名字各版本略有差异功能是执行一段返回布尔值的表达式。表达式写// 循环条件当前下标不超过总数就继续 parseInt(parent_job.getVariable(IDX, ), 10) parseInt(parent_job.getVariable(TOTAL_ROWS, ), 10);条件成立走循环体不成立走结束分支。两个parseInt的基数都写死 10别偷懒省掉——Kettle 变量是字符串08在某些解析器下会被当成八进制parseInt(08)在老实现上返回 0循环一次都不跑这种坑排查起来极其费时。4.2 取当前行并设置 TABLENAME循环体第一个动作是「从结果获取行」每执行一次从结果集里取下一行该行的字段会以字段名作为变量暴露出来。紧接着用一个「设置变量」作业项把Table_Name的值赋给TABLENAME变量名TABLENAME 变量值${Table_Name} 作用域根作业Root job为什么不直接用${Table_Name}而要多绕一层TABLENAME两个理由一是字段名大小写在不同数据库驱动下可能是TABLE_NAME、Table_Name或table_name直接引用迟早会遇到一个版本能跑、换个库就取不到值的情况统一转成自己命名的大写变量最省心二是后面所有转换里的 SQL 只认${TABLENAME}一个名字将来换了清单表的字段名只需要改这一处。4.3 循环体的统计 SQL 与变量替换开关循环体转换count_rows里就一个表输入加一个输出-- 表输入表名来自变量替换 SELECT COUNT(1) AS CNT FROM ${TABLENAME}关键配置在表输入步骤的选项里——必须勾选「替换 SQL 语句里的变量」Replace variables in script。这个勾选框默认是不勾的Kettle 会把${TABLENAME}当成一个普通字符串原样发到数据库报「表或视图不存在」。这个错我见过太多次日志里 SQL 打印出来就是FROM ${TABLENAME}一眼能认出来但第一次遇到往往要愣几分钟。同时要注意替换的时机变量替换发生在 SQL 发送之前替换的是纯文本所以如果表名里带空格、特殊字符或者需要双引号包裹比如 Oracle 里大小写敏感的表得在 SQL 里把引号写进去SELECT COUNT(1) AS CNT FROM ${TABLENAME}具体用双引号还是反引号还是方括号取决于目标数据库这一点不能照抄得按库来。输出的部分我一般用「文本文件输出」把TABLENAME、CNT、执行时间三列追加写到一个 CSV 里勾选「追加」而不是覆盖这样跑完一百张表得到一个完整的对账文件。如果目标是要入库就把文本文件输出换成表输出字段一一对应即可。4.4 计数器累加与跳回入口一轮跑完最后一个作业项做自增然后把执行指针跳回条件判断// 作业项计数器累加 var idx parseInt(parent_job.getVariable(IDX, ), 10); idx idx 1; parent_job.setVariable(IDX, idx); true;跳转的连线指向「条件判断」那一项而不是指向「从结果获取行」——这一点如果画错循环会变成先取行再判断最后一轮会多取一行导致结果集为空异常或者少跑最后一张表。判断在前、取行在后顺序不能颠倒。提示整个循环里没有「清空结果集」这一步。结果集是作业级的随着「从结果获取行」逐次消费自然前进千万不要在循环体里重新执行清单转换否则游标被重置永远停在第一张表上——这是最常见的「只跑了一张表就结束」的原因。5. 常见问题排查变量为空、循环不退出、脚本取不到值这一节列的五个问题基本都是我在自己的作业和别人的方案里真实踩过的按「现象 → 原因 → 解决」写方便对照。5.1 表输入报「表或视图不存在」日志里的 SQL 还带 ${}现象作业跑到循环体内报错日志打印的 SQL 是FROM ${TABLENAME}。原因有两个可能一是表输入步骤没勾「替换 SQL 语句里的变量」二是变量为空替换后变成FROM后面什么都没有。区分方法很简单在转换开头挂一个「写日志」步骤把${TABLENAME}打出来如果打印的是空字符串就是变量没设上。解决先确认「设置变量」作业项的作用域是根作业再确认TABLENAME的赋值发生在循环体转换执行之前顺序错了一切都白搭。5.2 循环跑完第一张表就停了现象日志里只有一张表的结果作业显示执行成功。原因通常是结果集被提前消费或者被重置——比如循环体里又执行了一次清单转换或者「从结果获取行」被放在了条件判断之前第一轮取走后判断失败。解决把清单转换移到循环外保证整个循环期间只执行一次检查条件判断的连线顺序确认是「判断 → 取行 → 统计 → 累加 → 回到判断」。5.3 循环永远不退出现象作业一直跑日志里表名反复是第一张或者报空指针。原因九成是IDX没真正写回根作用域在 JavaScript 步骤里只写了IDX idx 1而没有调用setVariable变量只在脚本内部有效下一轮读到的还是旧值。另一种情况是setVariable的作用域参数写成了s或省略写进了别的层级。解决统一用setVariable(IDX, value, r)并在累加步骤后面加一个写日志步骤打印当前值跑两轮就能确认是否真的在变。5.4 序号和实际表名错位统计了错误的表现象结果文件里每张表都有数据但对账时发现某几行的行数和表名对不上。原因多半是清单 SQL 里外层又加了一个ORDER BY与ROW_NUMBER() OVER (ORDER BY ...)的顺序不一致导致序号是按 A 顺序给的、数据是按 B 顺序取的。解决排序只保留窗口函数里的那一处外层不再写ORDER BY如果业务上需要特定的执行顺序就把它写进窗口函数的ORDER BY里。5.5 循环中断后无法续跑现象跑到第 60 张表时数据库连接超时整个作业失败重跑只能从第一张开始。原因是IDX是内存变量作业一停就没了。解决把执行进度落到一张日志表里每轮结束后写一条「表名 状态 耗时」重跑时清单 SQL 加上WHERE Table_Name NOT IN (SELECT Table_Name FROM ETL_LOG WHERE Status DONE)天然实现断点续跑。这个改造只多了两三个步骤但在跑几百张表的时候能救命。排查通用手法也顺带说一句把作业的日志级别调到 DebugKettle 会把每一步的变量快照打出来虽然输出很吵但定位变量类问题是最快的路。定位完记得把级别调回去否则日志文件一天能涨几百兆。6. 验收与答辩从单表跑通到全量跑完的检查清单方案跑通不等于可以交付。我验收这类循环作业固定走三步缺一步都不算完成。第一步单表验证。把清单 SQL 的WHERE条件收紧到只剩一张表例如加AND Table_Name T_A完整跑一遍作业确认结果文件里有且只有一行行数与手工执行SELECT COUNT(1) FROM T_A的结果一致。这一步验证的是变量替换链路通不通跟循环逻辑无关。第二步双表验证。放开到两张表重点看三件事结果文件是不是两行、顺序是否与清单一致、IDX在两轮里是不是 1 和 2。双表是循环类方案的分水岭——只跑一张表的实现和真正会循环的实现在这一步的表现完全不同。如果这里只出了一行直接回去看第 5 章的 5.2。第三步全量与异常验证。放开全部表跑一遍然后人为制造一次失败把某张表的统计 SQL 改成访问一个不存在的表名确认作业能报错、能定位到具体是哪张表、后续表不会继续跑。最后把对账文件里的总行数与数据库里的汇总数核对一遍。验收环节我习惯用一张固定表格记录跑完直接对照检查项判定标准不通过时先看哪表清单完整性结果行数 清单 SQL 单独执行的COUNT(1)清单 SQL 的过滤条件序号连续性SEQ 从 1 开始、无跳号无重复窗口函数的 ORDER BY变量替换日志中 SQL 已替换为真实表名表输入的替换变量勾选项循环次数日志中循环体执行次数 表数量IDX 自增与跳转连线结果一致性每张表行数与手工统计一致统计 SQL 本身失败隔离单表失败能定位、不污染其他表错误处理跳转配置答辩或者交付评审的时候讲这套方案的顺序也有讲究。别一上来就讲 JavaScript 怎么写先讲清楚「为什么变量一次只能装一个值所以需要一个外部的下标来驱动」把矛盾点摆出来评委自然能理解为什么要三个变量、为什么循环要放在作业层。然后画一遍执行路径清单转换跑一次 → 总数写进变量 → 判断 → 取行 → 设表名 → 跑统计 → 下标加一 → 回到判断。路径讲顺了剩下的都是配置细节被问到就打开作业截图指给对方看。如果时间充裕主动提一句断点续跑的改造思路这是加分项——说明你考虑过任务跑到一半挂掉的情况。最后一句经验这类循环作业最怕的不是写不出来而是写出来「看着能跑」但少跑了几张表还看不出来。从那以后我每次配完循环不管多急都强制先跑两张表的小样本、把结果文件打出来数行数再放全量。希望帮到你。本文还有配套的精品资源点击获取
返回列表