📌 今日目标
掌握高级数据插入技巧,告别单条逐行 INSERT,学会高效批量操作与冲突处理。
一、为什么需要「插入技巧」?
场景 1:数据迁移 --- 将旧表数据导入新表,改写表结构或字段名
场景 2:ETL 导入 --- 从 CSV / API 批量写入数万行数据
场景 3:避免重复 --- 插入时检测主键/唯一键冲突,自动更新或忽略
只会
INSERT INTO ... VALUES (...)的 SQL 是写不坏业务的,但写不出效率。
二、INSERT INTO SELECT:查询结果直接写入
语法
sql
INSERT INTO 目标表 (字段1, 字段2, ...)
SELECT 字段1, 字段2, ...
FROM 源表
WHERE 条件;
示例:从 users 表导入 VIP 用户到 vip_users
sql
-- 创建 vip_users 表
CREATE TABLE vip_users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
email VARCHAR(100),
level VARCHAR(20),
created_at DATETIME
);
-- 将 users 表中积分 > 1000 的用户插入 vip_users
INSERT INTO vip_users (name, email, level, created_at)
SELECT u.name, u.email, '黄金', u.created_at
FROM users u
WHERE u.points > 1000;
执行结果:
java
Query OK, 42 rows affected (0.01 sec)
Records: 42 Duplicates: 0 Warnings: 0
注意事项
- 字段数量和类型必须一一对应
- 可用表达式、常量、函数,如上面硬编码了
'黄金' - 注意字符集一致,避免乱码
三、批量 INSERT:一次写入多行
基础语法
sql
INSERT INTO 表 (字段1, 字段2) VALUES
(值1a, 值2a),
(值1b, 值2b),
(值1c, 值2c);
示例:批量插入订单数据
sql
INSERT INTO orders (user_id, product, amount, status) VALUES
(101, '笔记本电脑', 5999.00, '已支付'),
(102, '机械键盘', 899.00, '待支付'),
(103, '显示器', 2499.00, '已支付'),
(104, '鼠标垫', 29.90, '已取消'),
(105, 'USB Hub', 89.00, '待支付');
性能对比
| 方式 | 写入 1000 行 | 特点 |
|---|---|---|
| 逐条 INSERT | ~850ms | 每条独立事务,开销大 |
| 批量 INSERT(100 条/批) | ~45ms | 单条 SQL,大幅减少网络与日志 IO |
| LOAD DATA INFILE | ~10ms | MySQL 原生高速导入,跳过 SQL 层 |
经验法则: 单次批量 INSERT 建议 100~500 行,过大可能导致 binlog 膨胀和锁竞争。
四、INSERT IGNORE:跳过重复行
语法
sql
INSERT IGNORE INTO 表 (字段列表) VALUES (值列表);
示例
sql
CREATE TABLE products (
sku VARCHAR(20) PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2)
);
INSERT IGNORE INTO products (sku, name, price) VALUES
('SKU001', '无线鼠标', 129.00),
('SKU002', '蓝牙耳机', 399.00),
('SKU001', '无线鼠标-副本', 99.00); -- SKU001 已存在,此行被忽略
执行结果:
java
Query OK, 2 rows affected (0.00 sec)
Records: 3 Duplicates: 1 Warnings: 0
第 3 行因主键重复被跳过,其余 2 行插入成功,不报错。
五、ON DUPLICATE KEY UPDATE:冲突时自动更新
语法
sql
INSERT INTO 表 (字段列表) VALUES (值列表)
ON DUPLICATE KEY UPDATE
字段1 = 值1,
字段2 = 值2;
示例:商品库存 upsert
sql
CREATE TABLE inventory (
sku VARCHAR(20) PRIMARY KEY,
stock INT NOT NULL,
updated_at DATETIME
);
-- 插入或更新库存
INSERT INTO inventory (sku, stock, updated_at) VALUES ('SKU001', 50, NOW())
ON DUPLICATE KEY UPDATE
stock = stock + VALUES(stock), -- VALUES() 引用本次插入的值
updated_at = VALUES(updated_at);
多次执行后:
sql
-- 第 1 次执行 → 插入 (SKU001, 50)
-- 第 2 次执行 → 更新 stock = 50 + 50 = 100
-- 第 3 次执行 → 更新 stock = 100 + 50 = 150
VALUES(字段)是 MySQL 语法,引用本次 INSERT 部分的对应值。MySQL 8.0 起推荐使用别名方式:
sql
INSERT INTO inventory (sku, stock, updated_at) VALUES ('SKU001', 50, NOW()) AS new
ON DUPLICATE KEY UPDATE
stock = inventory.stock + new.stock,
updated_at = new.updated_at;
实战:用户积分流水 upsert
sql
INSERT INTO user_points (user_id, total_points, last_updated) VALUES (1001, 200, NOW())
ON DUPLICATE KEY UPDATE
total_points = total_points + VALUES(total_points),
last_updated = VALUES(last_updated);
六、REPLACE INTO:删除再插入
语法
sql
REPLACE INTO 表 (字段列表) VALUES (值列表);
示例
sql
REPLACE INTO products (sku, name, price) VALUES
('SKU001', '无线鼠标-升级版', 159.00);
⚠️ 与 ON DUPLICATE KEY UPDATE 的区别
| 行为 | REPLACE | ON DUPLICATE KEY UPDATE |
|---|---|---|
| 冲突时 | 先 DELETE 再 INSERT | 直接 UPDATE |
| 自增 ID | 会变化(删除重建) | 保持不变 |
| 触发器 | DELETE + INSERT 各触发一次 | 仅 UPDATE 触发器 |
| 性能 | 较差(删除+插入两倍开销) | 较好(原地更新) |
结论: 绝大多数场景优先用
ON DUPLICATE KEY UPDATE,除非你确实想重置整行数据。
七、各数据库冲突处理语法对照
| 数据库 | 语法 |
|---|---|
| MySQL | ON DUPLICATE KEY UPDATE |
| PostgreSQL | ON CONFLICT (字段) DO UPDATE SET ... |
| SQLite | ON CONFLICT(字段) DO UPDATE SET ... |
| SQL Server | MERGE ... WHEN MATCHED THEN UPDATE |
八、综合实战:每日销售汇总
sql
-- 销售明细表
CREATE TABLE daily_sales_summary (
sale_date DATE NOT NULL,
product_id INT NOT NULL,
total_qty INT DEFAULT 0,
total_amount DECIMAL(12,2) DEFAULT 0,
PRIMARY KEY (sale_date, product_id)
);
-- 从订单明细汇总插入,冲突则累加
INSERT INTO daily_sales_summary (sale_date, product_id, total_qty, total_amount)
SELECT
DATE(order_time),
product_id,
SUM(quantity),
SUM(quantity * price)
FROM order_items
WHERE order_time >= '2026-07-24' AND order_time < '2026-07-25'
GROUP BY DATE(order_time), product_id
ON DUPLICATE KEY UPDATE
total_qty = daily_sales_summary.total_qty + VALUES(total_qty),
total_amount = daily_sales_summary.total_amount + VALUES(total_amount);
九、避坑指南 🚧
| 陷阱 | 说明 |
|---|---|
| 批量插入过大 | 单条 SQL 超过 max_allowed_packet(默认 64MB)会报错 |
| 忽略 timezone | VALUES(NOW()) 在冲突更新时用的是第二次插入的时间,而非当前时间 |
| 主键自增跳跃 | 使用 ON DUPLICATE KEY UPDATE 冲突时自增计数器仍会 +1,造成 ID 空洞 |
| REPLACE 副作用 | 会删除索引外的其他行(外键约束下可能失败) |
📝 今日总结
INSERT INTO ... SELECT--- 数据迁移/复制的利器- 批量 INSERT --- 大幅提升写入性能
INSERT IGNORE--- 跳过重复,静默容错ON DUPLICATE KEY UPDATE--- "有则更新,无则插入"的 Upsert 模式REPLACE--- 慎用,有副作用
思考题: 假设你有一个日志表,每秒写入 1000 条。你会怎么设计插入方案来保证性能?提示:想想批量写入 + 表分区。