INSERT INTO语句用法详解:从基础插入到批量高效写入

2026-06-16 其他资讯 admin 1 次阅读

insert into语句用法详解:数据库入门基础

想象一下,你刚接手一家新开的咖啡馆。

店里空荡荡的,只有几张桌子和一台收银机。

这时候,客人来了,点了一杯拿铁。

你需要做两件事:第一,在菜单上确认这杯咖啡的价格;第二,在账本上记下“张三,拿铁,30元”。

在数据库的世界里,INSERT INTO 语句就是那个记账的动作。

很多新手开发者对 SELECT(查询)情有独钟,觉得查数据很酷,像侦探破案。

但说实话,如果只会查不会写,数据库就是个只读的新闻联播,毫无价值。

数据不进去,系统就是死的。

今天咱们不聊晦涩的理论,就聊聊怎么把数据稳稳当当地塞进数据库里。

最基础的“填空”游戏

先来看最原始的写法。

假设你有一张表叫 users,里面有三列:id(用户ID)、username(用户名)和 age(年龄)。

你想插入一个新用户,叫“阿强”,25岁。

代码长这样:

INSERT INTO users (id, username, age)
VALUES (1, '阿强', 25);

这段代码的意思很简单:往 users 表里,在指定的列里,填入指定的值。

注意括号里的顺序。

VALUES 后面的值,必须和前面列出的列一一对应。

第一个值是 1,对应 id;第二个是 '阿强',对应 username;第三个是 25,对应 age

顺序乱了,数据就错位了。

比如你把 25 放在了第二个位置,那“阿强”就突然变成 25 岁了,或者更糟,程序直接报错。

这是新手最容易犯的错:列名和值的顺序搞混。

还有一种更偷懒的写法,省略列名:

INSERT INTO users
VALUES (1, '阿强', 25);

这时候,数据库会默认按照建表时列定义的顺序,从左到右填入所有值。

只要你的列顺序没变,这招挺好使。

但一旦有人修改了表结构,比如插入了一个新列 email,这行代码就会瞬间崩溃,因为值的数量对不上了。

所以,老手通常建议:永远显式写出列名。

这样代码更清晰,也更具防御性。

说白了,显式写出列名,就是给未来的自己留条后路。

批量插入:效率的救星

现实场景中,你很少只插入一条数据。

如果是注册系统,用户可能成百上千地涌入。

如果你用循环,一条一条地执行 INSERT,数据库服务器会被你的请求打爆。

每次执行 SQL,都需要经过网络传输、解析、语法检查、执行计划优化、写入磁盘。

这一套流程下来,开销不小。

这时候,INSERT INTO 的批量写法就派上用场了。

语法也很简单,只需在 VALUES 后面加上多组数据,用逗号隔开:

INSERT INTO users (id, username, age)
VALUES 
(1, '阿强', 25),
(2, '李娜', 23),
(3, '王五', 30);

这就好比你去超市结账,一次性把所有商品放在传送带上,而不是扫一个,去前面排队付一次款。

效率提升是指数级的。

不过,这里有个坑。

有些数据库(比如 MySQL)对单次 INSERT 语句的长度有限制,受 max_allowed_packet 参数影响。

如果你一次插入几万条数据,可能会因为包太大而被截断或拒绝。

这时候,就需要分批次插入,或者使用数据库特有的批量导入工具(如 MySQL 的 LOAD DATA INFILE)。

但在日常开发中,一次插入几百到几千条数据,通常是没有问题的。

关键是平衡性能和安全。

默认值与自增主键的默契

很多表都有一个 id 字段,类型是 INTBIGINT,并且设置了 AUTO_INCREMENT(自增)。

这意味着,你不需要手动指定 id 的值。

数据库会自动帮你生成下一个可用的 ID。

这时候,你的 INSERT 语句可以写成这样:

INSERT INTO users (username, age)
VALUES ('阿强', 25);

注意,id 列被省略了。

数据库看到 id 列缺失,且该列是自增的,就会自动填入一个比当前最大值大 1 的数字。

这种写法非常优雅,避免了手动维护 ID 的麻烦。

但你也可以显式地插入 NULLDEFAULT

INSERT INTO users (id, username, age)
VALUES (NULL, '阿强', 25);

或者:

INSERT INTO users (id, username, age)
VALUES (DEFAULT, '阿强', 25);

效果是一样的。

这看起来像是废话,但在某些复杂场景下很有用。

比如,你的自增 ID 被删除过,或者你手动重置过计数器,显式使用 DEFAULT 可以确保你插入的是逻辑上的“下一个”值,而不是物理上的最大值。

另外,如果表中其他列也有默认值(比如 created_at 默认为当前时间),你也可以选择不插入这些列,让数据库自动填充。 从查询结果插

这大大简化了应用层的逻辑。

你不需要在代码里获取当前时间戳,再传给 SQL。

数据库比你更清楚“现在”是什么时候。

插入冲突:当数据已经存在怎么办?

这是实际开发中最头疼的问题之一。

假设用户重复提交了注册请求,或者程序逻辑有 bug,导致同一条数据被尝试插入两次。

第一次插入成功了。

第二次插入时,如果 username 设置了唯一约束(Unique Key),数据库就会报错。

错误信息通常是:“Duplicate entry '阿强' for key 'username'”。

程序如果没处理好这个异常,就会直接崩溃,给用户显示一个白色的错误页面。

这体验太差了。

我们需要一种机制,告诉数据库:“如果数据存在,就更新它;如果不存在,就插入它。”

这就是 INSERT INTO ... ON DUPLICATE KEY UPDATE 的用法(以 MySQL 为例)。 批量插入

INSERT INTO users (id, username, age)
VALUES (1, '阿强', 25)
ON DUPLICATE KEY UPDATE age = 25;

这段代码的意思是:尝试插入这条数据。

如果因为唯一键冲突导致插入失败,那就执行 UPDATE 语句,更新 age 字段。

注意,ON DUPLICATE KEY UPDATE 后面的字段,必须和 INSERT 中的字段一致,或者至少是你要更新的字段。

这个功能在“幂等性”设计中非常有用。

比如,用户的积分更新。

你可以直接执行这条 SQL,无论用户当前有多少积分,系统都能保证数据的一致性,而不需要先去 SELECT 查询当前积分,加一,再 UPDATE

省去了两次网络请求,也避免了并发竞争条件。

当然,不同数据库的实现方式不同。

PostgreSQL 用的是 INSERT INTO ... ON CONFLICT DO UPDATE

SQL Server 用的是 MERGE 语句,或者在应用层先判断再决定。

但核心思想是一样的:要么插,要么更,别报错。

从查询结果插入:数据搬运工

有时候,我们需要把数据从一个表复制到另一个表。

比如,每月生成一张“月度统计表”,需要从“每日流水表”中汇总数据。

这时候,INSERT INTO ... SELECT 就派上用场了。

INSERT INTO monthly_stats (month, total_sales)
SELECT DATE_FORMAT(sale_date, '%Y-%m') as month, SUM(amount) as total_sales
FROM daily_sales
WHERE sale_date >= '2023-01-01'
GROUP BY month;

这段代码的逻辑很清晰:先 SELECT 出需要的数据,然后直接 INSERT 到目标表中。

注意,SELECT 出来的列的顺序和数量,必须和 INSERT INTO 后面的列完全匹配。

这种写法在处理历史数据迁移、数据归档、ETL 任务时非常常见。

它比在应用层用代码循环查询再插入要快得多,因为数据库引擎内部优化了数据传输,避免了网络 IO 的瓶颈。

不过,这里有个细节要注意。

如果源表和目标表的结构不一致,或者数据类型不兼容,可能会发生隐式类型转换,导致精度丢失或性能下降。

所以在写这种语句时,最好显式指定目标列,并确保 SELECT 的结果与之对应。

性能与安全的隐形杀手

聊完了用法,咱们得聊聊风险。

INSERT 语句写错了,后果很严重。

首先是 SQL 注入。

如果你在拼接 SQL 字符串时,直接把用户输入塞进去,比如:

-- 危险写法
String sql = "INSERT INTO users (username) VALUES ('" + userInput + "')";

如果用户输入的是 ' OR 1=1; --,你的数据库就会变成这样:

INSERT INTO users (username) VALUES ('' OR 1=1; --')

这不仅会插入脏数据,甚至可能破坏表结构。

解决办法只有一个:使用预编译语句(Prepared Statements)。

在 Java 中是 PreparedStatement,在 Python 中是 cursor.execute(sql, params),在 Go 中是 db.Query("...", args...)

预编译语句会将 SQL 结构和数据分开传输,数据库先编译 SQL 模板,再填入参数。

这样,即使用户输入了恶意代码,它也只会被当作普通字符串处理,而不会被当作 SQL 命令执行。

这是底线,没得商量。

其次是性能瓶颈。

虽然批量插入很快,但如果不加索引,或者表太大,INSERT 依然会慢。

数据库在插入数据时,需要维护索引。

如果一张表有几十个索引,每次插入都要更新这么多索引树,IO 压力巨大。

所以,在建表时,索引不是越多越好。

只为你经常查询的字段建立索引。

对于 INSERT 操作来说,索引越多,写入越慢。

如果你正在进行大规模数据导入,建议暂时禁用非必要的索引,导入完成后再重建。

这在 MySQL 中可以通过 ALTER TABLE ... DISABLE KEYS 来实现(仅限 MyISAM,InnoDB 需通过调整参数)。 比如

总结:数据是活的

数据库不是静态的仓库,它是活的。

INSERT INTO 是让数据流动起来的血液。

从最简单的单条插入,到批量操作,再到处理冲突和数据迁移,每一个场景都有其特定的最佳实践。

记住几个核心原则:

  1. 始终显式指定列名,保持代码的可读性和健壮性。 2. 批量操作时,注意数据库的限制和事务边界。 3. 遇到唯一键冲突,善用 ON DUPLICATE KEYON CONFLICT,避免应用层崩溃。 4. 永远使用预编译语句,防止 SQL 注入。 5. 权衡索引与写入性能,不要盲目堆砌索引。

数据入表只是第一步。

后续的查询、更新、删除,都依赖于插入时的规范。

写对每一行 INSERT,就是为系统的稳定性打下基石。