优化 SSIS 数据流任务中使用的 XQuery-heavy SQL 查询

Optimizing a XQuery-heavy SQL query used in SSIS data flow task

此查询用于每晚将大约 600,000 行导入数据仓库中的目标 table。目标 table 在每次导入前被截断。

SELECT -- Around 70 fields from MainTable which contains around 600,000 rows
-- Around 150 fields from around 50 various tables, some quite big
-- Around 35 fields from XQuery derived table queries such as dt_EXTERNAL_CODE1
FROM MainTable
LEFT JOIN -- Around 50 tables
LEFT JOIN
(
SELECT df.ParentID,
ISNULL(df2.XMLValue.value('(Item/*[local-name()="CustomData"]/root/A/Number)[1]', 'float'),0) AS a, 
ISNULL(df2.XMLValue.value('(Item/*[local-name()="CustomData"]/root/B/Number)[1]', 'float'),0) AS b,
ISNULL(df2.XMLValue.value('(Item/*[local-name()="CustomData"]/root/C/Number)[1]', 'float'),0) AS c,
ISNULL(df2.XMLValue.value('(Item/*[local-name()="CustomData"]/root/D/Number)[1]', 'float'),0) AS d,
ISNULL(df2.XMLValue.value('(Item/*[local-name()="CustomData"]/root/E/Number)[1]', 'float'),0) AS e,
ISNULL(df2.XMLValue.value('(Item/*[local-name()="CustomData"]/root/F/Number)[1]', 'float'),0) AS f
FROM DynamicField df
INNER JOIN DynamicField df1 ON df.DynamicFieldID = df1.DynamicFieldID
INNER JOIN DynamicField df2 ON df1.DynamicFieldID = df2.ParentID
WHERE df2.XMLValue.value('(Item/*[local-name()="ExternalCode"])[1]', 'nvarchar(50)') IN('EXTERNAL_CODE1')
) dt_EXTERNAL_CODE1 ON MainTable.DynamicFieldID = dt_EXTERNAL_CODE1.ParentID
LEFT JOIN -- 6 more like the derived table query above, but with some other external code

SSIS 导入作业大约需要 10 个小时才能完成。关于如何优化此查询的任何建议?联接不能是内部联接。

我只想提一下,这个建议没有考虑可能添加到数据库中 XML 的索引。根据您未显示的其他六个查询和许多其他因素,索引 XML 数据可能也是一件好事。我在这里给出的建议实际上只是适用于几乎所有 X 查询表达式的通用 X 查询建议。

还值得注意的是,在 SQL 数据库中存储和使用 XML 像这样过滤以及关系数据是一个坏主意,尤其是当您计划使用它进行大规模 ETL 解决方案时数据。正如您已经经历过的那样,这将是一件麻烦事。如果您仍处于可以更改的阶段,我强烈建议您这样做。

除此之外,还有一些建议:

首先,应重写过滤器表达式 WHERE df2.XMLValue.value('(Item/*[local-name()="ExternalCode"])[1]', 'nvarchar(50)') IN('EXTERNAL_CODE1') 以使用 exist 运算符(Microsoft 文档 here). Per Microsoft (here):

For performance reasons, instead of using the value() method in a predicate to compare with a relational value, use exist() with sql:column()

其次,我会将新创建的 exist 表达式移动到连接子句而不是 WHERE 子句。当我查看此查询时,优化器可能会在实际执行连接之前将您的过滤器应用于整个 DynamicField df2 table。根据这些连接的基数,这可能会对性能产生不利影响。我想你希望这个过滤器只对从你的表达式 FROM DynamicField df INNER JOIN DynamicField df1 ON df.DynamicFieldID = df1.DynamicFieldID 返回的行执行。这里的要点是 减少将要使用任何 XML 过滤的记录数量将对性能有很大帮助

第三,每次调用 value() 都会实例化一个新的 XML reader,它需要遍历路径 (Item/*[local-name()="CustomData"]/root/D/Number)。减少 XML reader 的每个实例为检索 SELECT 所需的值而必须执行的工作量将大大提高性能。如果你有一个重复的路径你正在遍历(就像你的例子)你可能会更好地使用额外的 OUTER APPLY 运算符来调用 query 来检索 XML 元素 root 作为一个单独的节点,然后在最终 SELECTvalue 语句中使用该新节点。像这样:

SELECT 
   df.ParentID
    ,ISNULL(root.RootXmlFrag.value('(root/A/Number)[1]', 'float'),0) AS a
    ...... 
FROM 
    DynamicField df
    INNER JOIN DynamicField df1 ON df.DynamicFieldID = df1.DynamicFieldID
    INNER JOIN DynamicField df2 ON df1.DynamicFieldID = df2.ParentID
    OUTER APPLY df2.XMLValue.query('(Item/*[local-name()="CustomData"]/root)[1]') AS root(RootXmlFrag)

实际的最终 query 路径表达式可能会有所不同,但考虑到您不想为每个 value 表达式遍历 (Item/*[local-name()="CustomData"]/root/D/Number) 等复杂路径的想法最终肯定会对性能有帮助。