select * from deleted 不可靠,因表结构变更、计算列、lob等会导致报错或语义失真;应显式指定字段、用for json auto序列化或仅取主键,并避免单变量赋值多行数据。

不能直接用 SELECT * FROM deleted 安全捕获被删记录的所有字段——它在多数真实场景下会失败或埋下隐患,尤其当表结构变更、含计算列、稀疏列、大型对象(LOB)或触发器跨库调用时。
为什么 SELECT * 在 deleted 表里不可靠
SQL Server 的 deleted 是只读虚拟表,其列结构严格镜像原表,但 SELECT * 会隐式依赖当前列顺序和存在性,导致:
- 审计表字段与
deleted列数/顺序不一致时,INSERT INTO audit_table SELECT * FROM deleted直接报错Column name or number of supplied values does not match table definition - 原表新增一列(如
last_modified DATETIME2),而审计表未同步,插入失败 - 原表含
AS PERSISTED计算列,SELECT *会把它也拉进来,但该值在deleted中实际为NULL(计算列不存物理值),造成语义失真 - 使用
FOR JSON AUTO或FOR XML时,SELECT *可能因列名冲突(如多表 JOIN 后的重复列)抛出Column 'xxx' is invalid in the select list
安全获取整行删除数据的推荐写法
核心原则:显式控制字段来源、序列化方式和目标兼容性。不要让 SQL Server 猜你想要什么。
- 若只需主键用于日志关联:直接
SELECT id FROM deleted(单列)或SELECT order_id, line_no FROM deleted(复合主键) - 若需完整快照且审计表结构固定:显式列出所有字段,例如
SELECT id, name, email, created_at FROM deleted;后续表结构变更时,触发器必须同步改 - 若需灵活适配结构变化:用
FOR JSON AUTO序列化整行,SELECT (SELECT * FROM deleted FOR JSON AUTO) AS DeletedData—— 返回一个NVARCHAR(MAX)字符串,不依赖目标表字段数,也避开 NULL 处理歧义 - 避免
CONVERT(NVARCHAR(MAX), ...)拼接:它对datetime、uniqueidentifier等类型格式不统一,且无法还原原始类型信息
并发删除或多行场景下的关键约束
deleted 表天然支持多行,但很多人误把它当单行变量用,引发漏数据或死锁:
- 别写
DECLARE @id INT; SELECT @id = id FROM deleted—— 若删 100 行,@id只保留其中任意一行的值(SQL Server 不保证哪一行),其余丢失 - 别在触发器里再查原表验证:如
IF EXISTS (SELECT 1 FROM Orders WHERE id IN (SELECT id FROM deleted))—— 此时原表已无这些行,逻辑冗余,还可能因隔离级别看到未提交的中间状态 - 若需关联其他表(如用户信息),必须用
JOIN deleted ON ...,而非子查询套IN (SELECT ...),否则执行计划易退化为嵌套循环+重复扫描 - 注意
FOR JSON在大结果集(如一次删 10 万行)下内存开销高,建议限制单次触发器处理量,或改用批量异步归档
真正容易被忽略的是:即使你用了 FOR JSON AUTO,如果原表含 xml、geometry 或 hierarchyid 类型列,它们默认不会被 JSON 序列化(返回 null),需要显式转换为字符串再拼接。这不是语法错误,而是静默数据丢失。










