本地临时表(#table)仅对创建它的会话可见,断开即销毁;全局临时表(##table)对所有会话可见,但仅在最后一个引用它的会话结束时自动删除。

本地临时表(#table)天生就不可跨会话
因为 SQL Server 明确规定:以单个 # 开头的本地临时表,作用域仅限于**创建它的会话(session)**。这不是权限或配置问题,而是设计契约——每个会话在 tempdb 中拥有独立的命名空间,#t 在会话 A 和会话 B 里是两个完全无关的对象。
常见错误现象包括:
- 会话 A 执行
CREATE TABLE #tmp (id INT)后,会话 B 执行SELECT * FROM #tmp报错:Invalid object name '#tmp' - 哪怕用
sp_executesql或链接服务器调用,也无法让另一个会话“看到”当前会话的#表
全局临时表(##table)能跨会话,但有严格前提
## 表确实对所有会话可见,但它的生命周期不是“创建即永久”,而是绑定到**最后一个引用它的会话结束时**。这意味着:
- 会话 A 创建
##shared,会话 B 能立刻SELECT它 - 但如果会话 A 断开连接,而会话 B 还没执行任何操作,
##shared仍存在 - 一旦会话 B 也断开,且没有其他会话正在使用它,SQL Server 就立即删除该表
- 若会话 B 执行了
DROP TABLE ##shared,则会话 A 再查就会报错:Invalid object name '##shared'
动态 SQL 中创建的 ## 表名不能拼接
很多人想用参数生成表名,比如 EXEC('CREATE TABLE ##' + @suffix + ' (x INT)'),这会直接失败——SQL Server 解析器拒绝把拼接出的字符串识别为合法的全局临时表名。
正确做法只有两种:
- 硬编码表名:
DECLARE @sql NVARCHAR(MAX) = 'CREATE TABLE ##fixed_name (x INT)'; EXEC sp_executesql @sql; - 若必须参数化,只能拼接中间部分,并严格校验输入:
'##temp_' + REPLACE(@user_input, '''', ''''''),否则有注入风险
连接池会让 ## 表“意外残留”
在应用使用连接池(如 .NET 的 SqlConnection Pool)时,一个物理连接可能被多个逻辑请求复用。如果上一次请求建了 ##cache_123 但没删,下一次请求从池里拿到这个连接,再执行 CREATE TABLE ##cache_123 就会报错:There is already an object named '##cache_123' in the database。
所以显式检查并清理比依赖自动销毁更可靠:
- 创建前检查:
IF OBJECT_ID('tempdb..##cache_123') IS NOT NULL DROP TABLE ##cache_123; - 注意:
OBJECT_ID('##cache_123')返回NULL,必须写成OBJECT_ID('tempdb..##cache_123')
最易被忽略的一点:全局临时表的“可见性”不等于“可用性”。会话 B 能看到 ##t 并不意味着它能安全读写——如果会话 A 正在事务中持有排他锁(比如 SELECT * FROM ##t WITH (XLOCK)),会话 B 会被阻塞,直到事务结束。











