尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

达梦与MySQL的MERGE INTO语法对比及迁移改写实战

发布时间:2026/9/13 22:19:31

资讯中心
01
ARTICLE

达梦与MySQL的MERGE INTO语法对比及迁移改写实战

达梦与MySQL的MERGE INTO语法对比及迁移改写实战
最近在做达梦数据库和MySQL的数据迁移时被MERGE INTO这个语法卡了很久。以前写Oracle写习惯了一到MySQL里发现这玩意居然行不通两个库的 SQL 方言差异确实坑人。这篇文章就把MERGE INTO在达梦数据库和 MySQL 里的用法彻底讲清楚包括达梦怎么写、MySQL 用什么替代、从达梦往 MySQL 迁移时怎么改写、以及我在实际业务里踩过的那些坑。不管是后端开发还是 DBA只要你在跟这两种数据库打交道这篇都值得花几分钟看完。1. 先说清楚MERGE INTO 到底是干什么的1.1 从“有则更新无则插入”的场景说起业务里最经典的一类需求就是增量同步比如从消息队列消费一批用户数据目标表里已经有这个用户就更新资料目标表里还没有这个用户就插入一条新记录。如果不用MERGE INTO很多人第一反应是先SELECT判断是否存在再决定UPDATE还是INSERT。这样做有个致命问题判断和写入之间隔了一个时间窗口遇到并发请求时两个事务可能同时判断“不存在”然后都执行插入直接主键冲突或者产生脏数据。MERGE INTO就是 SQL 标准里专门解决这类问题的语法一条语句完成“匹配则更新、不匹配则插入”判断和写入在数据库内部原子完成。这种合并操作在数据仓库拉链表、日终对账、订单状态同步、用户画像更新、历史数据补偿等场景里都非常常见几乎可以算是日常业务开发的必会语法。1.2 标准 MERGE INTO 语法长什么样标准写法长这样MERGE INTO 目标表 t USING 源表/子查询 s ON (匹配条件) WHEN MATCHED THEN UPDATE SET 列 值 WHEN NOT MATCHED THEN INSERT (列1, 列2) VALUES (值1, 值2);拆开理解就是三部分往哪儿合并、拿什么来比、匹配上了做什么。USING后面可以跟物理表、临时表也可以直接跟一个子查询非常灵活。ON后面的匹配条件决定了“同一行”怎么定义一般用的是主键或者业务唯一键。这里有一个很多新手容易犯迷糊的点MERGE INTO操作的是目标表USING里的数据只是“参照物”最终数据写入或者更新都只发生在目标表上。理解了这个后面看达梦和 MySQL 的语法差异时就不会绕晕。提示匹配条件里的列名建议两边都用别名限定比如t.id s.id。很多数据库在解析多表同名列时不加别名直接报“列名不明确”达梦对这类问题的检查尤其严格。2. 达梦数据库原生支持语法最接近 Oracle2.1 达梦的 MERGE INTO 基础用法达梦数据库的 SQL 方言和 Oracle 非常接近MERGE INTO是原生支持的写法和 Oracle 几乎一模一样。我在达梦 8 上实测过下面这段 SQL 可以直接跑MERGE INTO user_info t USING ( SELECT 1001 AS user_id, 张三 AS user_name, 25 AS age FROM DUAL UNION ALL SELECT 1002 AS user_id, 李四 AS user_name, 30 AS age FROM DUAL ) s ON (t.user_id s.user_id) WHEN MATCHED THEN UPDATE SET t.user_name s.user_name, t.age s.age WHEN NOT MATCHED THEN INSERT (user_id, user_name, age) VALUES (s.user_id, s.user_name, s.age);这段 SQL 的意思是把子查询里的两行数据和user_info表做匹配user_id已经存在的就更新姓名和年龄不存在的就插入。一条语句处理了两种状态不需要先查再写。达梦里需要注意子查询里的每一列必须起别名而且FROM DUAL这种 Oracle 风格是支持的。如果是从 MySQL 切过来的开发可能会下意识写SELECT 1 AS id这种不带FROM的写法在达梦里会直接报语法错误。2.2 进阶场景WHEN MATCHED 里加 DELETE达梦的MERGE INTO还支持在匹配成功时进行删除操作这个功能在实际做数据订正时非常有用。比如有一张订单状态表以源表为基准做全量对齐源表里已经没有的订单目标表里也需要删掉MERGE INTO order_status t USING temp_order_sync s ON (t.order_id s.order_id) WHEN MATCHED THEN UPDATE SET t.status s.status, t.update_time SYSDATE DELETE WHERE t.status CANCELLED WHEN NOT MATCHED THEN INSERT (order_id, status, update_time) VALUES (s.order_id, s.status, SYSDATE);注意这个DELETE WHERE不是独立存在的它必须跟在UPDATE SET后面表示“匹配成功并且满足这个附加条件时执行删除”。这个写法在 Oracle 和达梦里语义一致但刚接触的人很容易把它理解成“不匹配时删除”这是完全错的。它的实际逻辑是先更新这一行如果更新后满足DELETE WHERE条件就把这行删掉。这种语法在日终对账、外部数据覆盖同步、临时表刷正式表等场景里非常省事一条语句就把“增、改、删”全做了。2.3 达梦里必须注意的几个规则第一目标表别名基本是强制要求。我在达梦 8 上测试目标表不加别名或者UPDATE SET里直接写列名不带表别名大概率会报错提示信息还比较隐晦容易让新手误以为语法写错了。第二ON 条件里的列不允许出现在 UPDATE SET 中。比如ON (t.user_id s.user_id)那么UPDATE SET t.user_id s.user_id这种写法会直接报错。原因是合并条件一旦允许被修改匹配关系就在语句执行过程中变化了数据库层面禁止这种不可控的操作。第三源表数据不能匹配到目标表的重复行。如果USING子查询里出现了两条相同user_id的记录而目标表里也有对应记录达梦会报“多次匹配”相关的错误。所以在USING里做好去重或者用子查询加聚合是一个必须养成的好习惯。3. MySQL官方不支持 MERGE替代方案实测3.1 为什么 MySQL 到今天还没有 MERGE INTO很多从 Oracle、达梦切到 MySQL 的人都会问同样的问题为什么 MySQL 不支持MERGE INTO这个问题没有特别官方的解释但从 MySQL 的发展路径看它一直走的都是“专用语法解决专用问题”的路子INSERT ... ON DUPLICATE KEY UPDATE就是专门为“有则更新、无则插入”设计的官方认为有这个就够了。坏消息是从 MySQL 5.7、8.0 一直到现在的 8.4、9.x 版本标准MERGE INTO语法一直没有被引入。所以在 MySQL 里直接执行MERGE INTO只会得到ERROR 1064 (42000)语法错误。做跨库迁移的人必须把这段 SQL 改写掉。3.2 方案一INSERT ... ON DUPLICATE KEY UPDATE这是 MySQL 里最接近MERGE INTO的替代方案也是我日常用得最多的写法INSERT INTO user_info (user_id, user_name, age) VALUES (1001, 张三, 25) ON DUPLICATE KEY UPDATE user_name VALUES(user_name), age VALUES(age);它的执行逻辑是尝试插入一行如果插入时触发了主键或者唯一键冲突就改为执行ON DUPLICATE KEY UPDATE后面的更新语句。这里有几个非常关键的细节第一必须存在主键或唯一键。MySQL 靠“唯一性冲突”来识别“是否匹配”如果你的表连一个唯一键都没有这条语句就永远只会插入永远不会更新。第二VALUES()函数在新版本里已经不建议使用了。MySQL 8.0.20 开始官方推荐用别名方式INSERT INTO user_info (user_id, user_name, age) VALUES (1001, 张三, 25) AS new ON DUPLICATE KEY UPDATE user_name new.user_name, age new.age;我用 8.0.32 实测过两种写法都能用但VALUES()会打 deprecation 警告长期维护的项目建议尽早切到新写法。第三它和达梦 MERGE 在语义上最大的区别是MySQL 无法在冲突时执行 DELETE。如果你需要在同步时把已取消的数据删掉ON DUPLICATE KEY UPDATE是做不到的必须另写一条DELETE。3.3 方案二REPLACE INTO看着方便但副作用大MySQL 里另一个经常被拿出来对比的语法是REPLACE INTOREPLACE INTO user_info (user_id, user_name, age) VALUES (1001, 张三, 25);它的逻辑是插入时如果遇到主键或唯一键冲突先删除旧记录再插入新记录。听起来很省事但它有几个非常严重的副作用删除旧记录后插入新记录意味着自增主键会变化如果这张表被其他表外键引用或者有缓存依赖主键 ID会出大问题。删除和插入操作会触发对应的DELETE触发器和INSERT触发器而不是UPDATE触发器业务逻辑容易悄悄走偏。每次替换都相当于先删再加日志量和索引维护成本明显高于真正的更新。LAST_INSERT_ID()之类的会话状态会被重置影响后续逻辑。所以REPLACE INTO我只建议在两类场景下使用一是表本身没有外键关联二是你明确需要“重建”整行数据而不是“更新部分列”。除此之外一律优先考虑ON DUPLICATE KEY UPDATE。3.4 方案三UPDATE JOIN 事务组合还有一种场景是源数据不是单条插入而是一张临时表或者子查询要跟目标表做批量匹配更新。这时可以用 MySQL 的UPDATE ... JOIN语法UPDATE user_info t JOIN temp_user_sync s ON t.user_id s.user_id SET t.user_name s.user_name, t.age s.age;做完更新之后再补一条INSERT ... SELECT ... WHERE NOT EXISTS把没匹配上的数据插进去INSERT INTO user_info (user_id, user_name, age) SELECT s.user_id, s.user_name, s.age FROM temp_user_sync s WHERE NOT EXISTS ( SELECT 1 FROM user_info t WHERE t.user_id s.user_id );两条语句外面包一层事务也能实现类似MERGE INTO的效果。但要注意这种方案并不能完全避免并发问题两个事务同时执行NOT EXISTS判断时仍然可能插入重复数据。所以在并发要求高的场景下我建议把“防止重复”的能力交给主键或唯一键配合INSERT IGNORE或ON DUPLICATE KEY UPDATE做兜底。3.5 三种替代方案怎么选方案适用场景并发安全性能处理删除吗推荐指数INSERT ... ON DUPLICATE KEY UPDATE单条或批量 upsert高依赖唯一约束不能首选REPLACE INTO明确要重建整行、无外键依赖中先删后插窗口期风险算间接支持不建议默认使用UPDATE JOIN INSERT 组合临时表批量对齐、非高并发低存在插入窗口可以另外加 DELETE低并发场景可用4. 从达梦迁移到 MySQLSQL 改写实战4.1 一个实际场景增量同步用户余额表我在一个项目里遇到过这样的需求每天晚上从上游系统同步用户账户余额上游给的是一个临时表里面有用户 ID、账户余额、更新时间。目标表里已经有这个用户就更新余额没有就插入新账户。这个需求在达梦里写起来非常顺因为我只需要一条MERGE INTOMERGE INTO account_balance t USING tmp_account_sync s ON (t.user_id s.user_id) WHEN MATCHED THEN UPDATE SET t.balance s.balance, t.update_time s.sync_time WHEN NOT MATCHED THEN INSERT (user_id, balance, update_time) VALUES (s.user_id, s.balance, s.sync_time);数据量大概每天 20 万行在达梦上执行时间一般在 3 到 5 秒左右非常稳定。但项目后来要把这套系统迁移到 MySQL这条 SQL 就成了第一批被“点名”要改写的语句。4.2 达梦写法演进到 MySQL 写法我最终在 MySQL 里的改写方案是INSERT ... ON DUPLICATE KEY UPDATE的批量版。先把上游临时表建好数据灌进去然后执行INSERT INTO account_balance (user_id, balance, update_time) SELECT s.user_id, s.balance, s.sync_time FROM tmp_account_sync s ON DUPLICATE KEY UPDATE balance s.balance, update_time s.sync_time;注意这里有个很容易踩的坑ON DUPLICATE KEY UPDATE后面不能直接写s.balance而是要写balance s.balance因为UPDATE子句里引用的是源表的别名。有些新手会写成SET balance s.balance会直接语法报错。改成这条语句后我在相同数据量下测了一下MySQL 8.0 执行时间大概在 6 到 9 秒比达梦稍微慢一点但在可接受范围内。性能上主要的差异来自 MySQL 在冲突时需要额外做一次索引查找和行更新这是它的实现机制决定的。4.3 性能与并发注意事项不管在达梦还是 MySQLMERGE INTO或ON DUPLICATE KEY UPDATE这类语句的性能很大程度上依赖匹配字段有没有索引。在达梦里如果ON条件使用的是非索引列执行计划大概率会走全表扫描源表 20 万行、目标表 200 万行时性能会指数级下降。我见过有人把ON条件写成t.user_name s.user_name而user_name没有索引一次同步跑了十分钟还没结束。解决办法很简单给匹配列建索引或者直接改用主键匹配。MySQL 这边的ON DUPLICATE KEY UPDATE则完全依赖表的唯一键或主键如果没有唯一约束这个语句直接退化成普通插入冲突识别根本不会触发。所以 MySQL 下这类操作之前一定要先确认业务唯一键已经建好例如UNIQUE KEY uk_user_id (user_id)。并发展方面这类合并语句都是“先探测、后写入”的逻辑目标行被操作期间会加行锁。如果同一行数据被多个事务同时合并后面的事务要等前面的提交或回滚。所以要注意控制事务大小别把几十万行合并包在一个大事务里不然锁持有时间太长线上业务很容易出现锁等待超时。实操心得我一般会把大表的合并操作按主键分段比如每 5000 行一个批次分批提交。这样即使某一批失败重跑的成本也很低不会出现一个 60 万行的大事务把innodb_lock_wait_timeout直接打爆的情况。4.4 同步完成后别忘了做数据校验改写后的 SQL 也不是一劳永逸。我在实际迁移后发现ON DUPLICATE KEY UPDATE在 MySQL 里有一个非常容易忽略的行为如果插入时触发了唯一键冲突并且更新前后的值完全相同MySQL 返回的影响行数是 0而不是 2。这就导致很多人按影响行数去做统计发现对不上怀疑数据丢了。处理办法有两种一是别依赖影响行数做业务判断改为以目标表的update_time字段为准做核对二是在同步结束后跑一次两边的COUNT和SUM校验。我在这个项目的最终方案是每次同步后额外执行一条核对 SQLSELECT COUNT(*) FROM account_balance t LEFT JOIN tmp_account_sync s ON t.user_id s.user_id WHERE t.balance s.balance OR t.update_time s.sync_time;返回值如果为 0说明两边数据一致这一批同步才算真正成功。5. 常见问题排查与避坑记录5.1 达梦报错无效的 UPDATE 语句有朋友把 Oracle 的写法搬到达梦时遇到报错信息大致是“无效的 UPDATE 语句”。常见原因有三类目标表没写别名。UPDATE SET里使用了ON条件中的列。USING子查询里的列没有取别名。如果是第三类达梦给的报错信息不会直接提示“别名缺失”而是甩出一个看起来很莫名其妙的语法错误。排查思路是逐层简化先去掉WHEN子句只保留MERGE INTO ... USING ... ON确认能跑通后再加回更新部分这样能很快定位问题。5.2 MySQL 的 ON DUPLICATE KEY UPDATE 影响行数谜团这个坑我在上面提过但值得单独再说一次。MySQL 官方文档明确写INSERT ... ON DUPLICATE KEY UPDATE的影响行数有三种情况1新插入一行。2发生冲突原行被更新。0发生冲突但新旧值一样MySQL 省略了实际更新操作。所以如果你拿影响行数去判断“到底更新了几行”结果一定会偏小。我在项目里就因为这个数字对不上排查了半天最后查了官方文档才明白。建议所有用这个语法的同学先把这个语义告诉团队成员避免踩同一个坑。5.3 别把 Git merge 和数据库 MERGE 搞混搜索相关热词里出现了很多“git merge 撤回”“idea 如何回退 merge 操作”的内容这里顺便提一嘴它们虽然都叫 merge但完全不是一回事。Git 的 merge 是把一个分支的提交合并到另一个分支回退用git revert、git reset或者git merge --abort来做。数据库里的MERGE INTO是 DML 操作操作的是表数据没有“分支”“提交”这些概念。实际问题排查时如果看到报错里的 merge 相关关键词先分清是数据库服务报错还是代码版本管理工具报错别拿git merge --abort去回滚数据库那就闹大笑话了。5.4 达梦突然连不上和 MERGE 有关系吗很多人在排查“达梦数据库突然连不上”时第一反应是网络或者服务挂了其实有一种隐蔽的情况一个长时间的MERGE INTO大事务占满了 undo 或锁资源后续会话连接被阻塞表现就是“连不上”或者“执行 SQL 无响应”。我遇到过一次因为 MERGE 大事务导致无法连接的情况最终定位是事务里有大量更新没有提交最后通过杀掉阻塞会话解决的。建议日常在达梦里跑大事务合并时养成COMMIT随手写的习惯并且设置好会话超时时间避免空闲事务长期悬挂。5.5 从达梦迁移时SQL 改写要扩散检查老项目里除了MERGE INTO还有大量达梦兼容 Oracle 的语法比如SYSDATE、DUAL、NVL、字符串拼接用||等等。如果只是把MERGE INTO单点改掉后面跑起来还是会报错。建议迁移前先用一个简单的脚本把 SQL 文件里所有这类关键字扫一遍做好完整清单再动手改。我自己在迁移时整理过一个简单的关键词清单MERGE INTO→ 改写ON DUPLICATE KEY UPDATESYSDATE→ 改写NOW()或者CURRENT_TIMESTAMPNVL→ 改写IFNULL||字符串拼接 → 改写CONCAT外连接()→ 改写LEFT JOINROWNUM→ 改写LIMIT这个清单不长但每一条都能救人一命。当初我改完MERGE INTO之后紧接着就被SYSDATE和NVL连续绊了两跤最后痛定思痛才把清单补全。从我个人的实际体验来看MERGE INTO这种语法最大的价值不在于省几行代码而在于它提供了一种“声明式”的数据合并思路你把合并规则讲清楚数据库来保证原子性。达梦作为对 Oracle 兼容性很好的数据库在这方面用起来确实顺手MySQL 虽然语法上绕了一点但ON DUPLICATE KEY UPDATE加上合理的表结构设计也能撑起大部分同步场景。最后分享一个我日常写同步逻辑的小技巧拿到需求先问自己一句“源和目标都在哪个数据库里”如果是达梦我会直接考虑MERGE INTO如果是 MySQL我会先确认唯一键然后首选ON DUPLICATE KEY UPDATE。数据库方言不同解决问题的工具也不同把每个库的脾气摸清楚比背再多 SQL 模板都管用。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。