sql-server – 为什么表变量强制进行索引扫描,而临时表使用查找和书签查找?

我试图理解为什么使用表变量阻止优化器使用索引查找然后书签查找与索引扫描.

填充表格:

CREATE TABLE dbo.Test 
(
    RowKey INT NOT NULL PRIMARY KEY,SecondColumn CHAR(1) NOT NULL DEFAULT 'x',ForeignKey INT NOT NULL 
) 

INSERT dbo.Test 
(
    RowKey,ForeignKey
) 
SELECT TOP 1000000 
    ROW_NUMBER() OVER (ORDER BY (SELECT 0)),ABS(CHECKSUM(NEWID()) % 10)     
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2 

CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey)

使用单个记录填充表变量,并尝试通过搜索外键列来查找主键和第二列:

DECLARE @Keys TABLE (RowKey INT NOT NULL) 

INSERT @Keys (RowKey) VALUES (10)

SELECT 
    t.RowKey,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey

以下是执行计划:

现在使用临时表的相同查询

CREATE TABLE #Keys (RowKey INT NOT NULL) 

INSERT #Keys (RowKey) VALUES (10) 

SELECT 
    t.RowKey,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    #Keys k
ON
    t.ForeignKey = k.RowKey

查询计划使用搜索和书签查找:

为什么优化器愿意使用临时表进行书签查找,而不是表变量呢?

在此示例中使用表变量来表示通过存储过程中的用户定义表类型传递的数据.

我意识到如果外键值发生了数十万次,索引搜索可能不合适.在这种情况下,扫描可能是更好的选择.对于我创建的场景,没有值为10的行.我仍然认为行为很有趣,并且想知道它是否有原因.

SQL Fiddle

添加OPTION(RECOMPILE)并未改变行为. UDDT有一个主键.

@@ VERSION是sql Server 2008 R2(SP2) – 10.50.4042.0(X64)(Build 7601:Service Pack 1)(管理程序)

解决方法

行为的原因是sql Server无法确定与ForeignKey匹配的行数,因为没有带有RowKey的索引作为前导列(它可以从#temp表的统计信息中推断出这一点,但那些不是对于表变量/ UDTT存在,因此它估计100,000行,使用扫描比搜索查找更好地处理.到sql Server实现时只有一行,为时已晚.

您可能能够以不同方式构建UDTT;在更现代的sql Server版本中,您可以在表变量上创建二级索引,但在2008 R2中不提供此语法.

顺便说一句,如果你试图通过暗示嵌套循环连接来避免位图/探测,你可以获得搜索行为(至少在我的有限试验中):

DECLARE @Keys TABLE (RowKey INT PRIMARY KEY); -- can't hurt

INSERT @Keys (RowKey) VALUES (10);

SELECT 
     t.RowKey,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey
    OPTION (LOOP JOIN);

我几年前的learned this trick from Paul White.当然,您应该注意在生产代码添加任何类型的连接提示 – 如果人们对底层对象进行更改并且特定类型的连接不再可能或不再是最佳连接,则可能会失败.

对于更复杂的查询,当您迁移到sql Server 2012或更高版本时,trace flag 2453可能会有所帮助.但是,这个标志对这个简单的连接没有帮助.同样的免责声明也适用 – 这只是一个替代方案,如果没有大量的文档和严格的回归测试程序,通常不应该这样做.

此外,Service Pack 1很长时间不受支持,您应该在Service Pack 3 MS15-058上联系.

相关文章

SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_n...
if not exists(select name from syscolumns where name=&am...
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_n...
要在 SQL Server 2019 中设置定时自动重启,可以使用 Window...
您收到的错误消息表明数据库 'EastRiver' 的...
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标...