数据定义(DDL)决定数据库长什么样,而数据操作(DML)决定数据库每天在做什么。 对于绝大多数业务系统来说,真正运行最频繁的不是 CREATE TABLE,而是 INSERT、UPDATE、DELETE 等 DML 操作。
本文将系统介绍 SQL Server 中最常用的 DML(Data Manipulation Language,数据操作语言)语句,包括 INSERT、UPDATE、DELETE、MERGE、OUTPUT 的使用方法、典型业务场景、性能优化技巧以及生产环境中的注意事项。
一、什么是 DML?
DML(Data Manipulation Language)即数据操作语言 ,主要负责对表中的数据进行新增、修改、删除和合并。
SQL Server 中最核心的 DML 语句包括:
-
INSERT:新增数据
-
UPDATE:修改数据
-
DELETE:删除数据
-
MERGE:同步、合并数据(UPSERT)
-
OUTPUT:返回受影响的数据
可以把数据库比作一本账本:
-
INSERT 就是在账本中新写一条记录;
-
UPDATE 是修改已有记录;
-
DELETE 是划掉记录;
-
MERGE 则像对照两本账本进行同步;
-
OUTPUT 则是操作时自动生成一份变更清单。
二、INSERT------新增数据
INSERT 用于向表中写入新数据。
基本语法
INSERT INTO 表名 (列1, 列2, ...)
VALUES (值1, 值2, ...);
场景一:插入单条用户数据
INSERT INTO Users
(
UserName,
Email,
CreateTime
)
VALUES
(
'Tom',
'tom@test.com',
GETDATE()
);
适用于:
-
用户注册
-
创建订单
-
新增商品
场景二:一次插入多条数据
INSERT INTO Users
(
UserName,
Email
)
VALUES
('Alice','alice@test.com'),
('Bob','bob@test.com'),
('Jack','jack@test.com');
SQL Server 2008 起支持这种写法。
相比循环 INSERT,效率明显更高。
场景三:从查询结果插入
例如归档历史订单。
INSERT INTO OrderHistory
(
OrderID,
UserID,
Amount
)
SELECT
OrderID,
UserID,
Amount
FROM Orders
WHERE Status='Completed';
这种方式通常比程序循环导入快得多。
场景四:使用 DEFAULT
INSERT INTO Users
(
UserName,
Status
)
VALUES
(
'Jerry',
DEFAULT
);
要求字段定义了默认值。
例如:
Status INT DEFAULT 1
性能与注意事项
建议:
-
指定列名,不建议使用
INSERT INTO Table VALUES(...) -
批量导入优先使用多值 INSERT、BCP、BULK INSERT
-
大批量插入可考虑关闭非聚集索引后重建
三、UPDATE------修改数据
UPDATE 用于修改已有记录。
基本语法
UPDATE 表名
SET 列=值
WHERE 条件;
场景一:修改用户手机号
UPDATE Users
SET Phone='13800001111'
WHERE UserID=1001;
场景二:订单批量修改状态
UPDATE Orders
SET Status='Completed'
WHERE PayStatus='Paid';
典型应用:
支付成功后更新订单状态。
场景三:基于 JOIN 更新
例如同步会员等级。
UPDATE U
SET U.LevelName=L.LevelName
FROM Users U
INNER JOIN UserLevel L
ON U.LevelID=L.LevelID;
这种 UPDATE 是 SQL Server 非常实用的扩展。
UPDATE 注意事项
务必带 WHERE 条件。
建议先执行:
SELECT *
FROM Orders
WHERE Status='Pending';
确认影响范围后再:
UPDATE Orders
SET Status='Processing'
WHERE Status='Pending';
这是 DBA 最基本的操作规范。
四、DELETE------删除数据
DELETE 删除的是数据,而不是表。
基本语法
DELETE
FROM 表名
WHERE 条件;
场景一:删除单个用户
DELETE
FROM Users
WHERE UserID=1001;
场景二:删除测试数据
DELETE
FROM Orders
WHERE UserID=-1;
很多开发环境都会保留这种测试账号。
场景三:删除过期日志
DELETE
FROM SystemLog
WHERE CreateTime < DATEADD(MONTH,-6,GETDATE());
这是日志清理最常见的方式。
DELETE 与 TRUNCATE 的区别
| 对比项 | DELETE | TRUNCATE |
|---|---|---|
| 删除方式 | 按行删除 | 整表快速清空 |
| WHERE | 支持 | 不支持 |
| 日志 | 较多 | 较少 |
| Identity | 不重置 | 重置 |
| 触发器 | 会触发 | 不触发 DELETE Trigger |
一般来说:
-
清空整张表 → TRUNCATE
-
删除部分数据 → DELETE
如果存在外键引用,TRUNCATE 通常无法执行。
五、MERGE------同步数据(UPSERT)
MERGE 可以一次完成:
-
存在则更新
-
不存在则插入
-
可选删除目标中多余数据
因此也称 UPSERT。
基本语法
MERGE Target AS T
USING Source AS S
ON T.ID=S.ID
WHEN MATCHED THEN
UPDATE ...
WHEN NOT MATCHED THEN
INSERT ...;
场景一:同步用户信息
MERGE Users AS T
USING TempUsers AS S
ON T.UserID=S.UserID
WHEN MATCHED THEN
UPDATE SET
T.UserName=S.UserName,
T.Email=S.Email
WHEN NOT MATCHED THEN
INSERT
(
UserID,
UserName,
Email
)
VALUES
(
S.UserID,
S.UserName,
S.Email
);
非常适合:
-
数据同步
-
ETL
-
数据仓库
场景二:同步商品库存
每天 ERP 导入库存:
MERGE ProductStock AS T
USING ImportStock AS S
ON T.ProductID=S.ProductID
WHEN MATCHED THEN
UPDATE SET
Stock=S.Stock
WHEN NOT MATCHED THEN
INSERT(ProductID,Stock)
VALUES(S.ProductID,S.Stock);
MERGE 注意事项
SQL Server 多个版本曾修复过 MERGE 的边界 Bug,生产环境建议:
-
保持最新累计更新(CU)
-
并发较高场景可考虑拆分为 UPDATE+INSERT 两步实现
-
大批量同步建议结合事务与索引优化
六、OUTPUT------获取受影响的数据
很多人不知道,SQL Server 可以直接返回本次 DML 操作的数据。
INSERT OUTPUT
INSERT INTO Users
(
UserName
)
OUTPUT INSERTED.UserID,
INSERTED.UserName
VALUES
(
'Lucy'
);
返回:
UserID
UserName
无需再次查询。
UPDATE OUTPUT
UPDATE Orders
SET Amount=Amount+100
OUTPUT
DELETED.Amount AS OldAmount,
INSERTED.Amount AS NewAmount
WHERE OrderID=10;
其中:
-
INSERTED:修改后数据
-
DELETED:修改前数据
非常适合:
-
审计日志
-
数据追踪
-
数据回滚记录
DELETE OUTPUT
DELETE
FROM Users
OUTPUT
DELETED.*
WHERE UserID=100;
删除前的数据可以直接保存到日志表。
七、事务管理:保证数据一致性
多个 DML 通常需要作为一个整体执行。
BEGIN TRAN;
UPDATE Account
SET Balance=Balance-100
WHERE UserID=1;
UPDATE Account
SET Balance=Balance+100
WHERE UserID=2;
COMMIT;
发生异常:
ROLLBACK;
最佳实践:
-
一个业务一个事务
-
事务尽量短
-
不要在事务中等待用户输入
-
及时 COMMIT 或 ROLLBACK
八、DML 最佳实践
1、先 SELECT,再 UPDATE/DELETE
SELECT *
FROM Orders
WHERE Status='Pending';
确认无误后再执行修改。
2、避免锁表
对于百万级数据:
不要:
DELETE FROM Orders;
建议:
WHILE 1=1
BEGIN
DELETE TOP (5000)
FROM Orders
WHERE CreateTime<'2023-01-01';
IF @@ROWCOUNT=0 BREAK;
END
批量删除能够有效减少锁竞争与事务日志压力。
3、合理建立索引
WHERE 条件字段建议建立索引。
否则:
UPDATE、DELETE 很容易全表扫描。
4、批量导入优化
对于海量数据:
-
使用 BULK INSERT
-
使用 SqlBulkCopy(.NET)
-
分批提交事务
-
导入完成后更新统计信息
九、常见陷阱
1、忘记 WHERE
UPDATE Users
SET Status=0;
整个用户表都会被修改。
这是数据库事故中最常见的问题之一。
2、隐式类型转换
例如:
WHERE UserID='100'
如果 UserID 为 INT,SQL Server 可能发生隐式转换,影响索引使用,导致性能下降。
建议保持参数类型与字段类型一致。
3、NULL 判断错误
错误写法:
WHERE Email=NULL
正确写法:
WHERE Email IS NULL
同样:
IS NOT NULL
而不是:
!= NULL
4、外键约束
例如:
Orders
引用
Users
删除用户:
DELETE FROM Users
WHERE UserID=1;
如果订单仍存在,将提示外键冲突。
应:
-
先删除子表
-
或配置级联删除(CASCADE)
-
或重新设计业务逻辑
十、综合案例:订单同步与归档
假设每天凌晨需要同步外部订单,并归档已完成订单。
第一步:同步新增和更新订单
MERGE Orders AS T
USING ImportOrders AS S
ON T.OrderID = S.OrderID
WHEN MATCHED THEN
UPDATE SET
T.Amount = S.Amount,
T.Status = S.Status
WHEN NOT MATCHED THEN
INSERT (OrderID, UserID, Amount, Status)
VALUES (S.OrderID, S.UserID, S.Amount, S.Status);
第二步:记录变更日志
UPDATE Orders
SET Status = 'Archived'
OUTPUT
INSERTED.OrderID,
DELETED.Status,
INSERTED.Status,
GETDATE()
INTO OrderChangeLog
WHERE Status = 'Completed';
第三步:归档历史数据
INSERT INTO OrderHistory
SELECT *
FROM Orders
WHERE Status='Archived';
DELETE
FROM Orders
WHERE Status='Archived';
整个流程建议放入事务中执行,并结合适当索引,确保同步、日志记录和归档的一致性。
十一、SQL Server 版本差异
不同版本对 DML 能力持续增强:
-
SQL Server 2008 :支持多行
VALUES插入、MERGE语句。 -
SQL Server 2012 :增强
OFFSET/FETCH等分页能力,便于与 DML 配合处理批量数据。 -
SQL Server 2016+ :在 JSON、Temporal Table 等特性上有明显增强,可配合
OUTPUT构建审计方案,同时对批量操作和查询优化器进行了持续改进。 -
SQL Server 2019/2022 :智能查询处理(Intelligent Query Processing)进一步优化部分 DML 相关执行计划,但
MERGE在高并发场景仍建议充分测试后再投入生产。
总结
DML 是数据库开发中使用频率最高的一组 SQL 语句,也是最容易因为误操作而引发生产事故的部分。掌握 INSERT、UPDATE、DELETE、MERGE 与 OUTPUT 的正确使用方式,不仅能够完成日常的数据维护工作,更能编写出安全、高效、易维护的数据处理程序。
最后,牢记几条经验法则:
-
任何 UPDATE、DELETE 都应先用 SELECT 验证影响范围。
-
涉及多步修改时使用事务,确保数据一致性。
-
批量操作采用分批提交,减少锁竞争和事务日志压力。
-
充分利用 OUTPUT 实现数据审计与变更追踪。
-
MERGE 虽然功能强大,但在高并发业务中应结合版本特性和实际测试谨慎使用。