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

Oracle MERGE 語句實戰:單條 SQL 實現 UPSERT (存在更新、不存在插入)

     編輯:Oracle 資料庫基礎

前言

傳統「先查後插/更新」模式需兩次數據庫往返,且存在競態條件。Oracle 9i+ 引入 MERGE 語句,單條 SQL 原子化實現 UPSERT。本文詳解語法、實戰範例、性能優化、並發陷阱與最佳實踐。

一、MERGE 語法結構

MERGE INTO target_table t
USING source_data s
ON (join_condition)
WHEN MATCHED THEN
    UPDATE SET col1 = expr1, col2 = expr2 ...
    [WHERE update_condition]
WHEN NOT MATCHED THEN
    INSERT (col1, col2, ...) VALUES (expr1, expr2, ...)
    [WHERE insert_condition];

關鍵點

  • USING 來源:表、視圖、子查詢、雙表連接
  • ON 條件:匹配邏輯,決定走 UPDATE 還是 INSERT
  • WHEN MATCHED:可加 WHERE 進一步過濾
  • WHEN NOT MATCHED:可加 WHERE 條件插入
  • UPDATE/INSERT 子句不寫表別名 (與獨立 DML 不同)

二、基礎實戰範例

1. 測試表準備

CREATE TABLE test_merge (
    id   NUMBER PRIMARY KEY,
    name VARCHAR2(30) NOT NULL,
    sex  VARCHAR2(2) DEFAULT '男'
);

INSERT INTO test_merge VALUES (1, 'SUNZHENXING', '男');
INSERT INTO test_merge VALUES (2, 'SUNHAILONG', '女');
COMMIT;

2. MERGE 實現:按 name 判斷,存在更新 sex,不存在插入

MERGE INTO test_merge t
USING (SELECT 3 AS id, 'SUNZHENXING' AS name, '女' AS sex FROM DUAL) s
ON (t.name = s.name)
WHEN MATCHED THEN
    UPDATE SET t.sex = s.sex WHERE t.name = 'SUNZHENXING'
WHEN NOT MATCHED THEN
    INSERT (id, name, sex) VALUES (s.id, s.name, s.sex);

3. 執行結果

場景執行前執行後說明
name='SUNZHENXING' 存在id=1, sex='男'id=1, sex='女'走 UPDATE 分支
name='NEW_USER' 不存在無id=3, name='NEW_USER', sex='女'走 INSERT 分支

三、進階用法

1. 來源為子查詢 (批量 UPSERT)

MERGE INTO target_table t
USING (
    SELECT id, name, status, updated_at
    FROM staging_table
    WHERE batch_id = :current_batch
) s
ON (t.id = s.id)
WHEN MATCHED THEN
    UPDATE SET t.name = s.name, t.status = s.status, t.updated_at = s.updated_at
WHEN NOT MATCHED THEN
    INSERT (id, name, status, created_at, updated_at)
    VALUES (s.id, s.name, s.status, s.updated_at, s.updated_at);

2. 僅更新、不插入 (同步修改)

MERGE INTO target_table t
USING source_table s
ON (t.id = s.id)
WHEN MATCHED THEN
    UPDATE SET t.col1 = s.col1, t.col2 = s.col2;

3. 僅插入、不更新 (去重插入)

MERGE INTO target_table t
USING source_table s
ON (t.id = s.id)
WHEN NOT MATCHED THEN
    INSERT (id, col1, col2) VALUES (s.id, s.col1, s.col2);

4. DELETE 子句 (10g+):匹配且滿足條件則刪除

MERGE INTO target_table t
USING source_table s
ON (t.id = s.id)
WHEN MATCHED THEN
    UPDATE SET t.col1 = s.col1
    DELETE WHERE t.status = 'OBSOLETE'  -- 更新後再判斷是否刪除
WHEN NOT MATCHED THEN
    INSERT (id, col1) VALUES (s.id, s.col1);

四、性能優化關鍵

優化點建議原理
ON 條件列有索引目標表 ON 列創建索引避免全表掃描匹配
來源數據量控制單次 MERGE 建議 < 10 萬行大數據量分批處理
並行 DMLALTER SESSION ENABLE PARALLEL DML; MERGE /*+ PARALLEL(t,4) */ ...大表合併利用多 CPU
避免觸發器目標表盡量無行級觸發器逐行觸發器殺死 MERGE 性能
統計信息新鮮合併前收集目標/來源表統計優化器選擇最佳連接方式

五、並發與一致性陷阱

1. 競態條件

會話 A 查無記錄 → 準備 INSERT | 會話 B 查無記錄 → 準備 INSERT | 兩者均執行 INSERT → 唯一鍵衝突

2. MERGE 原子性保障

  • 單條 MERGE 在語句級別原子執行,無競態條件
  • 鎖機制:掃描目標表獲取行鎖 (RX) 或表鎖,阻塞並發衝突

3. 死鎖風險

  • 多會話 MERGE 同一目標表,不同來源數據順序不同 → 死鎖
  • 緩解:來源數據按主鍵排序、縮短事務、設置 LOCK TABLE ... IN EXCLUSIVE MODE NOWAIT 串行化

六、常見錯誤與調試

錯誤原因修正
ORA-00904: "T"."COL": invalid identifierUPDATE/INSERT 子句使用了表別名直接寫列名:UPDATE SET col = val
ORA-30926: unable to get a stable set of rows來源數據 ON 條件非唯一,匹配多行來源去重、加唯一約束、或用 ROW_NUMBER() 取第一行
ORA-01400: cannot insert NULLNOT NULL 列 INSERT 未賦值檢查 INSERT 列表完整性、DEFAULT 值
性能極差無索引、大表全掃描、觸發器加索引、分批、禁用觸發器

七、MERGE vs 傳統 PL/SQL 對比

維度傳統 SELECT + IF + INSERT/UPDATEMERGE
網絡往返2-3 次1 次
原子性需顯式鎖 (SELECT FOR UPDATE)語句級原子
代碼複雜度高 (PL/SQL 塊)低 (單條 SQL)
批量處理需 FORALL/BULK COLLECT原生集合操作
可讀性分散邏輯聲明式、意圖清晰

結語

MERGE 是 Oracle 實現 UPSERT 的標準利器。掌握 ON 匹配邏輯、WHEN MATCHED/NOT MATCHED 分支、DELETE 子句、並行 Hint、索引策略,並警惕 ORA-30926 來源非唯一、死鎖風險,即可在高併發、大數據量場景下構建高效、正確、可維護的數據同步邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved