6

我在以 2000 兼容模式运行的 SQL 2005 服务器上有一个充满数据的表ItemValue,它看起来像(它是一个用户定义的值表):

ID    ItemCode     FieldID   Value
--    ----------   -------   ------
 1    abc123             1   D
 2    abc123             2   287.23
 4    xyz789             1   A
 5    xyz789             2   3782.23
 6    xyz789             3   23
 7    mno456             1   W
 9    mno456             3   45
                                 ... and so on.

FieldID来自ItemField表:

ID   FieldNumber   DataFormatID   Description   ...
--   -----------   ------------   -----------
 1             1              1   Weight class
 2             2              4   Cost
 3             3              3   Another made up description
 .             .              x   xxx
 .             .              x   xxx
 .             .              x   xxx
 x             91  (we have 91 user-defined fields)

因为我不能在 2000 模式下 PIVOT,所以我们被困在使用 CASE 和 GROUP BY 构建一个丑陋的查询来获取数据以查看它应该如何用于某些遗留应用程序,即:

ItemNumber   Field1   Field2    Field3 .... Field51
----------   ------   -------   ------
    abc123   D        287.23    NULL
    xyz789   A        3782.23   23
    mno456   W        NULL      45

你可以看到我们只需要这个表来显示直到第 51 个 UDF 的值。这是查询:

SELECT
    iv.ItemNumber,
    ,MAX(CASE WHEN f.FieldNumber = 1 THEN iv.[Value] ELSE NULL END) [Field1]
    ,MAX(CASE WHEN f.FieldNumber = 2 THEN iv.[Value] ELSE NULL END) [Field2]
    ,MAX(CASE WHEN f.FieldNumber = 3 THEN iv.[Value] ELSE NULL END) [Field3]
        ...
    ,MAX(CASE WHEN f.FieldNumber = 51 THEN iv.[Value] ELSE NULL END) [Field51]
FROM ItemField f
LEFT JOIN ItemValue iv ON f.ID = iv.FieldID
WHERE f.FieldNumber <= 51
GROUP BY iv.ItemNumber

FieldNumber约束 <= 51 时,执行计划类似于:

SELECT <== Computer Scalar <== Stream Aggregate <== Sort (Cost: 70%) <== Hash Match <== (Clustered Index Seek && Table Scan)

而且速度很快!我可以在大约一秒钟内拉回 100,000+ 条记录,这符合我们的需求。

但是,如果我们有更多的 UDF,并且我将约束更改为66以上的任何值(是的,我对它们进行了逐个测试),或者如果我完全删除它,我会丢失执行计划中的排序,它会被一大堆替换并行块收集、重新分区和分发流,整个过程很慢(即使只有 1 条记录也需要 30 秒)。

FieldNumber有一个聚集的唯一索引,并且是ItemField表中具有ID列(非聚集索引)的复合主键的一部分。ItemValue表的IDItemNumber列构成一个 PK, ItemNumber列上有一个额外的非聚集索引。

这背后的原因是什么?为什么更改我的简单整数约束会更改整个执行计划?

如果你能做到……你会做些什么不同的事情?从现在开始有几个月的 SQL 升级计划,但我需要在此之前解决这个问题。

4

3 回答 3

5

SQL Server足够聪明,可以CHECK在优化查询时考虑约束。

f.FieldNumber <= 51已优化,优化器看到应该连接整个两个表(最好使用 a 完成HASH JOIN)。

如果您没有约束,引擎需要检查条件并且很可能使用索引遍历来执行此操作。这可能会更慢。

能否请发布查询的整个计划?只需运行SET SHOWPLAN_TEXT ON然后查询。

更新:

这背后的原因是什么?为什么更改我的简单整数约束会更改整个执行计划?

如果通过约束你的意思是WHERE条件,这可能是另一回事。

集合操作(​​就是SQL这样)没有一个最有效的算法:每个算法的效率很大程度上取决于集合中的数据分布。

比如说,为了获取一个子集(这就是WHERE子句的作用),您可以在索引中找到记录范围并使用索引记录指针来定位表中的数据行,或者只扫描表中的所有记录并过滤它们使用WHERE条件。

前者操作的效率为m × const,后者的效率为n,其中m为满足条件n的记录数, 为表中的记录总数,const > 1

这意味着对于较大的m全扫描值更有效。

SQL Server意识到这一点,并根据影响集合操作中数据分布的常量相应地更改执行计划。

为此,SQL Server维护统计信息:每个索引列中数据分布的聚合直方图,并使用它们来构建查询计划。

因此,更改WHERE条件中的整数实际上会影响底层集合的大小和数据分布,并SQL Server重新考虑最适合该大小和布局的集合的算法。

于 2010-03-05T17:20:53.483 回答
0

在 66 岁时,您达到了一些内部成本估算阈值,该阈值决定使用一个计划而不是另一个计划。该阈值是什么以及为什么会发生并不重要。请注意,您的查询因每个 FieldNumber 值而异,因为您不仅更改 WHERE:您还更改了伪“枢轴”投影字段。

现在我不知道您的表和查询以及插入/更新/删除/模式的所有详细信息,但是对于您发布的 ItemValue 表的正确聚集索引结构的特定查询是这样的:

CREATE CLUSTERED INDEX  [cdxItemValue] ON ItemValue (FieldID, ItemNumber);

这种结构消除了对这个“枢轴”查询的结果进行中间排序的需要。

于 2010-03-05T19:12:30.187 回答
0

它被一大堆并行块取代

尝试这个:

SELECT
    iv.ItemNumber,
    ,MAX(CASE WHEN f.FieldNumber = 1 THEN iv.[Value] ELSE NULL END) [Field1]
    ,MAX(CASE WHEN f.FieldNumber = 2 THEN iv.[Value] ELSE NULL END) [Field2]
    ,MAX(CASE WHEN f.FieldNumber = 3 THEN iv.[Value] ELSE NULL END) [Field3]
        ...
    ,MAX(CASE WHEN f.FieldNumber = 51 THEN iv.[Value] ELSE NULL END) [Field51]
FROM ItemField f
LEFT JOIN ItemValue iv ON f.ID = iv.FieldID
WHERE f.FieldNumber <= 51
GROUP BY iv.ItemNumber
OPTION (Maxdop 1)

通过使用 Option(Maxdop 1),这应该可以防止执行计划中的并行性。

于 2010-03-05T17:55:47.060 回答