大数据量(>1000行)或需多次JOIN/ORDER BY/GROUP BY时必须用#TempTable,因其支持索引、统计信息和跨语句复用;小数据量(≤100行)且作用域受限时优先用@Table,但其优化器固定按5行估算,易致执行计划低效。

大数据量必须用 #TempTable,小数据量(≤100行)优先用 @Table;函数里根本不能用 #TempTable,只能选 @Table。
什么时候该用 #TempTable 而不是 @Table
核心看数据量和后续操作复杂度。优化器对 @Table 默认按“5行”估算,哪怕你插了 5000 行,执行计划仍可能选嵌套循环而不是哈希连接,导致 WHERE 条件走全表扫描、JOIN 内存不足溢出到 tempdb。
- 数据量 > 1000 行,或要被多次
JOIN/ORDER BY/GROUP BY,必须用#TempTable - 需要建索引——
CREATE CLUSTERED INDEX IX_user_id ON #tmp (user_id)是合法的,@Table不支持ALTER TABLE或显式索引 - 中间结果要被存储过程里多个独立语句复用(比如先聚合、再过滤、再关联主表),
#TempTable的统计信息会被后续查询真正利用 - 插入后立刻查,记得手动跑
UPDATE STATISTICS #tmp,否则首次查询仍可能按空表估算
@Table 看似轻量,但这些限制常被忽略
@Table 不写日志、不占事务空间,但换来的是作用域和优化能力的大幅收缩。
- 作用域仅限当前批处理:嵌套存储过程中无法访问外层声明的
@Table,EXEC sp_executesql里也拿不到 - 不能当
INSERT INTO @Table EXEC ...的目标,会报错Invalid use of side-effecting operator 'INSERT EXEC' in a function or procedure - 函数中强制使用
@Table,但UPDATE STATISTICS不可用,且优化器完全无视其实际行数——复杂逻辑下性能可能比预期差几倍 - 字段类型别乱用
SQL_VARIANT或自定义类型,跨批或跨函数时容易隐式转换失败
临时表命名与生命周期的真实风险
用错前缀或忽略清理逻辑,轻则报错,重则并发污染数据。
-
##GlobalTemp是全局的,高并发下 A 用户写入的数据可能被 B 用户读到,99% 的场景该用#LocalTemp - 存储过程异常退出时,
#TempTable会被自动删掉,但显式写DROP TABLE IF EXISTS #tmp更可靠,尤其在反复调试时避免There is already an object named '#tmp' in the database - MySQL 不认
#语法,必须写CREATE TEMPORARY TABLE tmp (...) ON COMMIT DELETE ROWS;PostgreSQL 必须加ON COMMIT DROP,否则临时表会活到会话结束,第二次执行直接报错 - 字段名含空格?
SELECT * INTO #tmp FROM (SELECT [user name] AS [user name] ...) t,否则SELECT INTO会因解析失败中断
NULL 值在中间结果里最容易引发静默错误
临时表/表变量本身不会过滤 NULL,但后续 WHERE status = 'done' 或 JOIN 条件天然排除 NULL,导致中间链断裂——而日志里既没报错也没警告。
- 插入前明确补默认值:
ISNULL(status, 'unknown')或CASE WHEN status IS NULL THEN 'pending' ELSE status END -
JOIN时若关联字段可能为 NULL,改用LEFT JOIN ... ON ISNULL(a.id, -1) = ISNULL(b.id, -1),或提前在临时表里WHERE id IS NOT NULL - 用
SET ANSI_NULLS OFF是饮鸩止渴,SQL Server 2022 已标记为废弃,别碰
最麻烦的不是语法写错,而是把 @Table 当成“更快的 #TempTable”来用——它快在初始化,慢在后续所有查询的执行计划上。数据量一上来,差别就不是毫秒级,是秒级甚至分钟级。

















