DB2导入畸形数据时Ingest语句回滚失效问题排查
问题:DB2导入出错时回滚无效,原表数据被部分替换
我有一个Bash Shell脚本用于执行DB2 SQL文件,目的是用指定文件内容完全替换数据库表MY_TABLE的数据。需求是:当导入文件存在错误时,保留原表数据。比如原表有两行数据,导入文件含一行畸形字符串数据,预期原表内容不变,但实际有效行替换了原表数据。脚本中已关闭自动提交,设置出错停止,错误时执行回滚命令,但回滚未生效,请问脚本问题出在哪里?
附script.ksh代码
SQL_FILE=/app/scripts/script.db2 LOG=/app/logs/script.log # ... # 数据库连接初始化代码 # ... # +c: 关闭自动提交 # -v: 回显命令 # -s: 出错时停止执行 # -p: 显示交互提示符(用于调试) # -td@: 使用'@'作为SQL文件中的语句分隔符 db2 +c -s -v -td@ -p < $SQL_FILE >> $LOG if [ $? -gt 2 ]; then echo "发生错误;正在回滚数据" >> $LOG db2 "ROLLBACK" >> $LOG exit 1 fi # 无错误则提交更改 db2 "COMMIT" >> $LOG
附script.db2代码
ingest from file '/app/temp/values.csv' format delimited by ',' ( $C1 INTEGER EXTERNAL, $C2 INTEGER EXTERNAL ) restart new 'SCRIPT_JOB' replace into DATA.MY_TABLE ( C1, C2 ) values ( $C1, $C2 )@
问题根源与解决办法
1. REPLACE INTO的特性直接导致数据丢失
REPLACE INTO的执行逻辑是先清空目标表所有数据,再插入新数据。哪怕导入中途出错,已经成功插入的有效行会保留,而清空表的操作已经完成——这时候回滚也恢复不了之前的数据,因为清空操作已经在事务内执行了。
2. 脚本回滚逻辑完全无效
当db2命令执行SQL文件出错退出后,当前的DB2会话已经结束。后续单独执行db2 "ROLLBACK"是在一个全新的会话里,这个新会话根本没有需要回滚的事务,所以回滚命令等于白执行。另外,$? -gt 2的判断条件也不准确,DB2的退出码中2就是错误,大于2的场景覆盖不全,会漏掉很多错误情况。
3. INGEST命令的默认行为是分段提交
就算关闭了自动提交(+c),INGEST默认会批量提交数据,处理一部分就提交一部分,导致错误发生前的操作已经无法回滚。
修改方案
方案一:替换REPLACE INTO为DELETE + INSERT组合,控制事务边界
把script.db2改成以下内容,用显式的事务包裹删除和插入操作,一旦出错就回滚整个事务:
-- 出错时自动回滚并退出 WHENEVER SQLERROR ROLLBACK WORK EXIT@ -- 开启事务 BEGIN WORK@ -- 先删除表数据(这一步在事务内) DELETE FROM DATA.MY_TABLE@ -- 执行导入插入,禁用批量提交确保在一个事务内 ingest from file '/app/temp/values.csv' format delimited by ',' DISABLE_BULK_INSERT ( $C1 INTEGER EXTERNAL, $C2 INTEGER EXTERNAL ) restart new 'SCRIPT_JOB' insert into DATA.MY_TABLE ( C1, C2 ) values ( $C1, $C2 )@ -- 无错误则提交 COMMIT WORK@
方案二:修正脚本的会话一致性问题
如果要保留脚本的回滚逻辑,必须保证回滚在同一个DB2会话中执行。可以用here-doc方式把所有操作放在同一个会话里:
SQL_FILE=/app/scripts/script.db2 LOG=/app/logs/script.log # ... # 数据库连接初始化代码 # ... # 用here-doc执行所有操作,保证会话一致 db2 +c -s -v -td@ -p >> $LOG << EOF BEGIN WORK@ DELETE FROM DATA.MY_TABLE@ ingest from file '/app/temp/values.csv' format delimited by ',' DISABLE_BULK_INSERT ( \$C1 INTEGER EXTERNAL, \$C2 INTEGER EXTERNAL ) restart new 'SCRIPT_JOB' insert into DATA.MY_TABLE ( C1, C2 ) values ( \$C1, \$C2 )@ COMMIT WORK@ EOF # 检查执行结果 if [ $? -ne 0 ]; then echo "发生错误;正在回滚数据" >> $LOG db2 +c "ROLLBACK WORK" >> $LOG exit 1 fi
内容的提问来源于stack exchange,提问作者Xirema
相关产品推荐
相关产品推荐

