是否可以向临时表添加索引? create #t 和 declare @t 有啥区别
Posted
技术标签:
【中文标题】是否可以向临时表添加索引? create #t 和 declare @t 有啥区别【英文标题】:Is it possible to add index to a temp table? And what's the difference between create #t and declare @t是否可以向临时表添加索引? create #t 和 declare @t 有什么区别 【发布时间】:2011-09-17 03:12:27 【问题描述】:我需要做一个非常复杂的查询。 在某一时刻,这个查询必须连接到一个无法被索引的视图。 这个视图也是一个连接大表的复杂视图。
View 的输出可以简化为:
PID (int), Kind (int), Date (date), D1,D2..DN
其中 PID 和 Date 和 Kind 字段不是唯一的(可能有不止一行具有相同的 pid、kind、date 组合),而是像这样在 join 中使用的那些
left join ComplexView mkcs on mkcs.PID=q4.PersonID and mkcs.Date=q4.date and mkcs.Kind=1
left join ComplexView mkcl on mkcl.PID=q4.PersonID and mkcl.Date=q4.date and mkcl.Kind=2
left join ComplexView mkco on mkco.PID=q4.PersonID and mkco.Date=q4.date and mkco.Kind=3
现在,如果我这样做,查询的执行将花费大量时间,因为我假设复杂视图运行了 3 次,并且在其大量行中只有一些实际使用(例如,在 40000仅使用了 2000 个)
我所做的是声明@temptable,然后插入@temptable select * from ComplexView where Date... - 每个查询一次我只从我的 ComplexView 中选择我要使用的行,然后我加入这个@temptable。
这大大减少了执行时间。
但是,我注意到,如果我在我的数据库中创建一个表,并在 PID、Kind、Date(非唯一聚集)上添加一个聚集索引并从该表中获取数据,然后从该表中删除 * 并从复杂视图插入此表需要几秒钟(3 或 4 秒),然后在我的查询中使用此表(左加入 3 次)将查询时间缩短到一半,从 1 分钟到 30 秒!
所以,我的问题是,首先 - 是否可以在声明的 @temptables 上创建索引。 然后 - 我看到人们谈论“创建#temptable”语法。也许这就是我需要的?我在哪里可以了解声明 @temptable 和创建 #temptable 之间的区别?我应该用什么来做像我这样的查询? (如果重要,此查询适用于 MS Reporting Services 报告)。
【问题讨论】:
【参考方案1】:#tablename
是一个物理表,存储在tempdb
中,当创建它的连接关闭时服务器将自动删除,@tablename
是一个存储在内存中的表,并在批处理/过程的整个生命周期内都存在创建它,就像一个局部变量。
您只能向#temp
表添加(非PK)索引。
create table #blah (fld int)
create nonclustered index idx on #blah (fld)
【讨论】:
这是否意味着如果两个人同时运行查询,并且此查询使用临时表,他们将访问相同的#tablename 但不同的@tablenames,因此在脚本中使用#tablename 进行临时查询不安全?换句话说,当同时运行两个查询时会发生什么,每个查询都有具有相同#temptable 标识符的create 语句?他们每个人都有自己的#temptable,还是都访问同一个表? 啊,我在下面的链接中找到了答案 @DanzaiVer 链接来自已删除的答案; sqlteam.com/article/… 表变量不一定“存储在内存中”。它们的处理方式与 #temp 表一样:它们将在内存中开始,但随着它们的增长被假脱机到 tempdb 中的物理磁盘。主要区别在于语法和范围(表变量的范围仅限于创建它们的批处理/函数/过程:#temp 表到会话。【参考方案2】:这不是一个完整的答案,但 #table 会创建一个您需要删除的临时表,否则它将保留在您的数据库中。 @table 是一个表变量,不会比您的脚本持续更长时间。
另外,我认为这篇文章将回答您问题的另一部分。
Creating an index on a table variable
【讨论】:
服务器连接断开后临时表将消失,仅在该连接范围内。并不是说丢弃不再需要的东西是一个坏习惯,只是没有必要。 有趣,我没有意识到这一点。感谢您的澄清。 其实是必须的。例如,当我在 sql management studio 中运行查询时,它不会关闭会话,因此 create 语句会给出一个错误,即对象已经存在!所以我认为最好手动删除它。网络上的文章也提出了建议。 即使您实际上不需要放弃,但此答案中的链接有一个链接可以直接回答我 100% 的问题,所以我将其标记为答案 抱歉,我的措辞与太多的否定词混淆了。这是一个好习惯;只是不需要。【参考方案3】:是的,您可以在临时表或表变量上创建索引。 http://sqlserverplanet.com/sql/create-index-on-table-variable/
【讨论】:
【参考方案4】:@tableName
语法是一个表变量。它们相当有限。语法在documentation for DECLARE @local_variable
中描述。您可以对表变量进行索引,但只能通过在列上指定 PRIMARY KEY
和 UNIQUE
约束来间接地进行。因此,如果您需要索引的列中的数据恰好是唯一的,您可以这样做。见this answer。对于许多用例来说,这可能“足够”,但仅适用于少量行。如果您的表变量没有索引,优化器通常会将表变量视为包含一行(无论实际有多少行),如果您有数百或数千行,这可能会导致糟糕的查询计划取而代之。
#tableName
语法是一个本地范围的临时表。您可以使用SELECT…INTO #tableName
或CREATE TABLE #tableName
语法创建这些。这些表的范围比变量的范围要复杂一些。如果您在存储过程中有CREATE TABLE #tableName
,则该存储过程中对#tableName
的所有引用都将引用该表。如果您只是在存储过程中引用#tableName
(不创建它),它将查看调用者的范围。所以你可以在一个过程中创建#tableName
,调用另一个过程,然后在另一个过程中读取/更新#tableName
。但是,一旦创建#tableName
的过程运行完成,SQL Server 将自动取消引用并清理该表。因此,没有理由手动清理这些表,除非您有一个旨在无限期或长时间循环/运行的过程。
在大多数情况下,您可以在临时表上定义复杂的索引,就像它们是永久表一样。因此,如果您需要对列进行索引但有重复的值会阻止您使用UNIQUE
,这就是要走的路。您甚至不必担心索引上的名称冲突。如果您在多个会话中运行类似CREATE INDEX my_index ON #tableName(MyColumn)
的内容,每个会话都创建了自己的名为#tableName
的表,SQL Server 将执行some magic,这样全局标识符my_index
的重用就不会爆炸。
此外,临时表会像普通表一样自动构建统计信息等。查询优化器将认识到临时表中可以包含不止 1 行,这本身就可以比表变量带来巨大的性能提升。当然,这也是很小的开销。尽管如果查询的运行时间超过一秒,这种开销可能是值得的,而且不会引起注意。
【讨论】:
【参考方案5】:要扩展 Alex K. 的答案,您可以在临时表上创建 PRIMARY KEY
IF OBJECT_ID('tempdb..#tempTable') IS NOT NULL
DROP TABLE #tempTable
CREATE TABLE #tempTable
(
Id INT PRIMARY KEY
,Value NVARCHAR(128)
)
INSERT INTO #tempTable
VALUES
(1, 'first value')
,(3, 'second value')
-- will cause Violation of PRIMARY KEY constraint 'PK__#tempTab__3214EC071AE8C88D'. Cannot insert duplicate key in object 'dbo.#tempTable'. The duplicate key value is (1).
--,(1, 'first value one more time')
SELECT * FROM #tempTable
【讨论】:
以上是关于是否可以向临时表添加索引? create #t 和 declare @t 有啥区别的主要内容,如果未能解决你的问题,请参考以下文章