<p>INSERTED 不是临时表,而是触发器中只读的内存快照虚拟行集,无物理存储,不支持建索引;真正可建索引的是 # 和 ## 临时表。</p>

INSERTED 临时表不能建索引——它根本不是真正的“表”,而是 DML 触发器中一个只读的逻辑结果集视图,底层没有物理存储结构,自然不支持 CREATE INDEX。
INSERTED 不是临时表,是触发器上下文中的虚拟行集
很多人误以为 INSERTED 是类似 #temp 的临时表,但实际它只是 SQL Server 在 AFTER 或 INSTEAD OF 触发器执行期间,为刚插入/更新/删除的数据提供的**内存中快照引用**。它没有对象 ID、不占 tempdb 空间、不能被 SELECT INTO、也不能 ALTER 或 DROP。
-
INSERTED和DELETED只在触发器作用域内存在,离开触发器体就不可访问 - 尝试执行
CREATE INDEX IX ON INSERTED(col)会直接报错:Msg 102, Level 15, State 1, Line X: Incorrect syntax near 'INSERTED'. - 它不支持
TRUNCATE、UPDATE、INSERT(除作为FROM源外),甚至不能加表别名后用AS(语法限制)
真正能建索引的“临时表”只有 # 和 ## 开头的那些
只有显式用 CREATE TABLE #t 或 SELECT ... INTO #t 创建的局部/全局临时表,才分配 tempdb 中的页和 IAM 结构,才能走完整 DDL 流程。这类表支持:
-
CREATE CLUSTERED INDEX(含主键隐式创建) -
CREATE NONCLUSTERED INDEX(带INCLUDE或WHERE过滤) - 索引可建在
tempdb的任意文件组上(包括PRIMARY或自定义) - SQL Server 2014+ 还支持在临时表上建内存优化索引(若启用内存优化)
想加速触发器里对 INSERTED 的处理?换思路
既然不能索引 INSERTED,就得绕开它做预处理:
- 把
INSERTED数据先INSERT INTO #staging—— 这个 staging 表可以提前建好索引 - 避免在触发器里对
INSERTED做多表 JOIN 或子查询;改用 CTE +ROW_NUMBER()控制数据流 - 如果业务允许,用
INSTEAD OF触发器接管插入逻辑,自己控制中间表生命周期 - 高频小批量场景下,考虑用表变量
@t替代(但注意:表变量不支持显式索引,仅靠主键/唯一约束生成隐式索引)
容易被忽略的关键点
最常踩的坑不是“怎么建索引”,而是没意识到 INSERTED 的生命周期和语义边界:
- 触发器嵌套时,每个层级的
INSERTED是独立快照,不会叠加 - 批量插入(如
BULK INSERT)触发的触发器,INSERTED可能包含数千行,但查询计划仍按“小结果集”估算,易导致参数嗅探失效 - 在
INSTEAD OF触发器中,INSERTED是唯一数据源,必须用它驱动后续逻辑——这时 staging 表 + 预建索引就是刚需

















