首批通过分布式安全可靠测评,为关键业务系统打造
关联更新如何选择 update 还是 merge into
更新时间:2026-05-28 02:06
本文讨论关联更新,需要选择 Update 还是 Merge into 语法以及他俩的优劣。
适用版本
OceanBase 数据库所有版本。
详细内容
示例表结构以及数据。
create table test1 (a int,b varchar(100));
create table test2 (a int,b varchar(100));
insert into test1 values(1,'张三');
insert into test1 values(2,'李四');
insert into test2 values(1,'张三_NEW');
此时如果需要关联 test2 表更新 test1 表中 a 字段重复的行,可以采用关联 update 和 merge into 的两种写法。
例如:
update test1 t1 set t1.b=(select b from test2 where t1.a=t2.a)
where exists (select 1 from test1 t3 where t3.a=t1.a);
或者:
merge into test1 t1 using test2 t2
on (t1.a=t2.a)
when matched then
update set t1.b=t2.b;
虽然语义完全一样但是 update 的语法一次只更新一行记录,所以只能使用嵌套循环的方式进行更新。如果遇到关联的表数据量过多或者没有有效索引导致 SQL 不适合走嵌套循环的场景,那么使用 update 进行关联更新就不是一个很好的方案,此时建议使用 merge into 实现对两表使用 hash join 或者 merge join 的方式一次性更新表中的多行数据。
查看关联 update 的执行计划,其中 SUBPLAN FILTER 表示使用嵌套循环的方式对 T1 表进行更新。
Query Plan: ==========================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
------------------------------------------
|0 |UPDATE | |1 |139 |
|1 | SUBPLAN FILTER | |1 |138 |
|2 | MERGE SEMI JOIN| |1 |93 |
|3 | SORT | |2 |47 |
|4 | TABLE SCAN |T1 |2 |46 |
|5 | SORT | |1 |46 |
|6 | TABLE SCAN |T3 |1 |46 |
|7 | TABLE SCAN |T2 |1 |46 |
==========================================
Outputs & filters:
-------------------------------------
0 - output(nil), filter(nil), table_columns([{TEST1: ({TEST1: (T1.__pk_increment, T1.A, T1.B)})}]),
update([T1.B=column_conv(VARCHAR2,utf8mb4_bin,length:100,NULL,subquery(1))])
1 - output([T1.__pk_increment], [T1.A], [T1.B], [column_conv(VARCHAR2,utf8mb4_bin,length:100,NULL,subquery(1))]), filter(nil),
exec_params_([T1.A]), onetime_exprs_(nil), init_plan_idxs_(nil)
2 - output([T1.__pk_increment], [T1.A], [T1.B]), filter(nil),
equal_conds([T3.A = T1.A]), other_conds(nil)
3 - output([T1.__pk_increment], [T1.A], [T1.B]), filter(nil), sort_keys([T1.A, ASC])
4 - output([T1.B], [T1.A], [T1.__pk_increment]), filter(nil),
access([T1.B], [T1.A], [T1.__pk_increment]), partitions(p0)
5 - output([T3.A]), filter(nil), sort_keys([T3.A, ASC])
6 - output([T3.A]), filter(nil),
access([T3.A]), partitions(p0)
7 - output([T2.B]), filter([? = T2.A]),
access([T2.A], [T2.B]), partitions(p0)
查看 merge into 的执行计划可以看到 T1 表和 T2 表采用了 merge join 的关联方式。
obclient [SYS]> explain merge into test1 t1 using test2 t2 on (t1.a=t2.a) when matched then update set t1.b=t2.b\G
*************************** 1. row ***************************
Query Plan: ================================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
------------------------------------------------
|0 |MERGE | |1 |93 |
|1 | MERGE RIGHT OUTER JOIN| |1 |93 |
|2 | SORT | |2 |47 |
|3 | TABLE SCAN |T1 |2 |46 |
|4 | SORT | |1 |46 |
|5 | TABLE SCAN |T2 |1 |46 |
================================================
Outputs & filters:
-------------------------------------
0 - output(nil), filter(nil),
columns([{TEST1: ({TEST1: (T1.__pk_increment, T1.A, T1.B)})}]), partitions(p0),
update([T1.B=column_conv(VARCHAR2,utf8mb4_bin,length:100,NULL,T2.B)]),
insert_conds(nil), update_conds(nil), delete_conds(nil)
1 - output([T2.B], [T1.__pk_increment], [T1.A], [T1.B]), filter(nil),
equal_conds([T1.A = T2.A]), other_conds(nil)
2 - output([T1.__pk_increment], [T1.A], [T1.B]), filter(nil), sort_keys([T1.A, ASC])
3 - output([T1.__pk_increment], [T1.A], [T1.B]), filter(nil),
access([T1.__pk_increment], [T1.A], [T1.B]), partitions(p0)
4 - output([T2.B], [T2.A]), filter(nil), sort_keys([T2.A, ASC])
5 - output([T2.A], [T2.B]), filter(nil),
access([T2.A], [T2.B]), partitions(p0)
使用 hint 让 merge into 的语句走 hash join 的关联方式,语句和执行计划如下。
obclient [SYS]> explain merge /*+use_hash(t2 t1)*/ into test1 t1 using test2 t2 on (t1.a=t2.a) when matched then update set t1.b=t2.b\G
*************************** 1. row ***************************
Query Plan: =========================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
-----------------------------------------
|0 |MERGE | |1 |93 |
|1 | HASH OUTER JOIN| |1 |93 |
|2 | TABLE SCAN |T2 |1 |46 |
|3 | TABLE SCAN |T1 |2 |46 |
=========================================
Outputs & filters:
-------------------------------------
0 - output(nil), filter(nil),
columns([{TEST1: ({TEST1: (T1.__pk_increment, T1.A, T1.B)})}]), partitions(p0),
update([T1.B=column_conv(VARCHAR2,utf8mb4_bin,length:100,NULL,T2.B)]),
insert_conds(nil), update_conds(nil), delete_conds(nil)
1 - output([T2.B], [T1.__pk_increment], [T1.A], [T1.B]), filter(nil),
equal_conds([T1.A = T2.A]), other_conds(nil)
2 - output([T2.A], [T2.B]), filter(nil),
access([T2.A], [T2.B]), partitions(p0)
3 - output([T1.__pk_increment], [T1.A], [T1.B]), filter(nil),
access([T1.__pk_increment], [T1.A], [T1.B]), partitions(p0)
但是 merge into 的语句也不是在所有场景都要优于关联 update。我们知道 merge into 的完整语法为:
merge into target表 using (select ... from source表) on(关联条件)
when matched then update
when not matched then insert
当 matched 和 not matched 同时存在的时候,是只能通过 source 表来外连接 target 表的这种执行方式,如果只有 matched 存在,not matched 不存在的场景,在 Oracle 是可以优化成内连接的,但是 OceanBase 数据库目前对 merge into 语句即使不存在 not matched 的语句时也必须是外连接,这导致了 OceanBase 数据库没有办法走 target 表作为驱动表嵌套循环 source 表的执行计划。
例如:对应的数据分布为 TEST1 表数据量非常小甚至为空,而 TEST2 表非常大,我们希望让 TEST1 表嵌套循环 TEST2 表,可以将 merge into 改写成 update 语句或者变通的修改 merge into 语句将原 source 表在 using 内部先和 target 表 join 一下让新 source 表的结果集变小。
merge into test1 t1 using (select temp1.* from test2 temp1 join test1 temp2 on temp1.a=temp2.a)t2
on (t1.a=t2.a)
when matched then
update set t1.b=t2.b;