全部產品
Search
文件中心

PolarDB:ALTER TABLE ... ENABLE/DISABLE CONSTRAINT

更新時間:Sep 17, 2025

在為大表添加或管理約束時,全表掃描和長時間的鎖等待會影響業務效能和可用性。PolarDB PostgreSQL版(相容Oracle)提供的ENABLE/DISABLE CONSTRAINT功能,允許您在不刪除約束定義的前提下,臨時禁用約束檢查,從而安全、高效地完成資料匯入、資料修複和約束上線等操作。

功能簡介

ENABLE/DISABLE CONSTRAINT功能通過修改約束的活動狀態來最佳化特定情境下的資料庫效能。該功能支援CHECKFOREIGN KEYPRIMARY KEYUNIQUE四種類型的約束。

  • DISABLE CONSTRAINT:將指定約束置為禁用狀態,對於所有約束類型,資料庫將不再對新的資料寫入和更新操作(INSERTUPDATE)進行校正。

  • ENABLE CONSTRAINT:將指定約束置為啟用狀態。此操作包含兩種校正模式:

    • ENABLE CONSTRAINT(預設模式):啟用約束時,不僅會對後續的新資料進行校正,還會掃描表中的全部存量資料,確保它們都符合約束規則。對於大表而言,此過程可能非常耗時並消耗大量I/O資源。

    • ENABLE CONSTRAINT ... NOT VALID:啟用約束時,僅對後續的新資料進行校正,跳過對存量資料的校正。此模式的核心優勢是速度快,因為它假定表中的現有資料已經滿足約束。

前提條件

您的叢集版本需為Oracle文法相容 1.0,且修訂版本為2.0.11.9.11.0及以上。

說明

您可在控制台查看核心小版本號碼,也可以通過SHOW polardb_version;語句查看。如未滿足核心小版本要求,請升級核心小版本

使用限制

  • 外掛程式依賴:此功能依賴polar_constraint外掛程式,需按指導完成安裝配置

  • 約束依賴限制

    1. 被參考條件約束的禁用限制

      如果一個主鍵(PRIMARY KEY)或唯一約束(UNIQUE)被其他表的外鍵約束(FOREIGN KEY)所引用,那麼在禁用或刪除引用它的外鍵約束之前,無法直接禁用此主鍵或唯一約束。系統會阻止此操作以維護資料完整性。

    2. 外鍵約束的啟用限制

      在啟用一個外鍵約束(FOREIGN KEY)之前,系統會檢查它所引用的、位於另一張表上的主鍵(PRIMARY KEY)或唯一約束(UNIQUE)的狀態。只有當被引用的主鍵或唯一約束處於啟用(ENABLED)狀態時,該外鍵約束才能被成功啟用。如果被引用的約束處于禁用狀態,啟用外鍵約束的操作將會失敗並報錯。

  • 相容性

    • 此功能為PolarDB PostgreSQL版(相容Oracle)擴充功能,原生PostgreSQL不支援禁用主鍵(PRIMARY KEY)和唯一約束(UNIQUE)。

    • 文法風格基於PostgreSQL,與Oracle的ENABLE VALIDATEENABLE NOVALIDATE等文法不同。

安裝外掛程式

首次使用該功能前,需要為叢集安裝polar_constraint外掛程式。

  1. 請檢查您的PolarDB PostgreSQL版(相容Oracle)叢集是否已預裝polar_constraint外掛程式。如果返回了polar_constraint外掛程式及其版本資訊,則表示您的叢集已成功安裝外掛程式。

    SELECT extname, extversion FROM pg_extension WHERE extname = 'polar_constraint';
  2. (可選)若您的叢集尚未安裝,則需要按照以下步驟安裝外掛程式,對於不同核心版本,安裝外掛程式方式不同:

    1. (可選)對於修訂版本在2.0.11.9.25.0以下的叢集:您需先前往PolarDB控制台配置與管理 > 參數配置頁面,修改叢集參數shared_preload_libraries,添加polar_constraint

      說明
      • 若參數中已有其他外掛程式,請使用逗號分隔。例如:pg_stat_statements,polar_constraint

      • 修改當前參數會自動重啟叢集,這將導致商務服務中斷。請在業務低峰期或維護視窗內進行操作。

    2. 為您指定的業務資料庫中安裝外掛程式。

      -- 安裝擴充
      CREATE EXTENSION IF NOT EXISTS polar_constraint;

文法參考

添加約束

  • 禁用狀態:添加約束時,會掃描表中的全部存量資料,確保它們都符合約束規則。

    ALTER TABLE table_name ADD CONSTRAINT constraint_name DISABLE;
    說明

    若為表添加了一個CHECK (column_name > 0)約束,且表中存在不符合該約束的資料,則會報錯,無法進行添加操作。

  • 啟用狀態

    • 添加約束時,會掃描表中的全部存量資料,確保它們都符合約束規則。

      ALTER TABLE table_name ADD CONSTRAINT constraint_name;
      說明

      若為表添加了一個CHECK (column_name > 0)約束,且表中存在不符合該約束的資料,則會報錯,無法進行添加操作。

    • 添加約束時,僅會對後續的新資料進行校正,跳過對存量資料的校正。

      ALTER TABLE table_name ADD CONSTRAINT constraint_name NOT VALID;
      說明

      若為表添加了一個CHECK (column_name > 0)約束,且表中存在不符合該約束的資料,則不會報錯,約束添加成功。您可後續在業務低峰期掃描並驗證存量資料。

啟用約束

  • (預設)啟用約束時,不僅會對後續的新資料進行校正,還會掃描表中的全部存量資料,確保它們都符合約束規則。

    ALTER TABLE table_name ENABLE CONSTRAINT constraint_name;
  • 啟用約束時,僅對後續的新資料進行校正,跳過對存量資料的校正。

    ALTER TABLE table_name ENABLE CONSTRAINT constraint_name NOT VALID;

禁用約束

ALTER TABLE table_name DISABLE CONSTRAINT constraint_name;

刪除約束

ALTER TABLE table_name DROP CONSTRAINT constraint_name;

操作建議

不同的約束操作會施加不同層級的鎖,對並發事務的影響也不同。理解這些差異是安全使用此功能的關鍵。

操作

鎖層級

讀取影響

寫入影響

DDL影響

全表掃描

風險與建議

ADD CONSTRAINT ...

(添加啟用狀態約束並立即驗證存量資料)

ACCESS EXCLUSIVE

高(阻塞所有讀操作)

高 (阻塞所有寫操作)

高(阻塞其他DDL)

  • 風險:對大表執行此操作會鎖定全表很長時間,導致服務中斷。

  • 建議:不推薦在生產環境的大表上直接使用。應採用兩階段添加法

ADD CONSTRAINT ... NOT VALID

(添加啟用狀態約束但不驗證存量資料)

ACCESS EXCLUSIVE

(短暫)

低(僅中繼資料修改時短暫阻塞)

低(僅中繼資料修改時短暫阻塞)

低(短暫阻塞)

  • 風險:存量資料可能不符合約束。

  • 建議:推薦用於大表添加約束的第一步。操作快,對業務幾乎無影響。後續在業務低峰期掃描並驗證存量資料。

ADD CONSTRAINT ... DISABLE

(添加禁用狀態約束並立即驗證存量資料)

SHARE UPDATE EXCLUSIVE

無 (不阻塞SELECT)

高(阻塞INSERT/UPDATE/DELETE

高(阻塞其他DDL)

  • 風險:

    • 操作開銷大:會執行全表掃描並長時間阻塞寫入,對業務有影響。

    • 行為易誤解:雖然驗證了存量資料,但約束並未啟用,後續寫入的新資料不會被校正,可能立即產生髒資料。

  • 建議:這是一個非常特殊的操作。可用於一次性驗證表資料是否滿足某規則,但又不想立即強制執行該規則的情境。因其阻塞寫入,建議在業務低峰期執行。

ENABLE CONSTRAINT

(啟用約束並驗證存量資料)

SHARE UPDATE EXCLUSIVE

無 (不阻塞SELECT)

高(阻塞INSERT/UPDATE/DELETE

高(阻塞其他DDL)

  • 風險:若存在不合規的存量資料,操作會失敗。全表掃描消耗資源並阻塞寫入。

  • 建議:在業務低峰期執行。啟用前最好先手動檢查資料。

ENABLE CONSTRAINT ... NOT VALID

(啟用約束但不驗證存量資料)

ACCESS EXCLUSIVE

(短暫)

  • 風險:存量資料的不一致性被忽略,只有新資料和修改的資料會被校正。

  • 建議:適用於想立即對新資料強制限制式,但暫時無法或不需要清理歷史資料的情境。這等同於Oracle的ENABLE NOVALIDATE

DISABLE CONSTRAINT

(禁用約束)

ACCESS EXCLUSIVE

(短暫)

  • 風險:資料完整性風險,禁用期間可寫入不合規的髒資料。

  • 建議:僅用於可控的臨時情境(如批量資料載入)。操作後應儘快重新啟用約束。

DROP CONSTRAINT

(刪除約束)

ACCESS EXCLUSIVE

(短暫)

  • 風險:永久性操作,約束定義將丟失。刪除主鍵(PRIMARY KEY)或唯一約束(UNIQUE)會連帶刪除其索引。

  • 建議:謹慎使用。確認不再需要該商務規則。如需臨時取消檢查,應使用DISABLE

為大表安全添加約束

直接為大表添加約束(ADD CONSTRAINT)會立即觸發全表資料校正,並持有ACCESS EXCLUSIVE鎖,長時間阻塞所有讀寫操作,對生產環境業務造成嚴重影響。推薦採用以下兩階段添加法,將影響降至最低。

樣本情境

為一張包含數億行資料的products表添加一個CHECK約束,確保price欄位大於0。

流程概述

  1. 添加啟用狀態的約束,但不驗證存量資料:快速添加約束定義,不校正存量資料,鎖時間短。

  2. 後台驗證存量資料:在業務低峰期掃描並驗證存量資料,確保表中欄位滿足約束。此過程允許並發讀取,對業務影響最小。

操作步驟

  1. 快速添加一個處於啟用狀態的約束定義。使用ADD CONSTRAINT ... NOT VALID文法,此操作僅修改中繼資料,不校正存量資料,操作時間短。

    -- 建立測試表和資料
    CREATE TABLE products (
        id SERIAL PRIMARY KEY,
        name TEXT,
        price NUMERIC
    );
    
    -- 插入一條不合規的資料
    INSERT INTO products (name, price) VALUES ('Bag', -1); 
    
    -- 插入一條合規的資料
    INSERT INTO products (name, price) VALUES ('Book', 10); 
    
    -- 添加開啟狀態的約束,不校正存量資料
    ALTER TABLE products ADD CONSTRAINT chk_price_positive CHECK (price > 0) NOT VALID;
  2. (可選)驗證約束對新寫入和更新的資料是否生效。

    -- 新插入的非法資料會被阻止
    INSERT INTO products (name, price) VALUES ('Pen', -5);
    -- 報錯:
    -- ERROR: new row for relation "products" violates check constraint "chk_price_positive"
    -- DETAIL: Failing row contains (3, Pen, -5).
    
    -- 更新的非法資料會被阻止
    UPDATE products SET price = -2 WHERE id = 1;
    -- 報錯:
    -- ERROR:  new row for relation "products" violates check constraint "chk_price_positive"
    -- DETAIL:  Failing row contains (1, Bag, -2).
  3. 驗證歷史資料是否滿足約束。使用SELECT查詢不合規的存量資料,並使用UPDATE修複存量資料。

    -- 尋找不合規的歷史資料
    SELECT * FROM products WHERE NOT (price > 0);
    
    -- 修複不合規的歷史資料
    UPDATE products SET price = 10 WHERE id = 1;

最佳化大批量資料匯入效能

在進行大批量資料匯入(如COPY/INSERT)時,啟用的約束(尤其是主鍵、唯一鍵和外鍵)會對每一行資料進行檢查,嚴重拖慢匯入速度。通過在匯入前後禁用和啟用約束,可以大幅提升效能。

樣本情境

將大量資料從CSV檔案匯入到target_table

操作步驟

  1. 在資料匯入前,臨時禁用目標表的所有約束。

    -- 假設target_table已有唯一鍵、外鍵和檢查約束
    ALTER TABLE target_table DISABLE CONSTRAINT uq_target;
    ALTER TABLE target_table DISABLE CONSTRAINT fk_target;
    ALTER TABLE target_table DISABLE CONSTRAINT chk_target;
    說明

    通常不建議也無法直接禁用主鍵,因為主鍵索引對資料群組織至關重要。主要禁用的是唯一約束(UNIQUE)、外鍵(FOREIGN KEY)和檢查(CHECK)約束。

  2. 執行資料匯入。由於約束被禁用,資料庫不會進行檢查,匯入速度會顯著加快。

    COPY target_table FROM '/path/to/data.csv' WITH CSV;
  3. 在資料匯入後,重新啟用約束並驗證所有資料。

    1. 不驗證匯入資料。

      說明

      ENABLE CONSTRAINT ... NOT VALID存量資料的不一致性被忽略,只有新資料和修改的資料會被校正。建議在啟用前最好先手動檢查資料。

      -- 啟用唯一約束
      ALTER TABLE target_table ENABLE CONSTRAINT uq_target NOT VALID;
      
      -- 啟用外鍵約束
      ALTER TABLE target_table ENABLE CONSTRAINT fk_target NOT VALID;
      
      -- 啟用CKECK約束
      ALTER TABLE target_table ENABLE CONSTRAINT chk_target NOT VALID;
    2. 驗證匯入資料。

      說明

      ENABLE CONSTRAINT會全表掃描消耗資源並阻塞寫入。建議在業務低峰期執行,且啟用前最好先手動檢查資料。

      -- 啟用唯一約束,這將觸發對全表資料的校正
      ALTER TABLE target_table ENABLE CONSTRAINT uq_target;
      -- 如果校正失敗,會報錯:ERROR: could not enable unique constraint "uq_target"
      
      -- 啟用外鍵約束,這將觸發對全表資料的校正
      ALTER TABLE target_table ENABLE CONSTRAINT fk_target;
      -- 如果校正失敗,會報錯:ERROR: insert or update on table "target_table" violates foreign key constraint "fk_target"
      
      -- 啟用CKECK約束,這將觸發對全表資料的校正
      ALTER TABLE target_table ENABLE CONSTRAINT chk_target;
      -- 如果校正失敗,會報錯:ERROR: check constraint "chk_target" is violated by some row

常見問題

執行ENABLE CONSTRAIN 時,提示ERROR: check constraint "..." is violated by some row,應如何處理?

這表示表中存在不滿足該約束條件的現有資料。您需要先找出並修複這些資料。 樣本

對於約束CHECK (price > 0)

  1. 尋找不合規的資料。

     SELECT * FROM your_table WHERE NOT (price > 0);
  2. 修複或刪除這些資料。

    UPDATE your_table SET price = ... WHERE ...;
    DELETE FROM your_table WHERE NOT (price > 0);
  3. 再次嘗試啟用約束。

    ALTER TABLE your_table ENABLE CONSTRAINT your_constraint_name;

啟用唯一約束時,提示ERROR: could not enable unique constraint "...",是什麼原因?

這表示用於強制唯一性的列中存在重複值。

樣本

  1. 尋找重複值。

    SELECT column_list, COUNT(*) FROM your_table GROUP BY column_list HAVING COUNT(*) > 1;
  2. 根據業務需求處理重複資料。例如,重複資料刪除記錄,僅保留一條。

    DELETE FROM your_table WHERE id NOT IN (SELECT MIN(id) FROM your_table GROUP BY column_list);
  3. 再次嘗試啟用約束。

    ALTER TABLE your_table ENABLE CONSTRAINT your_unique_name;