表变量必须用DECLARE声明结构、INSERT INTO插入数据,不支持SELECT...INTO或TRUNCATE;无统计信息,大数据量易导致执行计划错误;作用域限于当前批处理,不可跨EXEC访问;实际存储仍依赖tempdb,并非纯内存。

表变量声明和基本插入必须用 DECLARE 和 INSERT INTO
表变量不是临时对象,不能用 CREATE TABLE 建,也不能用 SELECT ... INTO 生成。必须显式声明结构,再逐条或批量插入。
常见错误是写成:SELECT * INTO @t FROM 表 —— 这会报错,因为 @t 是变量名,不是对象名,SQL Server 不支持这种语法。
-
DECLARE @t TABLE (id INT, name NVARCHAR(50))是唯一合法声明方式 - 插入单行:
INSERT INTO @t VALUES (1, 'a') - 插入多行结果集:
INSERT INTO @t SELECT id, name FROM 表 WHERE ... - 不支持
SELECT ... INTO @t、TRUNCATE TABLE @t、ALTER TABLE @t
表变量没有统计信息,大结果集下执行计划容易出错
SQL Server 不为表变量维护行数、列分布等统计信息,优化器默认按「1 行」估算,导致 JOIN 或 WHERE 条件走错索引,甚至选错嵌套循环而非哈希匹配。
比如你插入了 10 万行到 @t,但后续 JOIN @t ON ... 仍被当成小表处理,CPU 和逻辑读可能暴增。
- 小数据量(
- 中到大数据量(> 1000 行):优先考虑
#t临时表,它有统计信息、可建索引、支持UPDATE STATISTICS - 若坚持用表变量且数据量大,可在关键查询前加
OPTION (RECOMPILE),让优化器重编译并感知实际行数
表变量作用域仅限当前批处理,不能跨 EXEC 或动态 SQL 访问
这是最常踩的坑:在存储过程中定义了 @t,然后调用 EXEC('SELECT * FROM @t'),会报错「必须声明表变量 "@t"」。
原因:动态 SQL 在独立作用域执行,看不到外层声明的表变量。
- 临时表
#t可以跨EXEC访问(只要没超出会话生命周期) - 表变量无法被子过程、触发器、动态 SQL 引用
- 替代方案:改用临时表;或把逻辑内联进主批处理,避免拆分
表变量也走 tempdb,不是纯内存结构
很多人误以为 @t 一定在内存里 —— 实际上只要数据量超过内存缓存能力,SQL Server 一样把它刷到 tempdb 数据文件,和 #t 没本质区别。
你看到的「逻辑读 0」只是缓存命中,并不代表没落地。当并发高、tempdb 压力大时,表变量和临时表都会争抢 tempdb 的 PFS、GAM 页面,引发闩锁等待。
- 监控
sys.dm_db_task_space_usage可查各会话在tempdb的分配页数,@t和#t都会出现在这里 - 不要因名字带「变量」就忽略
tempdb容量与日志增长风险 - 如果存储过程高频使用大表变量,建议检查
tempdb文件是否均布、是否启用了延迟持久化事务(对某些场景有帮助)
表变量看着轻量,但它的隐性成本藏在执行计划、作用域边界和 tempdb 争用里。真正影响性能的,往往不是“用了没用”,而是“该用多大、在哪用、怎么让优化器信得过”。

















