人人都会AI编程

8.1 插入数据:INSERT 单条 / 批量插入、INSERT ... SELECT

更新时间:2026-07-10

数据写入是业务最频繁的操作之一,而 INSERT 的用法远不止 INSERT INTO table VALUES (...) 这么简单。不同写法在性能、安全性和可维护性上有显著差异,选对方式、掌握细节,能避免大量线上问题。

8.1.1 单条插入:最基础但不可滥用

最标准的单行插入语法如下:

INSERT INTO users (name, email, age) VALUES ('张三', 'zhangsan@example.com', 28);

你可以省略列名,但强烈不建议。显式列出列名有三个好处:

  1. 结构变更不报错:如果表新增了有默认值的列,旧的不带列名的 SQL 会因列数不匹配而执行失败。
  2. 可读性更好:其他人(包括几个月后的你)能一眼看出每个值对应什么字段。
  3. 避免位置误判:不依赖列顺序,降低数据写错列的风险。

单条插入在开发测试环境完全没问题,但在生产高并发场景下,逐条插入会产生大量网络往返和事务开销,性能极差。如果一条 SQL 只插一行,每秒能处理的写入量可能只有几百行,远达不到 MySQL 的实际吞吐能力。

8.1.2 批量插入:性能提升的关键手段

当一次需写入多行数据时,应优先使用批量插入语法,在一条 SQL 中放入多组值:

INSERT INTO users (name, email, age) VALUES
  ('李四', 'lisi@example.com', 25),
  ('王五', 'wangwu@example.com', 30),
  ('赵六', 'zhaoliu@example.com', 22);

批量插入带来的性能提升十分显著,原因在于:

  • 网络往返次数降为 1 次,减少 TCP 握手、SQL 解析和客户端-服务器交互的开销。
  • 事务开销大幅降低,一次批量插入只提交一次事务,节省了多次提交的日志刷盘和锁同步成本。
  • 索引更新可批量处理,插入多行后,InnoDB 的变更缓冲(Change Buffer)可以合并处理二级索引的维护,减少随机 I/O。

实际使用中,批量插入有两个关键注意事项:

  • 不要一次插入过多行:单条 SQL 过大(几 MB 甚至几十 MB)会占用过多网络带宽和内存,还可能超过 max_allowed_packet 导致失败。建议每批 500~2000 行,根据实际负载调整。应用中可以分批循环提交。
  • 必须处理部分失败:在 InnoDB 默认情况下,批量插入是一个原子操作——如果其中一行因主键冲突或违反唯一约束而失败,整条 SQL 会回滚,之前批次中已提交的数据不受影响。如果你的业务允许某些行插入失败而其他行继续,可以在语句中添加 IGNORE 关键字:
INSERT IGNORE INTO users (name, email) VALUES
  ('张三', 'zhangsan@example.com'),
  ('李四', 'lisi@example.com');

IGNORE 不是万能药,它只是将错误降级为警告,出问题的行会被跳过,其余行仍然插入。但这会掩盖真正的错误,使用前务必明确业务是否可以容错。

8.1.3 INSERT INTO ... SELECT:从一个表复制到另一个表

有时你需要将查询结果直接插入另一张表,而不是在客户端取出数据再批量插入。INSERT ... SELECT 正是为此设计:

INSERT INTO user_backup (id, name, email, created_at)
SELECT id, name, email, NOW() FROM users WHERE status = 'active';

这种写法的最大优势是数据完全在服务器端流转,不经过客户端,非常适合表间数据迁移、归档、汇总等场景。它既能保证高效,也可在单一语句内完成复杂的数据转换。

使用时需要注意以下几点:

  • 列顺序与类型必须匹配:SELECT 的列数、顺序、数据类型必须与 INSERT 列列表严格一致,否则执行会报错。
  • 谨防锁表和长事务:如果 SELECT 涉及大表全表扫描,会长时间占用资源并持有大量行锁(或间隙锁)。最好在事务中间执行,并预估影响范围。
  • 可以配合 ON DUPLICATE KEY UPDATE:当向已有唯一键的表转移数据时,可以写入冲突时更新:
INSERT INTO stats (day, count) 
SELECT date, COUNT(*) FROM orders GROUP BY date
ON DUPLICATE KEY UPDATE count = VALUES(count);

8.1.4 插入的常见性能陷阱与最佳实践

  • 不要省略列名,已多处强调,这是低成本防御性编码。
  • 尽量使用批量插入代替逐行 INSERT,尤其在导入大量数据时。可以用应用层循环组装批量 SQL,或利用 ORM 的批量插入支持(如 MyBatis 的 <foreach> 标签)。
  • 手动控制事务:如果一次要插入很多批次数据,在每个批次前开启事务,插入后提交,比依赖自动提交(每条 SQL 一个事务)快几十倍。
  • 合理处理自增主键:批量插入时,自增主键会连续分配,即使最终回滚也不会回收,可能导致主键空洞。对于大多数业务这无关紧要,但如果主键值必须密集无空洞,需要单独评估。
  • 避免在热点表上高频单条 INSERT,即使索引优化,锁竞争和日志刷盘也易形成瓶颈,批量 + 合理的分批提交是解决之道。

总之,INSERT 是最基础的写入手段,但不同的写法在性能上差异巨大。把单条变批量、把客户端循环变服务器端 SELECT 插入,往往就是解决写入性能瓶颈的第一步。