在 JOIN 条件中使用 GUID 字符串会扰乱执行时间

Using GUID string in JOIN condition messes up execution time

我发现了一个奇怪的行为。给定以下查询:

SELECT * FROM foo
JOIN bar ON bar.id = foo.bar_id AND bar.other_id = '00000000-0000-0000-0000-000000000000'

我服务器上的执行时间:~120 毫秒,读取:~125000

当我这样重写查询时:

DECLARE @other_id uniqueidentifier = '00000000-0000-0000-0000-000000000000'
SELECT * FROM foo
JOIN bar ON bar.id = foo.bar_id AND bar.other_id = @other_id

执行时间:~6ms,读取:~140

谁能解释一下为什么第一个查询这么慢?我假设 GUID 字符串在查询中一遍又一遍地转换,但我希望 SQL 服务器足够聪明,不会这样做。

造成差异的可能原因是执行计划不同,而不是字符串的重复转换。更高的读数表明了这一点。比较计划以验证。

一般来说,SQL服务器在编译期间知道实际值时会更准确地估计行数并生成一个好的计划。对于变量,估计是基于平均值而不是统计直方图。尝试更新统计信息并再次 运行 第一个查询,因为这可能是统计信息过时的症状。

第一个select先执行JOIN,这里可能找到了很多符合JOIN条件的行。 AND 之后的第二个条件将此结果限制为一行。因此,几乎完全读取了两个 tables。

第二个 select 有两个条件寻找定义为变量的单值常量。如果每个 table 的 id 是一个或两个 table 的主键,这将特别快。

我已经成功地重现了您的问题。丹·古兹曼 (+1) 是对的。这是正在发生的事情的演示:

使用以下代码创建 table 和数据集:

CREATE TABLE dbo.Test(ID INT IDENTITY (1,1),Val UNIQUEIDENTIFIER)
GO
INSERT INTO dbo.Test
SELECT NEWID() FROM sys.columns
GO 30
INSERT INTO dbo.Test
SELECT TOP 1000 Val FROM dbo.Test
GO 30

CREATE UNIQUE CLUSTERED INDEX idx ON dbo.Test(ID)
CREATE NONCLUSTERED INDEX idx2 ON dbo.Test(Val)
GO

现在看看你的统计数据:

DBCC SHOW_STATISTICS ('dbo.test',IDX2)

在 EQ_ROWS 中你会看到整数(与 Hi 键匹配的重复项计数),在 AVG_RANGE_ROWS 中你会看到小数平均值(每个不同键的平均行数)

运行 下一个语句来识别重复或不重复的键。

SELECT Val, COUNT(*) FROM dbo.Test GROUP BY Val HAVING COUNT(*) = 1
SELECT Val, COUNT(*) FROM dbo.Test GROUP BY Val HAVING COUNT(*) > 1

如果您 运行 下面的代码带有 Non-Unique [Val],那么执行计划将是相同的,因为 SQL 生成基于 AVG_RANGE_ROWS 的计划。

如果您 运行 带有唯一 [Val] 的代码,则计划略有不同,估计的行数发生变化,带有参数的查询将使用 AVG_RANGE_ROWS,带有硬编码值将使用 EQ_ROWS。这种差异可以使优化器在更复杂的环境中生成完全不同的计划。

DECLARE @r UNIQUEIDENTIFIER = 'CE043987-62B5-4AA6-9BE7-0005F2B54A24' 

SELECT * FROM dbo.Test WHERE Val = @r

SELECT * FROM dbo.Test WHERE Val = 'CE043987-62B5-4AA6-9BE7-0005F2B54A24'