数据量小于100行时优先用@table_variable,因其启动快、无日志开销、不触发重编译;超1000行或需多次join时必须用#temp_table,因其有真实统计信息和索引支持。

数据量小于100行时优先用@table_variable
SQL Server 对表变量默认按“1行”估算统计信息,数据越少,这个误判影响越小。100行以内只读、单次使用(比如存几条配置或中间计算结果),@t 启动快、无日志开销、不触发存储过程重编译。
- 别指望它走索引——只能靠主键/唯一约束隐式建索引,且无法手动加非聚集索引
- 不能跨
BEGIN...END块复用,更不能被嵌套存储过程访问 -
INSERT INTO @t SELECT ...没问题,但SELECT ... INTO @t语法非法,会报错
数据量超1000行或需多次JOIN时必须用#temp_table
一旦行数上到千级,表变量会让优化器持续误判为“1行”,强制选择嵌套循环连接,JOIN大表时性能断崖下跌。临时表有真实统计信息,能支持合理执行计划。
- 建索引必须在
INSERT之前:CREATE TABLE #t (...); CREATE INDEX IX_x ON #t(x); INSERT INTO #t SELECT ... - 避免对
IDENTITY列再建聚集索引——默认已是聚集,重复建等于浪费资源 - WHERE条件含多个字段时,优先组合索引,例如
WHERE status = ? AND created_at > ?→ 建(status, created_at)
需要跨作用域复用或嵌套调用时只能选#temp_table
表变量作用域严格限于当前批处理,存储过程嵌套时,内层过程根本看不到外层声明的@t;而#t在会话级可见,嵌套调用可直接读写,且值不会被覆盖。
- 全局临时表
##t适用于服务启动后需多会话共享的场景(如自动收集登录用户),但要注意命名冲突和清理时机 - 本地临时表
#t在存储过程退出时自动销毁,无需DROP,但显式DROP可提前释放资源 - 别在循环里反复
CREATE TABLE #t——每次创建都触发物理操作和重编译,高并发下易成瓶颈
SELECT INTO在循环里是危险操作
看似简洁的SELECT col INTO @var FROM t WHERE id = ?在循环中每执行一次,就触发一次独立查询+行锁,高并发下极易卡死。更糟的是@var是会话级变量,嵌套调用时值可能被意外覆盖。
- 批量提取改用
CREATE TABLE #cache AS SELECT ...(SQL Server用SELECT ... INTO #cache) - 单值需求优先走聚合:
SELECT MAX(col) FROM t WHERE ...,让优化器有机会走索引覆盖 - 游标循环内别做
INTO赋值再计算——把逻辑全下推到SELECT子句,例如SELECT id, price * tax_rate AS final_price
实际选型时最容易被忽略的点:索引创建时机和作用域边界。很多人写了CREATE INDEX却放在INSERT之后,结果索引白建;也常因没意识到表变量跨EXEC就失效,导致嵌套逻辑出错。










