前言
在日常的数据库操作中,我们经常会遇到这样的场景:"如果数据存在,就更新它;如果不存在,就插入一条新的"
。这种模式通常被称为 "Upsert"(Update + Insert)。在 MySQL 中,实现 Upsert 最优雅、最高效的方式之一就是使用 ON DUPLICATE KEY UPDATE
语法。
一、基本概念
1、什么是 ON DUPLICATE KEY UPDATE?
ON DUPLICATE KEY UPDATE是 MySQL 特有
的一种 INSERT 语句扩展,当执行 INSERT 操作时,如果插入的数据与表中已有数据的主键(PRIMARY KEY)或唯一索引(UNIQUE INDEX)发生冲突
(即要插入的值与已有记录的主键或唯一索引值相同),则不执行插入操作,而是转而执行 UPDATE 操作,更新已存在的记录。
2、工作原理
- 尝试插入:MySQL 首先尝试按照正常的 INSERT 语句插入新记录
- 检查冲突:在插入前,MySQL 会检查是否存在与待插入数据主键或唯一索引冲突的记录
- 冲突处理:
- 如果没有冲突:正常插入新记录
- 如果有冲突:不插入新记录,而是根据 ON DUPLICATE KEY UPDATE子句更新已存在的记录
3、基本语法
基本语法格式如下:
sql
INSERT INTO table_name (column1, column2, ..., columnN)
VALUES (value1, value2, ..., valueN)
ON DUPLICATE KEY UPDATE
column1 = value1,
column2 = value2,
...;
更常用的写法是使用 VALUES()
函数来引用原本打算插入的值:
sql
INSERT INTO table_name (column1, column2, ..., columnN)
VALUES (value1, value2, ..., valueN)
ON DUPLICATE KEY UPDATE
column1 = VALUES(column1),
column2 = VALUES(column2),
...;
⚠️
触发条件:只有当插入操作违反了 主键(PRIMARY KEY) 或 唯一索引(UNIQUE INDEX) 约束时,UPDATE 部分才会被执行
二、使用场景
1、计数器更新
- 最常见的应用场景是实现计数器功能,如文章浏览量、点赞数等
sql
INSERT INTO article_views (article_id, view_count)
VALUES (123, 1)
ON DUPLICATE KEY UPDATE
view_count = view_count + 1;
2、配置项更新
- 当需要更新或插入配置项时
sql
INSERT INTO system_config (config_key, config_value, last_updated)
VALUES ('site_title', 'My Website', NOW())
ON DUPLICATE KEY UPDATE
config_value = VALUES(config_value), last_updated = NOW();
3、购物车商品更新
- 添加商品到购物车,已存在则更新数量
sql
INSERT INTO shopping_cart (user_id, product_id, quantity)
VALUES (123, 456, 2)
ON DUPLICATE KEY UPDATE
quantity = quantity + VALUES(quantity),
added_at = CURRENT_TIMESTAMP;
⚠️
必须添加主键或唯一索引,否则ON DUPLICATE KEY UPDATE将不会触发,语句会正常执行插入操作(如果无其他错误)
三、高级用法
1、条件更新
- 在 ON DUPLICATE KEY UPDATE子句中使用
条件表达式
sql
-- 这个例子只在新的价格更低时才更新价格
INSERT INTO products (product_id, price, last_updated)
VALUES (101, 99.99, NOW())
ON DUPLICATE KEY UPDATE
price = IF(VALUES(price) < price, VALUES(price), price),
last_updated = NOW();
2、多表关联
- 虽然不能直接在 ON DUPLICATE KEY UPDATE中使用多表,但可以
结合子查询
sql
INSERT INTO user_stats (user_id, login_count)
SELECT 123, 1 FROM dual
WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = 123)
ON DUPLICATE KEY UPDATE
login_count = login_count + 1;
3、批量操作优化
- 对于大量数据的批量插入/更新,考虑以下优化
sql
INSERT INTO log_entries (user_id, action, timestamp)
VALUES
(1, 'login', NOW()),
(2, 'view', NOW()),
(3, 'purchase', NOW())
ON DUPLICATE KEY UPDATE
action = VALUES(action),
timestamp = VALUES(timestamp);
⚠️
当表有多个唯一约束时,任何唯一键冲突都会触发UPDATE
四、其他处理冲突的方案
1、REPLACE INTO
- 实际上是先DELETE再INSERT,主键会有变化
sql
REPLACE INTO users (email, name, login_count)
VALUES ('test@example.com', 'Test User', 1);
2、INSERT IGNORE
- 冲突时直接忽略,不更新
sql
INSERT IGNORE INTO users (email, name)
VALUES ('test@example.com', 'Test User');