SUM / GROUP性能和主键

时间:2009-11-09 19:49:53

标签: sql-server performance

我有一个表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.

2 个答案:

答案 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只有很少的行,那么嵌套循环更好,顺序没有区别。