在表变量上创建索引

Posted

技术标签:

【中文标题】在表变量上创建索引【英文标题】:Creating an index on a table variable 【发布时间】:2010-10-27 12:41:03 【问题描述】:

您可以在 SQL Server 2000 中为表变量创建索引吗?

DECLARE @TEMPTABLE TABLE (
     [ID] [int] NOT NULL PRIMARY KEY
    ,[Name] [nvarchar] (255) COLLATE DATABASE_DEFAULT NULL 
)

我可以在Name 上创建索引吗?

【问题讨论】:

创建这两种临时表都是有成本的;如果你有这么多数据以至于你需要一个索引,那么可能是时候考虑使用一个真实的表了;您设置为交易安全;按 spid 或用户 ID 过滤,然后在最后将其清除。真实表和临时表都有其起伏,但如果性能是一个问题;也可以用一张真正的桌子试试。 临时表“是”一个真正的表,完成后它就会消失。真正的区别(除了它会自动消失)是它在 TempDB 中。当涉及到索引和约束时,这实际上是巨大的,因为您最终可能会出现名称冲突,不仅与您的代码的其他执行有关,而且与在您实例中的其他数据库中执行的代码发生冲突。 @bielawski 这是一个表变量而不是临时表。表变量不允许显式命名的约束,系统生成的名称保证是唯一的。它们确实允许从 2014 年开始使用命名索引,但这不是问题,因为索引只需要在一个对象内而不是跨对象进行唯一命名。 我的观点是 2 倍。 1)除了使用变量来避免事务纠缠之外,临时表和表变量之间没有实质性区别。然而,在 V-2000 中,没有将约束、索引...添加到变量的语法。 2) 如果可以使用临时表,则命名表附件(如索引 WILL)如果使用静态名称,则会与同时执行同一 SP 的副本发生冲突!下面的机制是明确开发的,因为我在这些确切情况下将 SP 故障追溯到命名索引冲突。它们必须是唯一的。 @bielawski - 没有 index 名称不需要在对象之间是唯一的 - 只有约束名称才可以。这是微不足道的测试。只需执行CREATE TABLE #T1(X INT); CREATE TABLE #T2(X INT); CREATE INDEX IX ON #T1(X); CREATE INDEX IX ON #T2(X); 【参考方案1】:

这个问题被标记为 SQL Server 2000,但为了方便在最新版本上开发的人们,我将首先解决这个问题。

SQL Server 2014

除了下面讨论的添加基于约束的索引的方法之外,SQL Server 2014 还允许在表变量声明中使用内联语法直接指定非唯一索引。

示例语法如下。

/*SQL Server 2014+ compatible inline index syntax*/
DECLARE @T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
       INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);

过滤索引和包含列的索引目前无法使用此语法声明,但 SQL Server 2016 进一步放宽了这一点。从 CTP 3.1 开始,现在可以为表变量声明过滤索引。通过 RTM,可能也允许包含列,但当前位置是它们"will likely not make it into SQL16 due to resource constraints"

/*SQL Server 2016 allows filtered indexes*/
DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)

SQL Server 2000 - 2012

我可以在 Name 上创建索引吗?

简短回答:是的。

DECLARE @TEMPTABLE TABLE (
  [ID]   [INT] NOT NULL PRIMARY KEY,
  [Name] [NVARCHAR] (255) COLLATE DATABASE_DEFAULT NULL,
  UNIQUE NONCLUSTERED ([Name], [ID]) 
  ) 

更详细的答案如下。

SQL Server 中的传统表可以具有聚集索引,也可以采用heaps 的结构。

可以将聚集索引声明为唯一以禁止重复键值或默认为非唯一。如果不是唯一的,则 SQL Server 会以静默方式将 uniqueifier 添加到任何重复的键以使它们唯一。

非聚集索引也可以显式声明为唯一的。否则,对于所有索引键(不仅仅是重复项)的非唯一情况 SQL Server adds the row locator(聚集索引键或 RID 堆),这再次确保它们是唯一的。

在 SQL Server 2000 - 2012 中,表变量的索引只能通过创建 UNIQUEPRIMARY KEY 约束来隐式创建。这些约束类型之间的区别在于主键必须在不可为空的列上。参与唯一约束的列可以为空。 (尽管 SQL Server 在存在 NULLs 的情况下实现的唯一约束并不符合 SQL 标准中的规定)。此外,一张表只能有一个主键,但可以有多个唯一约束。

这两个逻辑约束都是用唯一索引物理实现的。如果未明确指定,PRIMARY KEY 将成为聚集索引和非聚集唯一约束,但可以通过使用约束声明明确指定 CLUSTEREDNONCLUSTERED 来覆盖此行为(示例语法)

DECLARE @T TABLE
(
A INT NULL UNIQUE CLUSTERED,
B INT NOT NULL PRIMARY KEY NONCLUSTERED
)

由于上述原因,可以在 SQL Server 2000 - 2012 中的表变量上隐式创建以下索引。

+-------------------------------------+-------------------------------------+
|             Index Type              | Can be created on a table variable? |
+-------------------------------------+-------------------------------------+
| Unique Clustered Index              | Yes                                 |
| Nonunique Clustered Index           |                                     |
| Unique NCI on a heap                | Yes                                 |
| Non Unique NCI on a heap            |                                     |
| Unique NCI on a clustered index     | Yes                                 |
| Non Unique NCI on a clustered index | Yes                                 |
+-------------------------------------+-------------------------------------+

最后一个需要一点解释。在此答案开头的表变量定义中,Name 上的 非唯一 非聚集索引由 Name,Id 上的 唯一 索引模拟(回想一下 SQL Server无论如何都会默默地将聚集索引键添加到非唯一 NCI 键中)。

也可以通过手动添加IDENTITY 列作为唯一符来实现非唯一聚集索引。

DECLARE @T TABLE
(
A INT NULL,
B INT NULL,
C INT NULL,
Uniqueifier INT NOT NULL IDENTITY(1,1),
UNIQUE CLUSTERED (A,Uniqueifier)
)

但这并不是对非唯一聚集索引通常如何在 SQL Server 中实际实现的准确模拟,因为这会将“唯一性”添加到所有行。不仅仅是那些需要它的人。

【讨论】:

注意:2000-2012 解决方案仅在文本列 @AndreFigueiredo 是的,这也是这些版本中永久表上索引键的最大大小。 我只是想指出 SQL 2014 答案在 Azure 中运行良好。谢谢马丁! @CashCow 没有 SQL Server 2018,我也没有在 cmets 中运行临时支持渠道,所以如果您有问题,请作为新问题提出 @CashCow - 答案没有错误。您做错了什么,或者连接到的数据库版本比您认为的要旧得多(或具有一些旧的兼容性级别),但我不会在 cmets 中为您解决问题。您发布的代码中有一个明显的问题,即列名不能以 @ 开头,除非引用【参考方案2】:

应该理解,从性能的角度来看,@temp 表和支持变量的#temp 表之间没有区别。它们驻留在同一个地方 (tempdb) 并以相同的方式实现。所有差异都出现在附加功能中。请参阅这篇令人惊讶的完整文章:https://dba.stackexchange.com/questions/16385/whats-the-difference-between-a-temp-table-and-table-variable-in-sql-server/16386#16386

虽然在某些情况下无法使用临时表,例如在表或标量函数中,但对于 v2016 之前的大多数其他情况(甚至可以将过滤索引添加到表变量中),您可以简单地使用 #临时表。

在 tempdb 中使用命名索引(或约束)的缺点是名称可能会发生冲突。不仅在理论上适用于其他过程,而且通常很容易与过程本身的其他实例一起尝试在其 #temp 表的副本上放置相同的索引。

为避免名称冲突,通常可以使用以下方法:

declare @cmd varchar(500)='CREATE NONCLUSTERED INDEX [ix_temp'+cast(newid() as varchar(40))+'] ON #temp (NonUniqueIndexNeeded);';
exec (@cmd);

这确保了名称始终是唯一的,即使在同一过程的同时执行之间也是如此。

【讨论】:

命名索引没有问题 - 索引只需在表中唯一命名。问题在于命名约束,最好的解决方案通常是不在临时表中命名它们 - 命名约束会阻止临时表对象缓存。 这必须仅适用于某些版本(如果适用于任何版本)。我不得不特别想出这个解决方法,因为我在同时执行期间将 sp 失败追溯到命名索引的冲突。 @bielawski 您使用的是 2016 年吗?我很好奇临时表上的命名索引是否会给并发环境带来风险。 @Elaskanator 是的,他们是。我们在负载下发现 SQL 元数据表中的争用,删除索引的名称解决了问题。这是 SQL 2016。 在存储过程中处理表变量与临时表时,实际上存在显着的性能差异。临时表受到与物理表类似的重新编译阈值的影响,而表变量提供了一种不受重新编译阈值影响的机制。在较大的程序中,这可能会产生巨大的差异。【参考方案3】:

如果表变量有大数据,那么代替表变量(@table)创建临时表(#table)。表变量不允许在插入后创建索引。

 CREATE TABLE #Table(C1 int,       
  C2 NVarchar(100) , C3 varchar(100)
  UNIQUE CLUSTERED (c1) 
 ); 

    创建具有唯一聚集索引的表

    将数据插入临时“#Table”表

    创建非聚集索引。

     CREATE NONCLUSTERED INDEX IX1  ON #Table (C2,C3);
    

【讨论】:

在插入语句后创建索引以避免不必要的排序 请注意,如果表变量在函数中,则这是不可能的

以上是关于在表变量上创建索引的主要内容,如果未能解决你的问题,请参考以下文章

Oracle创建索引SQL简单的例子,在表中的指定字段和如何使用索引呢?

Oracle创建索引SQL简单的例子,在表中的指定字段和如何使用索引呢?

在表或视图上设置索引?

数据库怎样创建一个唯一聚集索引

Oracle创建索引SQL简单的例子,在表中的指定字段和如何使用索引呢?

mysql创建索引