MySQL on duplicate key update

本文深入探讨MySQL中onDuplicateKeyUpdate的功能与实践,介绍如何在数据同步时智能处理insert与update操作,避免重复数据冲突,同时提供具体示例和测试结果,帮助读者掌握这一高效的数据管理技巧。

项目中需要临时做一些数据抽取转换同步,例如将A、B、C表的数据进行筛选关联,再加入表D中,那么一条数据初次同步时,可以直接insert,但是第二次同步时就应当选择update。我最开始很蠢的通过查询判断是否已存在再选择insert或者update的方式实现,在做完了之后才想起来mysql自带了类似功能的实现,就是on duplicate key update。

为加深映像,写了这篇文章记录下使用心得吧。

语法:

insert into

table_name( field1, field2,...fieldN )

values( value1, value2,...valueN )

on duplicate key update

field2=values(field2), ...,valueN=values(valueN );

其中on duplicate key update后面接的就是主键或者唯一索引发生冲突时要更新的字段,但是如果这些字段本身又触发了唯一索引重复,那么还是会报错。

下面是一些具体的使用情况:

创建测试表demo,语句如下:

CREATE TABLE "demo" (
  "id" bigint(20) NOT NULL,
  "a" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "b" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "c" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "d" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "e" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY ("id"),
  UNIQUE KEY "unique_a" ("a") USING BTREE,
  UNIQUE KEY "unique_b" ("b") USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

现有数据内容如下,后面每一步操作之前都会将数据重置为如下内容,其中字段id、a和b都要求唯一:

MariaDB [demo]> select * from demo;
+----+------+------+------+------+------+
| id | a    | b    | c    | d    | e    |
+----+------+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    | 1    |
|  2 | 2    | 2    | 2    | 2    | 2    |
|  3 | 3    | 3    | 3    | 3    | 3    |
|  4 | 4    | 4    | 4    | 4    | 4    |
|  5 | 5    | 5    | 5    | 5    | 5    |
|  6 | 6    | 6    | 6    | 6    | 6    |
+----+------+------+------+------+------+
6 rows in set (0.00 sec)

示例1:

insert into demo(id,a,b,c,d,e) 
values('1','7','7','7','7','7')
on duplicate key update
a=values(a), b=values(b), c=values(c), d=values(d), e=values(e);

返回结果如下:

Query OK, 2 rows affected (0.00 sec)
更新成功,注意受影响的行数是2。

示例2:

insert into demo(id,a,b,c,d,e) 
values('1','7','7','7','7','7'),('2','3','8','8','8','8'),('3','9','9','9','9','9')
on duplicate key update
a=values(a), b=values(b), c=values(c), d=values(d), e=values(e);

其中第二行的字段a会和第三行更新前的字段a重复,返回结果如下:

Duplicate entry '3' for key 'unique_a'

但是如果将插入顺序修改为如下形式,是可以更新成功的:

insert into demo(id,a,b,c,d,e) 
values('1','7','7','7','7','7'),('3','9','9','9','9','9'),('2','3','8','8','8','8')
on duplicate key update
a=values(a), b=values(b), c=values(c), d=values(d), e=values(e);

如果不想调整顺序,但希望MySQL能更新正确的部分内容,可添加ignore实现:

insert ignore into demo(id,a,b,c,d,e) 
values('1','7','7','7','7','7'),('3','9','9','9','9','9'),('2','3','8','8','8','8')
on duplicate key update
a=values(a), b=values(b), c=values(c), d=values(d), e=values(e);

示例3:

如果,存在多个字段对应不同的行都有冲突,那么更新哪一行呢?

insert into demo(id,a,b,c,d,e)
values('1','2','3','7','7','7') 
on duplicate key update
c=values(c),d=values(d),e=values(e);

其中id(主键)、a、b都有冲突,更新结果如下,只更新了第一行数据,通过主键id确定:

MariaDB [demo]> select * from demo;
+----+------+------+------+------+------+
| id | a    | b    | c    | d    | e    |
+----+------+------+------+------+------+
|  1 | 1    | 1    | 7    | 7    | 7    |
|  2 | 2    | 2    | 2    | 2    | 2    |
|  3 | 3    | 3    | 3    | 3    | 3    |
|  4 | 4    | 4    | 4    | 4    | 4    |
|  5 | 5    | 5    | 5    | 5    | 5    |
|  6 | 6    | 6    | 6    | 6    | 6    |
+----+------+------+------+------+------+
6 rows in set (0.00 sec)

 如果只有a、b字段冲突呢?

insert into demo(id,a,b,c,d,e)
values('7','2','3','7','7','7') 
on duplicate key update
id=values(id), c=values(c), d=values(d), e=values(e);

更新结果如下,只更新了第二行数据,通过字段a确定:

MariaDB [demo]> select * from demo;
+----+------+------+------+------+------+
| id | a    | b    | c    | d    | e    |
+----+------+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    | 1    |
|  3 | 3    | 3    | 3    | 3    | 3    |
|  4 | 4    | 4    | 4    | 4    | 4    |
|  5 | 5    | 5    | 5    | 5    | 5    |
|  6 | 6    | 6    | 6    | 6    | 6    |
|  7 | 2    | 2    | 7    | 7    | 7    |
+----+------+------+------+------+------+
6 rows in set (0.00 sec)

以上测试可以发现,当前id、a、b字段都冲突时,更新行以id为准,当a、b字段冲突时,更新行以a字段为准。

我猜测这个优先级是在创建表结构时,由唯一索引的先后顺序决定的,但需要额外注意的是,主键是否永远是最高优先级?

修改建表语句如下,颠倒索引顺序,数据不变内容:

CREATE TABLE "demo" (
  "id" bigint(20) NOT NULL,
  "a" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "b" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "c" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "d" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  "e" varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  UNIQUE KEY "unique_b" ("b") USING BTREE,
  UNIQUE KEY "unique_a" ("a") USING BTREE,
  PRIMARY KEY ("id")
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

执行如下语句:

insert into demo(id,a,b,c,d,e)
values('1','2','3','7','7','7') 
on duplicate key update
c=values(c),d=values(d),e=values(e);

更新结果如下,依然是更新第一行,通过主键id确定:

+----+------+------+------+------+------+
| id | a    | b    | c    | d    | e    |
+----+------+------+------+------+------+
|  1 | 1    | 1    | 7    | 7    | 7    |
|  2 | 2    | 2    | 2    | 2    | 2    |
|  3 | 3    | 3    | 3    | 3    | 3    |
|  4 | 4    | 4    | 4    | 4    | 4    |
|  5 | 5    | 5    | 5    | 5    | 5    |
|  6 | 6    | 6    | 6    | 6    | 6    |
+----+------+------+------+------+------+

仅有a、b冲突时:

insert into demo(id,a,b,c,d,e)
values('7','2','3','7','7','7') 
on duplicate key update
id=values(id), c=values(c), d=values(d), e=values(e);

结果如下,更新行变成了第三行,由字段b冲突导致:

+----+------+------+------+------+------+
| id | a    | b    | c    | d    | e    |
+----+------+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    | 1    |
|  2 | 2    | 2    | 2    | 2    | 2    |
|  4 | 4    | 4    | 4    | 4    | 4    |
|  5 | 5    | 5    | 5    | 5    | 5    |
|  6 | 6    | 6    | 6    | 6    | 6    |
|  7 | 3    | 3    | 7    | 7    | 7    |
+----+------+------+------+------+------+

结论:

如果插入语句中存在多个唯一性冲突,那么更新目标行优先通过主键指定,如果主键并未冲突,那么更新目标行通过唯一索引的先后顺序进行指定。

<think> 我们被要求解释MySQL中的ON DUPLICATE KEY UPDATE用法。根据引用[1][2][3][4],我们可以总结如下: 1. 功能:用于处理当插入的数据会导致唯一键(主键或唯一索引)冲突时,转而执行更新操作。 2. 语法:在INSERT语句后添加ON DUPLICATE KEY UPDATE子句,指定更新哪些列。 3. 注意事项: - 只适用于有唯一键或主键的表 - 使用VALUES()函数可以引用插入语句中本应插入的值 - 如果没有重复键,则正常插入;如果有,则更新 - 在批量插入时,ON DUPLICATE KEY UPDATE会对每一行可能冲突的记录进行更新 我们将详细解释并提供示例。 ### 一、基本语法 ```sql INSERT INTO table_name (col1, col2, ...) VALUES (val1, val2, ...) ON DUPLICATE KEY UPDATE col1 = new_val1, col2 = new_val2, ...; ``` ### 二、使用场景 当插入数据时,如果唯一键(主键或唯一索引)发生冲突,则执行UPDATE操作;否则执行INSERT操作。 ### 三、核心机制 1. **检测冲突**:根据主键或唯一索引判断是否重复 2. **更新操作**:使用UPDATE子句更新指定列 3. **引用原值**:通过`VALUES(列名)`获取原INSERT语句中的值 ### 四、示例说明 #### 示例1:基础更新 ```sql -- 假设id为主键 INSERT INTO users (id, name, age) VALUES (1, 'Alice', 25) ON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age); ``` - 若id=1不存在:插入新记录 - 若已存在:更新name和age为VALUES提供的值(即'Alice'和25) #### 示例2:表达式更新 ```sql INSERT INTO sales (product_id, quantity) VALUES (100, 5) ON DUPLICATE KEY UPDATE quantity = quantity + VALUES(quantity); -- 累加数量 ``` - 冲突时:将原有quantity增加5 ### 五、批量操作 ```sql INSERT INTO users (id, name) VALUES (1, 'Bob'), (2, 'Cindy'), (3, 'David') ON DUPLICATE KEY UPDATE name = VALUES(name); ``` - 对每一行独立判断:若某行冲突则更新该行,否则插入 ### 六、注意事项 1. **唯一键要求**:必须有主键或唯一索引(否则等同于普通INSERT) 2. **多唯一键冲突**:如果存在多个唯一键,只要有一个唯一键冲突就会触发更新(且按表定义顺序第一个冲突的键生效) 3. **VALUES()函数**:仅在UPDATE子句中有效,返回当前插入行的值 4. **影响行数**: - 插入成功:返回1 - 更新成功:返回2(删除旧行+插入新行计为2,但实际更新为1行) - 更新但数据无变化:返回0(如更新值与原值相同) ### 七、性能特点 1. 比先查询后更新效率更高(减少网络交互) 2. 比`REPLACE INTO`更优(`REPLACE`会先删除再插入,可能影响自增ID) ### 八、与标准SQL对比 MySQL不支持`MERGE INTO`语句,`ON DUPLICATE KEY UPDATE`是其特有实现,功能类似但语法更简洁。 §§相关问题§§ 1. ON DUPLICATE KEY UPDATE在批量插入时如何保证原子性? 2. 使用VALUES()函数有什么限制? 3. 多唯一键冲突时具体如何处理? 4. 如何获取ON DUPLICATE KEY UPDATE影响的行数? 5. 在InnoDB事务中,ON DUPLICATE KEY UPDATE会加什么类型的锁?[^1] [^1]: 引用自用户提供的参考内容 </think>### MySQL `ON DUPLICATE KEY UPDATE` 用法详解 #### 一、核心功能 当插入数据导致**唯一键(主键或唯一索引)冲突**时,自动转为执行更新操作。这是MySQL实现"插入或更新"(UPSERT)的高效方式[^1][^2]。 #### 二、基本语法 ```sql INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...) ON DUPLICATE KEY UPDATE 列1 = 新值1, 列2 = 新值2; ``` #### 三、使用场景 1. **主键冲突时更新** ```sql -- id为主键,冲突时更新name和age INSERT INTO users (id, name, age) VALUES (1, 'Alice', 25) ON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age); ``` 2. **唯一索引冲突处理** ```sql -- email字段有唯一索引 INSERT INTO customers (email, orders) VALUES ('alice@example.com', 1) ON DUPLICATE KEY UPDATE orders = orders + VALUES(orders); -- 订单数累加 ``` #### 四、核心函数:`VALUES()` 在UPDATE子句中通过`VALUES(列名)`引用**原INSERT语句中的值**[^3]: ```sql INSERT INTO products (id, stock) VALUES (101, 10) ON DUPLICATE KEY UPDATE stock = stock + VALUES(stock); -- 库存增加而非覆盖 ``` #### 五、批量操作 一次处理多行数据,每行独立判断冲突[^3]: ```sql INSERT INTO employees (id, salary) VALUES (101, 5000), (102, 6000), (103, 7000) ON DUPLICATE KEY UPDATE salary = VALUES(salary) * 1.1; -- 冲突时工资涨10% ``` #### 六、执行结果判断 通过返回值确定操作类型: - **0**:更新但数据无变化 - **1**:成功插入新行 - **2**:成功更新现有行 #### 七、注意事项 1. **必须存在唯一约束**(主键或唯一索引)[^2] 2. **多列唯一键冲突**:任一唯一键冲突都会触发更新 3. **自增ID处理**:更新时自增ID不会跳号(与`REPLACE INTO`不同) 4. **触发器触发**:同时触发`BEFORE INSERT`和`BEFORE UPDATE`触发器 #### 八、性能优势(vs 传统方式) | 操作方式 | 步骤数 | 网络请求 | 原子性 | |------------------------|--------|----------|--------| | `SELECT` + `INSERT/UPDATE` | 2-3 | 2-3 | ❌ | | `ON DUPLICATE KEY UPDATE` | 1 | 1 | ✔️ | #### 九、典型应用场景 1. **计数器累加** ```sql INSERT INTO page_views (page_id, views) VALUES (123, 1) ON DUPLICATE KEY UPDATE views = views + 1; ``` 2. **最后更新时间记录** ```sql INSERT INTO user_activity (user_id, last_active) VALUES (1001, NOW()) ON DUPLICATE KEY UPDATE last_active = NOW(); ``` 3. **数据同步** ```sql INSERT INTO target_table SELECT * FROM source_table ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2); ``` > 💡 **最佳实践**: > - 优先使用`VALUES()`函数确保更新一致性 > - 批量操作时每批建议不超过1000行 > - 监控`innodb_autoinc_lock_mode`配置对并发的影响
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值