首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle 外鍵約束深度解析:級聯刪除實戰與參照完整性策略

     編輯:Oracle 資料庫基礎

核心差異:Oracle vs SQL Server 級聯能力

關鍵點:**Oracle 僅支持級聯刪除 (ON DELETE CASCADE),不支持級聯更新 (ON UPDATE CASCADE)**。SQL Server 兩者均支持。若業務需主鍵值變更自動傳播至子表,Oracle 需通過觸發器或應用層邏輯實現。

級聯刪除語法標準範例

建表並定義級聯刪除

-- 父表
CREATE TABLE departments (
    dept_id     NUMBER(4) PRIMARY KEY,
    dept_name   VARCHAR2(30) NOT NULL
);

-- 子表:定義外鍵並啟用 ON DELETE CASCADE
CREATE TABLE employees (
    emp_id      NUMBER(6) PRIMARY KEY,
    emp_name    VARCHAR2(50) NOT NULL,
    dept_id     NUMBER(4),
    salary      NUMBER(8,2),
    CONSTRAINT fk_emp_dept
        FOREIGN KEY (dept_id)
        REFERENCES departments(dept_id)
        ON DELETE CASCADE  -- 核心:刪除部門時自動刪除該部門所有員工
);

多層級聯刪除鏈路 (實戰常見)

-- 三層級聯:訂單 → 訂單項 → 發貨明細
CREATE TABLE orders (
    order_id     NUMBER PRIMARY KEY,
    cust_id      NUMBER,
    order_date   DATE DEFAULT SYSDATE
);

CREATE TABLE order_items (
    item_id      NUMBER PRIMARY KEY,
    order_id     NUMBER,
    product_id   NUMBER,
    quantity     NUMBER,
    CONSTRAINT fk_item_order
        FOREIGN KEY (order_id)
        REFERENCES orders(order_id)
        ON DELETE CASCADE
);

CREATE TABLE shipments (
    ship_id      NUMBER PRIMARY KEY,
    item_id      NUMBER,
    ship_date    DATE,
    qty          NUMBER,
    CONSTRAINT fk_ship_item
        FOREIGN KEY (item_id)
        REFERENCES order_items(item_id)
        ON DELETE CASCADE
);
-- 刪除一筆訂單,關聯的所有項與發貨記錄自動級聯刪除

參照完整性約束四大動作模式 (Oracle 支持度)

動作模式觸發條件子表外鍵列行為Oracle 支持適用場景
RESTRICT (默認)父表主鍵被 UPDATE/DELETE阻止操作,拋出 ORA-02292✅ 支持嚴格保護主數據,防止誤刪
SET NULL父表主鍵被 UPDATE/DELETE子表外鍵列置為 NULL✅ 支持 (需外鍵列允許 NULL)保留子記錄但斷開關聯,如員工離職部門保留
SET DEFAULT父表主鍵被 UPDATE/DELETE子表外鍵列置為 DEFAULT 值✅ 支持 (需定義 DEFAULT)歸檔歸屬未知歸檔部門
CASCADE父表主鍵被 DELETE子表關聯行同步刪除✅ 僅支持 DELETE訂單-明細、部門-員工等強生命週期綁定
CASCADE (UPDATE)父表主鍵被 UPDATE子表外鍵值同步更新❌ **不支持**需觸發器/應用層實現

定義約束動作的完整語法

CREATE TABLE child_table (
    child_id   NUMBER PRIMARY KEY,
    parent_id  NUMBER,
    data_col   VARCHAR2(100),
    CONSTRAINT fk_child_parent
        FOREIGN KEY (parent_id)
        REFERENCES parent_table(parent_id)
        ON DELETE SET NULL  -- 或 CASCADE / SET DEFAULT
        -- ON UPDATE CASCADE  -- Oracle 報錯:ORA-02256
);

修改現有約束:必須「先刪後建」

Oracle **不支持** `ALTER TABLE ... MODIFY CONSTRAINT` 直接變更外鍵動作。標準流程:

-- 1. 查詢約束名
SELECT constraint_name, table_name, delete_rule
FROM user_constraints
WHERE constraint_type = 'R' AND table_name = 'CHILD_TABLE';

-- 2. 刪除舊約束
ALTER TABLE child_table DROP CONSTRAINT fk_child_parent;

-- 3. 重建新約束 (含所需動作)
ALTER TABLE child_table ADD CONSTRAINT fk_child_parent
    FOREIGN KEY (parent_id)
    REFERENCES parent_table(parent_id)
    ON DELETE CASCADE;  -- 新動作

性能與鎖機制深度解析 (生產環境必讀)

鎖升級與死鎖風險

  • 執行父表 `DELETE` 時,Oracle 會在子表上獲取 **TM 鎖 (DML Enqueue) 模式 4 (Share/Row Exclusive - SRX)**,阻止子表結構變更 (DDL) 但允許並發 DML。
  • 若子表外鍵列 **無索引**,Oracle 必須對子表全表掃描並施加 **TX 鎖 (行級排他鎖) 於所有掃描行**,極易導致:
    1. 大量鎖等待 (enq: TM - contention)
    2. 死鎖 (ORA-00060),特別是併發刪除不同父鍵但子表掃描重疊時
    3. 刪除性能災難 (全表掃描 + 鎖開銷)
  • 黃金法則:**所有外鍵列必須建立索引** (非唯一索引即可),使 Oracle 能精準定位子表行,僅鎖定受影響行,避免全表掃描鎖升級。

索引建議

-- 為外鍵列建立索引 (命名規範: IDX_表名_FK列名)
CREATE INDEX idx_emp_dept_id ON employees(dept_id);
CREATE INDEX idx_item_order_id ON order_items(order_id);
CREATE INDEX idx_ship_item_id ON shipments(item_id);

級聯刪除的替代方案與最佳實踐

場景 1:軟刪除 (業務常需保留歷史)

-- 父表增加狀態列
ALTER TABLE departments ADD (status VARCHAR2(1) DEFAULT 'A' CHECK (status IN ('A','D')));

-- 視圖過濾有效數據
CREATE VIEW v_active_departments AS
SELECT * FROM departments WHERE status = 'A';

-- 應用層邏輯刪除:UPDATE departments SET status='D' WHERE dept_id=:id;
-- 子表保持不變,報表/審計仍可關聯歷史部門

場景 2:歸檔表 + 分區交換 (大數據量級聯刪除優化)

-- 1. 建立歸檔表 (結構同源表)
CREATE TABLE orders_archive PARTITION BY RANGE (order_date) (
    PARTITION p_2023 VALUES LESS THAN (DATE '2024-01-01'),
    PARTITION p_2024 VALUES LESS THAN (DATE '2025-01-01')
) AS SELECT * FROM orders WHERE 1=0;

-- 2. 批量歸檔 (避免巨大事務)
DECLARE
    CURSOR c_old IS SELECT order_id FROM orders WHERE order_date < DATE '2024-01-01';
    TYPE id_arr IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    l_ids id_arr;
BEGIN
    OPEN c_old;
    LOOP
        FETCH c_old BULK COLLECT INTO l_ids LIMIT 10000;
        EXIT WHEN l_ids.COUNT = 0;
        FORALL i IN 1..l_ids.COUNT
            DELETE FROM orders WHERE order_id = l_ids(i);
        COMMIT; -- 分批提交
    END LOOP;
    CLOSE c_old;
END;
/
-- 3. 歸檔數據可通過 Partition Exchange Loading (PEL) 秒級移入歸檔表

場景 3:觸發器實現級聯更新 (彌補 Oracle 缺失)

CREATE OR REPLACE TRIGGER trg_dept_pk_cascade
AFTER UPDATE OF dept_id ON departments
FOR EACH ROW
BEGIN
    IF :OLD.dept_id != :NEW.dept_id THEN
        UPDATE employees SET dept_id = :NEW.dept_id WHERE dept_id = :OLD.dept_id;
        -- 可擴展至多層子表
    END IF;
END;
/
-- 注意:觸發器增加寫入延遲,需評估併發影響

避坑指南:常見錯誤與排查

  1. ORA-02291: integrity constraint violated - parent key not found → 插入子表時父鍵不存在,檢查父表數據或事務可見性。
  2. ORA-02292: integrity constraint violated - child record found → 刪除父表時存在子記錄且約束為 RESTRICT/SET NULL/SET DEFAULT (非 CASCADE),需先處理子記錄或改用 CASCADE。
  3. ORA-02256: number of referencing columns must match referenced columns → 外鍵列數/順序/類型與主鍵不匹配。
  4. ORA-02270: no matching unique or primary key for this column-list → 父表引用列無主鍵/唯一約束。
  5. 死鎖 (ORA-00060) 頻發 → 檢查子表外鍵列是否缺失索引,或應用邏輯導致交叉刪除 (會話 A 刪父 1 鎖子 100,會話 B 刪父 2 鎖子 100)。
  6. 級聯刪除遞歸深度過大導致 ORA-00036 (recursive SQL level too deep) → 罕見,超過 255 層遞歸,需拆分業務邏輯或用批量腳本替代。

總結

  • Oracle 外鍵級聯**僅支持刪除**,不支持更新,業務若需主鍵變更傳播必須用觸發器/應用層。
  • **外鍵列必建索引**,否則級聯刪除將引發全表掃描、鎖升級、死鎖、性能雪崩。
  • 修改約束動作需 **DROP + CREATE**,不可直接 ALTER。
  • 大數據量級聯刪除優先考慮 **分區交換/批量腳本/軟刪除**,避免單一巨大事務阻塞系統。
  • 根據業務語義選擇 RESTRICT/SET NULL/CASCADE:核心主數據用 RESTRICT,弱關聯用 SET NULL,強生命週期綁定用 CASCADE。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved