Liquibase变更集处理千万级数据表时陷入停滞问题求助
问题背景
数据库存储1.1亿条记录,已为11张表新增一列,通过外键关联EVENT表完成该列数据填充。目前所有目标列数据已填充完毕,但Liquibase连续2天处于停滞状态,未标记变更完成。执行的PL/SQL脚本如下:
DECLARE -- Fixed variables/constants TYPE tablearraytype IS VARRAY(11) OF VARCHAR2(100); additional_tables tablearraytype := tablearraytype('Table1', 'Table2', 'Table3', 'Table4', 'Table5', 'Table6', 'Table7', 'Table8', 'Table9', 'Table10', 'Table11'); l_table_name VARCHAR2(100 CHAR); l_sql VARCHAR2(4000); BEGIN EXECUTE IMMEDIATE 'ALTER SESSION FORCE PARALLEL DML PARALLEL 8'; EXECUTE IMMEDIATE 'ALTER SESSION FORCE PARALLEL QUERY PARALLEL 8'; FOR i IN 1..additional_tables.count LOOP BEGIN l_table_name := additional_tables(i); dbms_output.put_line('Updating org data for table ' || l_table_name); IF l_table_name = 'Table10' THEN l_sql := 'UPDATE Table10 faci ' || 'SET faci.OWNER_ORG_ID = (' || 'SELECT e.OWNER_ORG_ID ' || 'FROM CONTACT ci ' || 'JOIN EVENT e ON ci.EVENT_ID = e.EVENT_ID ' || 'WHERE ci.ID = faci.ID) ' || 'WHERE faci.OWNER_ORG_ID IS NULL'; -- Check if OWNER_ORG_ID is NULL ELSIF l_table_name = 'Table11' THEN l_sql := 'UPDATE Table11 rre ' || 'SET rre.OWNER_ORG_ID = (' || 'SELECT e.OWNER_ORG_ID ' || 'FROM RISK rr ' || 'JOIN EVENT e ON rr.EVENT_ID = e.EVENT_ID ' || 'WHERE rr.ID = rre.ID) ' || 'WHERE rre.OWNER_ORG_ID IS NULL'; -- Check if OWNER_ORG_ID is NULL ELSE l_sql := 'UPDATE ' || l_table_name || ' SET ' || l_table_name || '.OWNER_ORG_ID = ' || '(SELECT OWNER_ORG_ID FROM EVENT WHERE ' || l_table_name || '.EVENT_ID = EVENT.EVENT_ID) ' || 'WHERE ' || l_table_name || '.OWNER_ORG_ID IS NULL'; -- Check if OWNER_ORG_ID is NULL END IF; EXECUTE IMMEDIATE l_sql; COMMIT; -- Execute additional update for Table8 to set EVENT_TYPE_ID if NULL IF l_table_name = 'Table8' THEN l_sql := 'UPDATE Table8 rp ' || 'SET rp.EVENT_TYPE_ID = (' || 'SELECT e.EVENT_TYPE_ID ' || 'FROM EVENT e ' || 'WHERE rp.EVENT_ID = e.EVENT_ID) ' || 'WHERE rp.EVENT_TYPE_ID IS NULL'; -- Check if EVENT_TYPE_ID is NULL EXECUTE IMMEDIATE l_sql; COMMIT; dbms_output.put_line('EVENT_TYPE_ID update successful for Table8 table.'); END IF; dbms_output.put_line('Update successful for table ' || l_table_name); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Error occurred while updating table ' || l_table_name || ': ' || sqlerrm); END; END LOOP; END; /
可能的原因分析
- 事务机制冲突:脚本中每次UPDATE后手动执行
COMMIT,但Liquibase默认将整个变更集作为单事务处理。若未配置runInTransaction="false",Liquibase会在脚本执行完后尝试再次提交,此时无事务可提交,导致流程停滞。 - 隐性异常未捕获:脚本用
dbms_output输出日志,但Liquibase环境中无法捕获这些输出。若某步更新出现隐性异常(如锁等待超时、资源不足),脚本可能卡在循环中,Liquibase误以为变更仍在执行。 - 并行DML资源占用:开启并行DML/查询后,大表更新会占用大量数据库资源,导致会话长时间处于等待状态,Liquibase无法感知实际工作已完成。
解决建议
1. 调整Liquibase变更集配置
在对应变更集里添加runInTransaction="false",避免Liquibase事务机制与脚本手动提交冲突:
<changeSet id="update_owner_org_id" author="your_author" runInTransaction="false"> <sqlFile path="your_update_script.sql"/> </changeSet>
2. 检查数据库会话状态
登录数据库,查询Liquibase相关会话的状态,确认是否真的在运行或已完成:
SELECT s.sid, s.serial#, s.status, s.event, s.wait_time FROM v$session s WHERE s.program LIKE '%liquibase%' OR s.module LIKE '%liquibase%';
- 若会话状态为
INACTIVE,说明脚本已执行完成,可手动标记变更集为已执行; - 若会话处于
WAITING,查看event字段确认等待原因(如锁、IO瓶颈),针对性解决。
3. 优化脚本日志与异常处理
替换dbms_output为日志表记录,方便排查问题:
-- 先创建日志表(若不存在) CREATE TABLE CHANGE_EXEC_LOG ( TABLE_NAME VARCHAR2(100), EXEC_STATUS VARCHAR2(20), ERROR_MSG VARCHAR2(4000), EXEC_TIME TIMESTAMP DEFAULT SYSTIMESTAMP ); -- 修改脚本中的成功/异常分支: -- 成功更新后插入日志 INSERT INTO CHANGE_EXEC_LOG (TABLE_NAME, EXEC_STATUS) VALUES (l_table_name, 'SUCCESS'); COMMIT; -- 异常时插入日志 INSERT INTO CHANGE_EXEC_LOG (TABLE_NAME, EXEC_STATUS, ERROR_MSG) VALUES (l_table_name, 'FAILED', sqlerrm); COMMIT;
4. 优化大表更新策略
降低并行度或分批次更新,避免资源长时间占用:
-- 示例:分批次更新某表 DECLARE l_min_event_id NUMBER; l_max_event_id NUMBER; l_batch_size NUMBER := 100000; BEGIN SELECT MIN(EVENT_ID), MAX(EVENT_ID) INTO l_min_event_id, l_max_event_id FROM Table1 WHERE OWNER_ORG_ID IS NULL; FOR l_start IN l_min_event_id..l_max_event_id BY l_batch_size LOOP EXECUTE IMMEDIATE 'UPDATE Table1 t SET t.OWNER_ORG_ID = (SELECT e.OWNER_ORG_ID FROM EVENT e WHERE t.EVENT_ID = e.EVENT_ID) WHERE t.OWNER_ORG_ID IS NULL AND t.EVENT_ID BETWEEN :start AND :end' USING l_start, l_start + l_batch_size - 1; COMMIT; END LOOP; END; /
5. 手动标记变更完成
确认所有数据已正确填充后,用Liquibase命令手动标记变更集为已执行:
liquibase markNextChangeSetRan --changeLogFile=your_changelog.xml
内容的提问来源于stack exchange,提问作者AJ23
相关产品推荐
相关产品推荐

