优化 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
作为一个单独的节点,然后在最终 SELECT
的 value
语句中使用该新节点。像这样:
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)
等复杂路径的想法最终肯定会对性能有帮助。
此查询用于每晚将大约 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
作为一个单独的节点,然后在最终 SELECT
的 value
语句中使用该新节点。像这样:
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)
等复杂路径的想法最终肯定会对性能有帮助。