我要提问
ARTICLE DETAIL

资讯详情

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

Kettle循环变量实战:多表SQL批量执行与避坑指南

Kettle循环变量实战:多表SQL批量执行与避坑指南 简介围绕 Kettle 设置循环变量与循环作业控制整理的 docx 文档面向已接触 ETL、需要批量处理多张数据表的工程师与数据开发学习者内容兼顾概念梳理与实操查阅。内容以 User_Tables 中存放的表名为例从 SQL 表名替换这一常见问题切入说明单个变量只能承载一个值的限制并借 Trans 脚本把待执行表名复制到运行结果再结合获取表数量、循环控制器、获取表行数与计数器累加等步骤拆解类似 for 循环的执行思路同时交代变量 TABLENAME 的赋值与调用方式。压缩包仅含 1 个 docx 文件大小约 282KB单文件结构直观便于快速定位关键步骤。该文档已有 2545 人学习适合用作 Kettle 循环抽取、批量统计表行数及作业流程编排的参考也能帮助读者理解表名变量传递和循环控制步骤之间的配合。1. 一条 SQL 模板要落在 40 张表上Kettle 设置循环变量到底卡在哪接手一套老系统的时候最烦的不是写 SQL而是同一条 SQL 要换 40 个表名跑 40 遍。控制表里躺着待处理的表名模板语句除了${TABLENAME}之外一个字都不变手写 40 条 SELECT 显然不是人干的事。Kettle 设置循环变量就是为这种场景准备的从控制表里把表名逐条读出来每读一条就赋给变量再让下游转换用${TABLENAME}去拼 SQL作业层负责判断「还有没有下一张表」。适合做批量统计、批量抽取、批量同步的 ETL 从业者也适合刚接手别人 .kjb 文件、看到跳线就发怵的同学。但真动手就会发现Kettle 的循环不是写个 for 就完事它靠作业项加变量拼出来的变量作用域、结果集生命周期、类型转换任何一环选错循环都会在第二轮悄悄翻车。2. 先打通 ${TABLENAME}变量替换与作用域的两道关循环跑不起来十次里有八次不是循环逻辑写错而是变量根本没传进去。所以顺序不能反先证明「一个变量能被一条 SQL 用上」再去谈「一堆表名轮流用」。这一章只做两件事把表输入里的变量替换打开把 Set Variables 的作用域选对。2.1 表输入里的变量引用与「替换 SQL 语句里的变量」开关Kettle 的表输入步骤对变量不是默认解析的你不勾那个复选框${TABLENAME}会被当成普通字符串原样发给数据库然后数据库回你一句「表或视图不存在」。这是最常见的第一次翻车点。原项目里的做法是统计每张表的总行数落到配置上就是下面这条模板。-- t03_统计单表行数.ktr 中「表输入」步骤的 SQL -- 注意变量外侧不要加单引号否则会被当成字符串常量 SELECT COUNT(1) AS CNT FROM ${TABLENAME};代码里只有一处变量引用但参数得配对表输入对话框底部的「替换 SQL 语句里的变量」必须勾上「从步骤插入数据」保持不勾因为这里的表名来自作业变量不来自上游流「记录数量限制」填 0避免只取一行导致统计失真。SQL 末尾的分号在部分数据库驱动下会报语法错遇到就删掉。变量名大小写要和 Set Variables 里定义的完全一致Kettle 的变量名区分大小写${tablename}和${TABLENAME}是两个东西这种细节在日志里只表现为「表不存在」很容易查半天。提示验证变量有没有真的替换成功不要靠猜。把日志级别调到 Debug表输入执行时会把展开后的 SQL 打出来直接看 FROM 后面跟的是不是你要的表名。2.2 Set Variables 的四个作用域选错就是「变量明明设了却读不到」Kettle 的变量是有作用域的同一个名字可以在不同层级存在不同的值。设置变量时的「Valid in the Java Virtual Machine」「Valid in the root job」「Valid in the parent job」「Valid in the parent job grandparents」四个选项决定的不是变量能不能用而是谁读得到、读到哪一份。作用域选项生效范围典型用途Valid in the Java Virtual Machine整个 JVM 内所有作业和转换跨多层作业传参、全局开关Valid in the root job当前根作业及其下所有子项循环计数器、当前表名Valid in the parent job直接父作业两层嵌套时的参数回传Valid in the parent job grandparents父作业及更上层多层嵌套作业循环变量我一般统一选「Valid in the Java Virtual Machine」理由很实际循环体通常是一个独立转换转换读取父作业变量在多数版本里没问题但只要中间多套一层子作业选 root job 就可能读不到。选 JVM 级别是把不确定性一次性消掉代价是同名变量会互相覆盖所以命名上要带业务前缀比如TABLENAME_STAT别图省事叫T。反过来如果循环体里又启动了别的作业并且也用了同名变量JVM 级别就会互相踩这就是选太宽的代价。我的习惯是只在本作业内循环的计数器用 root job要传给子转换的表名用 JVM两者分开命名出问题的时候一眼能定位。2.3 最小验证先让一张表跑通再谈循环在拼整个作业之前先做一次单表验证五分钟能省两小时。新建一个转换里面只放两个步骤一个「设置变量」一个「表输入」。设置变量里填TABLENAME值写死成一张真实存在的表作用域选 JVM表输入用 2.1 里的模板勾上变量替换。执行看日志里展开的 SQL 是不是指向那张表结果集里是不是只有一行 CNT。这一步跑通说明变量替换链路没问题后面循环出问题就只可能是「变量没更新」或者「结果集被冲掉」排查范围直接缩小一半。跑不通八成是勾选漏了或者变量名拼错问题在原地不用往循环上赖。还有个容易被忽略的点写死验证时用的表名别用系统表或者临时表用你自己控制表里真实存在的那张业务表这样连字符集、大小写、schema 前缀这些坑一起验证掉了。有些数据库需要在表名前带 schema${TABLENAME}里就得包含 schema或者模板写成${SCHEMA}.${TABLENAME}这个在单表验证阶段就该定下来别等循环跑起来才发现拼出来的表名全都不对。3. 把表名列表搬进结果集控制表设计与两个准备转换单表验证通过之后问题从「怎么用变量」变成「怎么把一堆表名按顺序喂给变量」。Kettle 的变量一次只能持有一个值项目正文里说得很直白两张以上的表就没法直接替换${TABLENAME}必须有个东西把多个表名存起来逐条遍历。这个「东西」就是结果集也就是转换执行完输出到作业内存里的那批行。3.1 控制表结构与取表名的 SQL控制表是所有批量作业的入口设计得糙后面循环再优雅也白搭。最简结构三列就够了表名、启用标记、上次执行时间。-- 控制表存放本轮需要处理的表名清单 CREATE TABLE user_tables ( table_name VARCHAR(64) NOT NULL, -- 待处理的物理表名含 schema 时写成 schema.table enabled CHAR(1) DEFAULT Y,-- 开关临时跳过某张表不用改作业 last_run DATETIME -- 上次处理时间便于断点重跑 );取表名的 SQL 一定要带ORDER BY这不是洁癖。结果集是按转换输出的物理顺序取行的没有排序数据库返回的顺序在不同次执行之间可能不同导致你昨天跑到第 17 张失败今天重跑时第 17 位换成了另一张表日志对不上号。-- t01_列出待处理表.ktr 中「表输入」步骤的 SQL SELECT table_name FROM user_tables WHERE enabled Y ORDER BY table_name;这个转换的输出端接「复制记录到结果」步骤把整批表名写进作业结果集。作业里后续的「从结果获取记录」每次读一行读一行少一行这是 Kettle 里最接近迭代器的机制。参数上要注意两点。第一「复制记录到结果」只能出现在转换的末端中途接会影响上游步骤的提交行为第二转换里别再加「排序记录」之类的步骤去重复排序SQL 里排好就够了多一道内存排序在小结果集上没意义在大结果集上还占内存。3.2 用「获取系统信息」算总数并写进变量只有结果集还不够循环总得知道什么时候停。原项目里用了一个独立的「获取表的数量」步骤思路是单独跑一个转换把总数算出来设成变量。实现上两条路一条是直接 SQL count一条是转换里用「获取系统信息」步骤取输入行数。-- t02_统计待处理表数量.ktr 中「表输入」步骤的 SQL -- 与 t01 的 WHERE 条件必须严格一致否则总数和实际取到的行数对不上 SELECT COUNT(1) AS TOTAL FROM user_tables WHERE enabled Y;外部 SQL 简单直接缺点是两处 WHERE 条件要人工保持同步改了一处忘了另一处就会出现「总数是 40、实际只取到 38 行」的诡异现象循环跑到第 39 次时取不到行作业报错退出。想避开这个同步问题就在 t02 里复用 t01 的查询结果「表输入」拉全量行接「获取系统信息」步骤类型选 Number of rows in input输出字段名TOTAL再接「设置变量」步骤把TOTAL字段写成变量作用域 JVM。这样条件只维护一份但代价是会把全表数据拉进内存表多的时候不划算。我的取舍是控制表行数通常在几百以内用「获取系统信息」更安全数据量大就老老实实两条 SQL并在变更时把两条一起改。3.3 结果集的字段名、类型与列顺序结果集不是无类型的内存表它的列名和类型来自转换的输出流下游「从结果获取记录」和「设置变量」都得按列名对上。三个细节值得单独拎出来。第一只输出必要的列。如果为了调试在查询里加了rownum或者sysdate结果集就多出几列后面「从结果获取记录」拿到的字段结构跟着变设置变量时选错列取到的就不是表名。第二字段类型保持一致。table_name在控制表里是 VARCHAR转换输出的就是 StringSet Variables 里直接取流字段不会有问题但如果某一步做了CONCAT或者类型转换字段可能变成别的类型变量里存进去的文本就会带上多余空格或者变成数字格式。第三结果集是全作业共享的同一个作业里任何一个转换勾了「复制记录到结果」都会覆盖上一份。这条在循环场景下是致命的下一章会专门讲这里先记住结论循环体里的转换绝对不要再往结果集里写东西。4. 作业侧的 for 三段式初始化、判断、步进到这一步变量能用、表名列表也有了剩下就是在作业里把for(int i0;isize;i)翻译成 Kettle 的跳线。项目正文里对应关系写得很清楚循环控制器对应isize获取表行数对应循环体计数器累加对应i。下面把这套骨架落成具体配置。4.1 作业骨架与各作业项的跳线关系先把结构摆出来再逐项填配置。整条作业一共七个作业项跳线是一条主链加一次回环。顺序作业项关键配置对应 for 的哪一段1Start只留一个起点进入循环前2Transformation指向 t01_列出待处理表.ktr准备列表3Transformation指向 t02_统计待处理表数量.ktr求 size4Set VariablesCOUNTER 1i 05Simple EvaluationCOUNTER TOTALtrue/false 两条跳线i size6Transformation指向 t03_统计单表行数.ktr循环体7JavaScriptCOUNTER 自增 取下一行表名i跳线的接法才是关键。第 5 项 Simple Evaluation 的 true 出口接第 6 项false 出口接「成功」第 7 项 JavaScript 执行完跳线回接到第 5 项形成闭环。第 6 项执行失败时走红色错误跳线可以直接连到「成功」之外的失败分支也可以先记日志再中止最后一种做法下一章展开。这里有个顺序问题值得说清楚取下一行表名到底放在循环体的前面还是后面。如果放在循环体前面第一轮循环取到第一条表名正常如果放在后面第一轮循环时还没有表名可用循环体的 SQL 就会拼出空表名。所以「从结果获取记录」必须放在循环体之前、进入循环体之后立刻执行这也是为什么第 7 项要把「取下一行」和「计数器自增」放在同一个 JavaScript 里——它们是同一次迭代结束时的两个动作。4.2 Simple Evaluation 的条件怎么写才不误判Simple Evaluation 在 4.4 里就是「简单评估」作用是把两个值比一下按结果走 true 或 false 跳线。配置里最容易错的是「值」的来源类型它可以是固定值、字段、变量、或者上一步的结果。这里两边都得选变量。左侧选COUNTER比较符右侧选TOTAL。如果右侧误配成固定值条件永远不会随数据变化如果左侧选成了字段作业项拿不到流字段会直接报配置错误。// 如果 Simple Evaluation 的界面选项让你不放心可以用 JavaScript 作业项替代 // 该脚本返回 true 继续循环返回 false 结束循环 var counter parseInt(parent_job.getVariable(COUNTER, 0), 10); var total parseInt(parent_job.getVariable(TOTAL, 0), 10); if (isNaN(counter)) { counter 0; } if (isNaN(total)) { total 0; } // 两个变量都是字符串必须先转数字再比较否则 9 10 会判成 false (counter total);代码里的核心就一句比较但注释里的两个坑值钱getVariable拿回来的一定是字符串直接比大小会走字典序parseInt一定带基数 10某些环境里前置 0 会被当八进制处理。JavaScript 作业项的好处是能顺手把变量值打到日志里排查时能直接看到第几轮、比较的是哪两个数比盯着跳线颜色猜强得多。注意「简单评估」比较的是变量当前值不是变量在某个时刻的快照。如果 COUNTER 在循环体内被改成了 root job 作用域而评估读的是 JVM 作用域你看到的就是一个永远不变的数字表现为死循环或者只跑一轮。4.3 步进JavaScript 里做计数器累加与取下一行计数器自增和取下一行表名放在同一个 JavaScript 作业项里逻辑上是「一次迭代收尾」。原项目里这个步骤叫「计数器累加」同时承担TABLENAME的赋值思路一致。// 作业项JavaScript循环收尾 // 职责一COUNTER 自增并写回 // 职责二把下一行表名的读取交给「从结果获取记录」作业项这里只做校验 var counter parseInt(parent_job.getVariable(COUNTER, 0), 10); if (isNaN(counter)) { counter 0; } counter counter 1; // setVariable 只接受字符串传数字会被隐式转换显式写 更稳妥 parent_job.setVariable(COUNTER, counter); // 把本轮已经跑完的表名记下来便于失败后定位 var lastTable parent_job.getVariable(TABLENAME, ); parent_job.setVariable(LAST_TABLE, lastTable);三个参数要说明白。parent_job是 Kettle 作业脚本里的内置对象指向当前作业用它读写变量才能让跳线条件读到setVariable的第二个参数必须是字符串直接传数字在某些版本里会变成浮点格式getVariable的第二个参数是默认值第一次执行时变量还不存在不给默认值会拿到 null 再被 parseInt 转成 NaN计数器就废了。至于「取下一行表名」稳妥做法是把它单独做成一个「从结果获取记录」作业项插在 Simple Evaluation 的 true 出口和循环体之间。它每次执行取一行取到就把字段写进流里取不到就走 false 出口——这个出口可以直接接到「成功」作为计数器的第二重保险。计数器用来记进度和打日志结果集耗尽用来兜底两个条件任何一个成立都能让循环停下来。4.4 循环体转换如何拿到 TABLENAME循环体本身就是一个普通转换里面放表输入和结果输出唯一特殊的是它引用了${TABLENAME}。变量能不能读到取决于前面 Set Variables 的作用域和「从结果获取记录」的字段赋值是否真的执行了。赋值动作通常在循环体之前的一个 Set Variables 作业项里完成字段选「从结果获取记录」输出的table_name变量名TABLENAME作用域 JVM。这里有个界面细节Set Variables 里每一行都有「Stream column」和「Value」两列如果手滑把表名敲进了 Value 列变量就永远是一个固定字符串循环跑 40 次全在统计同一张表——这个错误肉眼极难发现因为作业不报错日志正常只是结果全都一样。循环体转换执行完之后还会回到 JavaScript 做自增然后回到 Simple Evaluation。整条链路里循环体转换是最不该有副作用的一环它只读变量、只写业务结果不设变量、不碰结果集、不改计数器。把职责切干净出问题时才能一眼看出是「变量没传进去」还是「循环没转起来」。5. 避坑排查循环只跑第一轮、变量永远不变的五种现场循环作业的报错往往不报在循环本身而是报在最后一张表上或者干脆不报错、结果全错。下面五条是按踩坑频率排的每条都按「现象 → 原因 → 解决」写对着日志一条条排就行。5.1 现象循环只跑了一轮就结束原因通常有两个。一是 Simple Evaluation 的 COUNTER 初值没设getVariable拿到空串转成 NaN比较结果为 false第一次判断就走了 false 分支二是 TOTAL 变量没设成功右边取到 01 0不成立。解决在 Start 之后单独插一个 Set Variables把 COUNTER 设成 1、TOTAL 设成 0 作为兜底初值跑之前在日志里把两个变量都打出来确认。另外注意 TOTAL 那个转换有没有真的执行完转换失败时变量是不会被设置的作业却可能继续往下走。5.2 现象第二轮开始 TABLENAME 还是第一张表原因Set Variables 里把表名写成了固定值而不是引用「从结果获取记录」的流字段或者「从结果获取记录」被放在了循环体后面导致第一轮用的是上一轮遗留的值。解决打开 Set Variables 检查每一行的来源列字段类型必须是 Stream column 而不是 Value把「从结果获取记录」的作业项位置挪到 Simple Evaluation 的 true 出口之后、循环体之前并在它后面紧跟 Set Variables保证顺序固定。5.3 现象跑到第二轮表名列表就空了或者作业突然只处理一张表原因循环体转换里勾了「复制记录到结果」。结果集是作业级共享的循环体每跑一次就把自己的输出写进结果集把原来的表名列表覆盖掉下一轮「从结果获取记录」取到的是业务数据不是表名。解决循环体转换里去掉「复制记录到结果」步骤或者把它改成写文件、写日志表。这条属于血泪经验症状隐蔽日志里只会显示表名变成了奇怪的值不会提示结果集被覆盖。5.4 现象表输入报「表或视图不存在」但表确实在原因有三表输入没勾「替换 SQL 语句里的变量」SQL 里写成了${TABLENAME}带了引号变量作用域选的 parent job而循环体转换隔了一层读不到。解决先看 Debug 日志里展开后的 SQL 长什么样引号和表名一目了然作用域统一改成 JVM 级别确认变量名大小写与 SQL 里完全一致。5.5 现象计数器变成 1、11、111或者循环次数比表数多一次原因变量是字符串COUNTER 1做的是拼接不是加法循环次数多一次通常是判断用了而计数器从 0 开始或者计数器从 1 开始但判断用了。解决所有数值运算前先parseInt(x, 10)初值和比较符成对确认COUNTER 1配 TOTALCOUNTER 0配 TOTAL选定一种就别混用。日志里把每轮的 COUNTER / TOTAL 打出来跑一次就对上了。6. 进阶把循环跑稳的三件事循环能转起来只是及格线生产上还得管三件事跑完能不能查、跑挂能不能续、跑快会不会互相踩。这三件事决定了这套作业能不能长期放着不管。第一件每轮落一条日志。在循环体转换的末端加一个「表输出」把表名、行数、开始时间、结束时间、执行状态写进etl_log表字段和业务结果分开存。这样循环跑完不用去翻 Kettle 的日志文件一条 SQL 就能查出哪张表失败、失败在第几轮。日志表的主键用「表名 批次号」批次号在作业开始时用SELECT DATE_FORMAT(NOW(),%Y%m%d%H%i%s)生成一次写进变量整批共用重跑时换一个批次号历史记录不会被覆盖。第二件失败不要整个作业中断。循环体的错误跳线红色接到一个「写日志」转换把失败的表名和错误信息记下来然后跳回 JavaScript 继续下一轮而不是直接连「成功」结束。这样 40 张表里有 3 张挂了另外 37 张照样跑完第二天只补那 3 张。代价是要在作业结束时加一个汇总判断统计失败条数非零就返回非零退出码让调度系统能感知到。控制表里的last_run字段也在这时候更新重跑时加一个WHERE last_run IS NULL OR last_run 批次开始时间的条件天然支持断点续跑。第三件别用并行去加速循环。有人会把 Start 作业项设成「运行下一项」并复制多份循环体想让 40 张表并发跑。问题在于变量是作业级共享的多个副本同时改TABLENAME和COUNTER结果就是 A 副本拿到的表名被 B 副本覆盖SQL 打到错误的表上还可能因为计数错乱提前结束。稳妥做法是保持单作业串行把并发压力交给数据库端或者按表名哈希把清单拆成 4 个分片每个分片一个独立作业变量名各带分片前缀互不干扰。验证这套东西有没有真跑对最直接的办法是拿结果对账循环跑完后用一条SELECT SUM(cnt) FROM etl_log WHERE batch_no ?和源库的总行数比一比两边对得上才说明每张表都真的统计了而不是某几张表被重复统计。这个对账动作我现在是强制做的因为吃过一次亏Set Variables 里的表名被写成了固定值40 轮全在统计同一张表日志行数、计数器、循环次数全都正常只有对账能发现。从那以后我每次拼完循环作业都强制走一遍「单表验证 → 两表跑通 → 全量对账」三步不管多简单的需求都不跳过。循环这种东西跑起来容易跑对很难中间任何一环的静默失败都不会给你报错。希望帮到你。本文还有配套的精品资源点击获取
返回列表