插入数据
向表中新增行使用 INSERT 语句。它属于 数据操纵语言(Data Manipulation Language,DML),只负责增加记录,不会自动修改或删除已经存在的记录。
本文以通用 SQL 为主。不同数据库在自增列、重复键处理、返回自增值等方面存在差异,示例中的方言写法会单独标注。
INSERT 的基本结构
最常用的写法是:
INSERT INTO table_name (column_1, column_2, column_3)
VALUES (value_1, value_2, value_3);
列名和数据值按位置一一对应:第一个值写入第一个列,第二个值写入第二个列,以此类推。建议始终显式写出列名,不要依赖表的物理列顺序。
下面假设存在一张商品表:
CREATE TABLE products (
product_id BIGINT GENERATED ALWAYS AS IDENTITY,
sku VARCHAR(40) NOT NULL,
product_name VARCHAR(120) NOT NULL,
description TEXT,
price DECIMAL(12, 2) NOT NULL,
stock INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'on_sale',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_products PRIMARY KEY (product_id),
CONSTRAINT uq_products_sku UNIQUE (sku),
CONSTRAINT ck_products_price CHECK (price >= 0),
CONSTRAINT ck_products_stock CHECK (stock >= 0)
);
GENERATED ALWAYS AS IDENTITY 是标准 SQL 风格的自动生成编号写法;实际使用时,MySQL、SQL Server 和 SQLite 可能需要不同的建表语法。
插入一行
INSERT INTO products (sku, product_name, price, stock)
VALUES ('BK-001', 'SQL 入门', 59.90, 10);
这条语句会新增一行:
product_id由数据库生成;description没有提供值,因而为NULL;status使用默认值'on_sale';created_at使用当前时间的默认值。
插入后可以查询结果:
SELECT product_id, sku, product_name, price, stock, status
FROM products
WHERE sku = 'BK-001';
查询结果需要稳定顺序时,应显式使用 ORDER BY,不要假设插入顺序就是返回顺序。
为什么要写列名
下面的写法依赖表中所有列的顺序:
-- 不推荐:表结构增加、调整列顺序后容易失效
INSERT INTO products
VALUES (1, 'BK-001', 'SQL 入门', NULL, 59.90, 10, 'on_sale', CURRENT_TIMESTAMP);
显式列名有三个好处:
- 读者能直接看出每个值的含义;
- 可以省略自动生成、可空或有默认值的列;
- 表增加列时,已有插入语句不一定需要改动。
只有在确实需要为每一列赋值、并且已经接受结构强耦合时,才考虑省略列名。
一次插入多行
在同一条 INSERT 中使用多个值列表:
INSERT INTO products (sku, product_name, price, stock)
VALUES
('BK-002', 'SQL 查询实践', 79.00, 8),
('BK-003', '数据库设计', 88.50, 5),
('BK-004', '事务原理', 66.00, 12);
每一行都必须满足以下要求:
- 值的数量与列的数量相同;
- 值的顺序与列的顺序一致;
- 值能够转换为对应列的数据类型;
- 所有
NOT NULL、UNIQUE、外键和CHECK约束都能通过。
一次插入多行通常比循环执行多条单行语句更高效,但大批量导入仍应根据数据库和驱动的限制分批处理。单条语句出错时,整条语句如何处理由数据库和事务设置决定,不能把不同数据库的行为简单等同起来。
默认值、NULL 与 DEFAULT
这三个概念很容易混淆。
省略列:让默认值生效
如果插入语句不包含某列,数据库会按该列定义处理:有 DEFAULT 就使用默认值,没有默认值且允许空值就使用 NULL。
INSERT INTO products (sku, product_name, price)
VALUES ('BK-005', 'SQL 性能优化', 99.00);
这里 stock 为 0,status 为 'on_sale',description 为 NULL。
使用 DEFAULT 关键字
当列已经列出,但本次写入希望使用它的默认值时,可以写 DEFAULT:
INSERT INTO products (sku, product_name, price, stock, status)
VALUES ('BK-006', 'SQL 安全', 72.00, DEFAULT, DEFAULT);
DEFAULT 表示“采用列定义的默认表达式”,不是固定的字符串或数字。默认值可能是 CURRENT_TIMESTAMP 这样的表达式。
显式写入 NULL
INSERT INTO products (sku, product_name, description, price)
VALUES ('BK-007', 'SQL 进阶', NULL, 105.00);
这会明确把 description 设为 NULL。如果把 NULL 写入一个有默认值的列,通常不会触发默认值,而是写入空值;但具体行为仍应以目标数据库文档为准。
NULL 表示缺失或未知,不等于空字符串 ''、数字 0 或布尔值 FALSE。业务上必填的列应使用 NOT NULL,不要依靠插入方“自觉”提供值。
全部使用默认值
当一张表的每一列都允许空值、拥有默认值或由数据库生成时,可以使用:
INSERT INTO audit_events DEFAULT VALUES;
这表示不提供任何业务值,让数据库为所有列选择默认处理方式。部分数据库使用其他方言表达同样的意思,迁移 SQL 前应核对目标数据库。
从查询结果插入
INSERT 不只能接收 VALUES,还可以把 SELECT 的结果写入另一张表:
INSERT INTO archived_products (sku, product_name, price, archived_at)
SELECT sku, product_name, price, CURRENT_TIMESTAMP
FROM products
WHERE status = 'off_sale';
这种方式适合归档、数据迁移和批量转换。写这类语句时要注意:
- 目标列列表与
SELECT返回列按位置对应; - 类型、数量和精度必须兼容;
- 不要使用
SELECT *,否则源表增加列后可能改变插入含义; - 目标表的唯一约束、外键和检查约束仍然会生效;
- 可能重复执行的任务要设计唯一键或其他幂等策略。
也可以在 SELECT 中清洗和转换数据:
INSERT INTO product_search (product_id, search_name)
SELECT product_id, LOWER(TRIM(product_name))
FROM products
WHERE product_name IS NOT NULL;
自动生成列和业务主键
自动生成的编号通常不应在普通插入中手动提供:
INSERT INTO products (sku, product_name, price)
VALUES ('BK-008', 'SQL 编程规范', 68.00);
这样做可以让数据库负责生成唯一编号,也避免应用层在并发场景下自行计算“下一个编号”。自动编号不保证连续,事务回滚、并发写入和删除都可能留下空号,因此不要把连续编号当作业务规则。
数据迁移确实需要保留旧编号时,应使用目标数据库提供的专用方式。例如,PostgreSQL 的 identity 列可使用 OVERRIDING SYSTEM VALUE,SQL Server 可能需要 IDENTITY_INSERT,MySQL 通常可以显式写入 AUTO_INCREMENT 列。导入完成后还要确认序列或自增计数器不会与已有数据冲突。
业务编号(如 sku、订单号)与技术主键是两类概念。业务编号需要稳定且唯一时,应单独设置 UNIQUE 约束,而不是把它和数据库生成的主键混为一谈。
处理重复数据
重复提交可能来自用户重复点击、网络重试、消息重复投递或任务重新执行。仅在应用层“先查询再插入”存在并发竞态,最终仍应由数据库唯一约束兜底。
通用做法是先定义业务唯一键:
ALTER TABLE products
ADD CONSTRAINT uq_products_sku UNIQUE (sku);
需要“已存在就忽略”或“已存在就更新”时,语法属于数据库方言。例如:
-- PostgreSQL、SQLite 等数据库常见写法
INSERT INTO products (sku, product_name, price)
VALUES ('BK-001', 'SQL 入门(修订版)', 62.00)
ON CONFLICT (sku) DO NOTHING;
-- MySQL 常见写法
INSERT INTO products (sku, product_name, price)
VALUES ('BK-001', 'SQL 入门(修订版)', 62.00)
ON DUPLICATE KEY UPDATE
product_name = VALUES(product_name),
price = VALUES(price);
这些写法不能直接跨数据库复制。更新已有行时,要明确哪些列允许被覆盖,并评估触发器、更新时间、审计记录和并发冲突的影响。若业务逻辑是“存在则更新,不存在则插入”,也可以考虑目标数据库支持的 MERGE,但必须阅读对应版本的并发和唯一性说明。
获取生成的值
插入后经常需要拿到数据库生成的主键或时间戳。部分数据库支持在语句末尾返回:
INSERT INTO products (sku, product_name, price)
VALUES ('BK-009', 'SQL 实战', 89.00)
RETURNING product_id, created_at;
RETURNING 并非所有数据库都支持;SQL Server 常见 OUTPUT inserted.product_id,MySQL 通常通过驱动提供的“生成键”接口读取自增值。应用程序应使用驱动的参数绑定和生成键 API,不要通过再次查询“最大 ID”来猜测刚插入的行,因为并发写入会导致结果错误。
约束如何影响插入
插入不是绕过数据规则的入口,数据库会在写入时检查约束:
| 约束或规则 | 常见失败原因 |
|---|---|
NOT NULL | 必填列没有值,且没有可用默认值 |
PRIMARY KEY / UNIQUE | 主键或业务唯一键重复 |
FOREIGN KEY | 被引用的父表记录不存在 |
CHECK | 价格、库存或状态不满足范围/集合要求 |
| 数据类型 | 字符串、数字、日期等无法转换,或超出精度/长度 |
| 权限 | 当前用户没有对目标表插入的权限 |
例如,下面两条语句会分别触发唯一约束和检查约束:
-- sku BK-001 已存在时失败
INSERT INTO products (sku, product_name, price)
VALUES ('BK-001', '重复商品', 10.00);
-- price 不能为负数
INSERT INTO products (sku, product_name, price)
VALUES ('BK-010', '错误商品', -1.00);
看到错误时,应根据数据库返回的约束名和列名定位问题,不要通过删除约束或把所有值改成字符串来“修复”。
事务中的插入
需要同时写入多张表时,通常应放在同一个事务中:
BEGIN;
-- 通过 RETURNING、OUTPUT 或驱动的生成键接口取得新订单 ID
INSERT INTO orders (user_id, total_amount)
VALUES (1001, 158.90);
-- 将上一条语句返回的订单 ID 绑定到 :new_order_id
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (:new_order_id, 1, 2);
COMMIT;
如果中途失败,应回滚整个事务:
ROLLBACK;
这样可以避免只创建了订单、却没有订单明细的半成品数据。BEGIN、自动提交和事务隔离级别会因数据库与驱动配置而不同;批量导入前应确认连接是否处于自动提交模式,以及单条语句失败时事务是否仍可继续。
对于很大的导入任务,可以按固定数量分批提交,并记录成功批次和失败位置。批次过大可能占用过多内存、锁和日志空间,批次过小则会增加提交开销。
使用参数化查询
应用程序不要把用户输入直接拼接到 SQL 字符串中:
错误思路:"INSERT INTO products (sku) VALUES ('" + userInput + "')"
应使用数据库驱动提供的参数化查询:
INSERT INTO products (sku, product_name, price)
VALUES (?, ?, ?);
? 只是部分驱动的占位符,其他驱动可能使用 $1、:sku 或 @sku。参数化可以正确处理引号、特殊字符和类型转换,也是防止 SQL 注入的关键措施。表名和列名通常不能作为普通值参数绑定;如果它们来自用户选择,应使用服务端维护的白名单映射。
一个完整的插入流程
下面的流程把显式列名、默认值、事务和结果检查放在一起:
BEGIN;
INSERT INTO products (sku, product_name, description, price, stock)
VALUES
('BK-011', 'SQL 事务实战', '通过示例理解提交与回滚。', 92.00, 20),
('BK-012', 'SQL 索引基础', NULL, 75.00, 15);
-- 在应用代码中使用驱动返回的生成键,或按业务唯一键查询刚插入的数据
SELECT product_id, sku, status
FROM products
WHERE sku IN ('BK-011', 'BK-012')
ORDER BY sku;
COMMIT;
实际项目中,查询验证结果通常由应用程序执行;不要用“查询最大主键”的方式判断刚插入的行。
插入数据检查清单
编写或审查 INSERT 时,可以依次检查:
- 是否明确写出了目标列名,并且列和值逐一对应;
- 是否正确区分了省略列、
DEFAULT、NULL和空字符串; - 是否省略了应该由数据库生成的主键或时间列;
- 数字、金额、日期和布尔值是否使用了合适的数据类型;
- 是否会触发主键、唯一键、外键、检查约束或
NOT NULL错误; - 批量导入是否需要分批、事务和失败重试策略;
- 可重复执行的任务是否有业务唯一键和幂等写入策略;
- 应用代码是否使用参数化查询,而不是拼接用户输入;
- 是否确认了目标数据库关于自增列、重复键和返回值的方言;
- 是否用明确条件验证了写入结果,而不是依赖行顺序或最大 ID。
掌握 INSERT INTO ... VALUES 后,再结合 INSERT ... SELECT、约束和事务,就能覆盖大多数日常新增数据与批量导入场景。