You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 07:32:03