我有一个表myTable
,其中包含myGuid
(uniqueidentifier),myValues
(浮点数),myGroup
(整数)和一堆其他不重要的字段现在。我想做一些简单的事情:
SELECT SUM(myValues)
FROM myTable
WHERE myGuid IN (SELECT * FROM ##test)
GROUP BY myGroup
##test
只是一个包含单个字段的临时表(guid_filter
)
包含一堆独特的标识符。
现在这是奇怪的事情:
当我使用myGuid作为主键创建myTable时(看起来如此) 像显而易见的事情一样),查询很慢(< EDIT> 8-12s< / EDIT>)。
当我使用myAutoInc创建myTable时,整数自动增量 字段,作为主键,查询速度很快(~2s),即使是 WHERE子句仍由myGuid过滤。 (myGuid只是一个“正常” 此方案中的非聚集索引。)
这有什么合理的解释吗?我(天真)的假设是 第一个选项更快,因为SQL Server可以使用guid 查找myValues而不是通过guid - > myAutoInc - > myValues。所以,结果对我来说非常令人惊讶。
这是SHOWPLAN_TEXT输出。缓慢的情况(XML query plan):(编辑:更新,感谢Remus注意到myGuid上有一个不必要的额外非聚集索引)
|--Compute Scalar(DEFINE:([Expr1007]=CASE WHEN [Expr1015]=(0) THEN NULL ELSE [Expr1016] END))
|--Stream Aggregate(GROUP BY:([myDB].[dbo].[myTable].[myGroup]) DEFINE:([Expr1015]=COUNT_BIG([myDB].[dbo].[myTable].[myValues]), [Expr1016]=SUM([myDB].[dbo].[myTable].[myValues])))
|--Sort(DISTINCT ORDER BY:([myDB].[dbo].[myTable].[myGroup] ASC, [myDB].[dbo].[myTable].[myGuid] ASC))
|--Nested Loops(Inner Join, OUTER REFERENCES:([tempdb].[dbo].[##test].[guid_filter], [Expr1014]) OPTIMIZED WITH UNORDERED PREFETCH)
|--Table Scan(OBJECT:([tempdb].[dbo].[##test]))
|--Clustered Index Seek(OBJECT:([myDB].[dbo].[myTable].[PK__myTable__2334397B]), SEEK:([myDB].[dbo].[myTable].[myGuid]=[tempdb].[dbo].[##test].[guid_filter]) ORDERED FORWARD)
Table 'myTable'. Scan count 0, logical reads 38046, physical reads 1, read-ahead reads 6914, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '##test'. Scan count 1, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
快速方案(XML query plan):
|--Compute Scalar(DEFINE:([Expr1007]=CASE WHEN [globalagg1009]=(0) THEN NULL ELSE [globalagg1011] END))
|--Stream Aggregate(GROUP BY:([myDB].[dbo].[myTable].[myGroup]) DEFINE:([globalagg1009]=SUM([partialagg1008]), [globalagg1011]=SUM([partialagg1010])))
|--Parallelism(Gather Streams, ORDER BY:([myDB].[dbo].[myTable].[myGroup] ASC))
|--Stream Aggregate(GROUP BY:([myDB].[dbo].[myTable].[myGroup]) DEFINE:([partialagg1008]=COUNT_BIG([myDB].[dbo].[myTable].[myValues]), [partialagg1010]=SUM([myDB].[dbo].[myTable].[myValues])))
|--Sort(DISTINCT ORDER BY:([myDB].[dbo].[myTable].[myGroup] ASC, [myDB].[dbo].[myTable].[myAutoInc] ASC))
|--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([myDB].[dbo].[myTable].[myGroup], [myDB].[dbo].[myTable].[myAutoInc]))
|--Nested Loops(Inner Join, OUTER REFERENCES:([myDB].[dbo].[myTable].[myAutoInc], [Expr1017]) OPTIMIZED WITH UNORDERED PREFETCH)
|--Nested Loops(Inner Join, OUTER REFERENCES:([tempdb].[dbo].[##test].[guid_filter], [Expr1016]) OPTIMIZED WITH UNORDERED PREFETCH)
| |--Table Scan(OBJECT:([tempdb].[dbo].[##test]))
| |--Index Seek(OBJECT:([myDB].[dbo].[myTable].[myGuid]), SEEK:([myDB].[dbo].[myTable].[myGuid]=[tempdb].[dbo].[##test].[guid_filter]) ORDERED FORWARD)
|--Clustered Index Seek(OBJECT:([myDB].[dbo].[myTable].[PK__myTable__2334397B]), SEEK:([myDB].[dbo].[myTable].[myAutoInc]=[myDB].[dbo].[myTable].[myAutoInc]) LOOKUP ORDERED FORWARD)
Table 'myTable'. Scan count 0, logical reads 66988, physical reads 48, read-ahead reads 2515, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '##test'. Scan count 5, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
答案 0 :(得分:3)
GUID是聚集索引的不良选择,仅仅因为它太大了。使用INTEGER字段允许数据库将更多信息打包到“页面”中,因此对于任何给定的查询,需要从磁盘中获取更少的页面。
另请注意,群集密钥存储在每个非聚集索引中(因为这是用于查找数据的内容),这会使问题更加复杂。
答案 1 :(得分:3)
在“慢”的情况下查看计划,它显示查询对索引[myDB].[dbo].[myTable].[myGuid]
进行搜索,然后在[myDB].[dbo].[myTable].[PK__myTable__2334397B]
上进行聚簇索引搜索。这只有在你在myTable(myGuid)上创建了非聚集索引并且还将myTable(myGuid)声明为聚簇索引键时才有意义(看起来是这样,从典型的'PRIMARY KEY'声明自动生成的名称命名约定来看聚集索引对象'PK _...')。
除此之外,计划非常相似,并且它们都非常糟糕,因为它们包括SORT。在第一种情况中,autoInc列的宽度与非聚集索引的潜在较大宽度所涉及的GUID的差异可以解释差异,但我怀疑是完整的故事。
请重新进行测试,确保myGuid上有一个群集密钥,并且您在同一个密钥上也没有非聚集索引。该计划应该只在myTable上使用聚集索引进行一次单一搜索,以比较您想要比较的案例。
另外,显然,请确保比较相同的##测试内容,并且缓冲池缓存在两种情况下均相同。在每次测试之前运行DBCC FREESYSTEMCACHE('All')
然后运行查询至少5次,忽略第一次运行(这将是加热缓冲池的运行)。
另外,正如Arthur已经指出的那样,如果## test内容足够大,那么在## test(即聚簇键)上进行订单保证可以加快速度,因为嵌套循环可以用合并连接替换。如果## temp只有很少的行,那么嵌套循环更好,顺序没有区别。