除了使用临时表之外,有没有办法在单独的步骤中过滤/排序?
Posted
技术标签:
【中文标题】除了使用临时表之外,有没有办法在单独的步骤中过滤/排序?【英文标题】:Is there a way to filter/sort in separate steps other than using a temporary table? 【发布时间】:2018-01-17 09:16:26 【问题描述】:我有一个长时间运行的 SQL 查询。
我发现单独计算过滤器比一起计算要快。
例子:
SELECT ...
FROM Foo
WHERE Cond1 AND Cond2
比
慢很多(例如:1 秒 vs 2 分钟)SELECT ...
INTO Tmp
FROM Foo
WHERE Cond1
SELECT ...
FROM Foo
INNER JOIN Tmp ON ...
WHERE Cond2
是否有另一种更简洁的方法来指定 SQL 应该单独执行每个过滤器? (除了在我的示例中使用临时表)
编辑:
有人询问有关表和查询的更多信息:
SELECT d.IdDocument
FROM Document d
WHERE ((d.IdDocument IN (SELECT ex.RefDocument FROM ExternalKey ex WHERE ex.FieldB = 60)))
SELECT d.IdDocument
FROM Document d
WHERE
((d.FieldA IN (SELECT Id FROM PARAM0)) AND ((d.IdDocument IN (SELECT ex.RefDocument FROM ExternalKey ex WHERE ex.FieldB = 60 AND ex.FieldC IN (SELECT Id FROM PARAM1))))) OR
((d.FieldA IN (SELECT Id FROM PARAM2)) AND ((d.IdDocument IN (SELECT ex.RefDocument FROM ExternalKey ex WHERE ex.FieldB = 61 AND ex.FieldC IN (SELECT Id FROM PARAM3))))) OR
((d.FieldA IN (SELECT Id FROM PARAM4)) AND ((d.IdDocument IN (SELECT ex.RefDocument FROM ExternalKey ex WHERE ex.FieldB = 62 AND ex.FieldC IN (SELECT Id FROM PARAM5))))) OR
((d.FieldA IN (SELECT Id FROM PARAM6)) AND ((d.IdDocument IN (SELECT ex.RefDocument FROM ExternalKey ex WHERE ex.FieldB = 59 AND ex.FieldC IN (SELECT Id FROM PARAM7)))))
文档:13.000.000 行。 IdDocument 是聚集的主键。 FieldA 上有一个索引。
外部键:49.000.000 行。 (FieldB, FieldC) 上有一个索引,其中包括 RefDocument 列。 RefDocument 是引用 IdDocument 的外键。
PARAM 是使用 TVP 填充的临时表。
这两个查询非常快(不到一秒)。但是,如果我使用 AND 将两个 WHERE 条件组合成一个查询(并添加适当的括号),它将永远运行。
我也尝试过只加入一次 ExternalKey 表(并去掉“d.IdDocument IN”部分),但速度很慢(例如:执行几分钟)
例如:
SELECT COUNT(distinct d.IdDocument)
FROM Document d
INNER JOIN ExternalKey ex ON ex.RefDocument = d.IdDocument
WHERE (
(d.FieldA IN (SELECT Id FROM PARAM0) AND ex.FieldB = 60 AND ex.FieldC IN (SELECT Id FROM PARAM1)) OR
(d.FieldA IN (SELECT Id FROM PARAM2) AND ex.FieldB = 61 AND ex.FieldC IN (SELECT Id FROM PARAM3)) OR
...
【问题讨论】:
您提供给我们的统计数据有一个上下文,例如表有多大,查询的运行方式。你能发布表结构和完整的查询吗? 你在使用复合索引吗?还是分别为条件中的每个字段编制索引? @TimBiegeleisen:我会添加一些上下文 explain 非常擅长向您展示如何提高给定查询的性能以及缓慢的部分在哪里 【参考方案1】:虽然您的原始示例显示cond1 AND cond2
,但您的完整示例似乎显示了很多OR
情况。恕我直言 AND
将允许服务器首先选择最严格的一个并从那里开始,但经验表明 MSSQL 不太喜欢 OR
,也不太喜欢 IN()
运算符,这可能是你真正遇到的问题。
要解决OR
,您可以使用UNION ALL
(或UNION
,如果您期望双打)。要解决IN()
,您可以使用WHERE EXISTS()
。
然后你会得到类似的东西:
SELECT d.IdDocument
FROM Document d
WHERE EXISTS (SELECT * FROM PARAM0 p0 WHERE p0.id = d.FieldA)
AND EXISTS (SELECT * FROM ExternalKey ex JOIN PARAM1 p1 ON p1.id = ex.FieldC WHERE ex.fieldB = 60 AND ex.RefDocument = d.IdDocument)
UNION ALL
SELECT d.IdDocument
FROM Document d
WHERE EXISTS (SELECT * FROM PARAM2 p2 WHERE p2.id = d.FieldA)
AND EXISTS (SELECT * FROM ExternalKey ex JOIN PARAM3 p3 ON p3.id = ex.FieldC WHERE ex.fieldB = 60 AND ex.RefDocument = d.IdDocument)
UNION ALL
...
PS:我也不太清楚你在 TSQL 中是如何处理SELECT Id FROM PARAM0
的?可能是因为 SQL 在确定这些“表参数”中的预期内容时也存在问题吗?
-- 更新--
我不喜欢跳过原始问题。你提到这些是TVP的。 来自the documentation:
表值参数只能被索引以支持 UNIQUE 或 PRIMARY KEY 约束。 SQL Server 不维护表值参数的统计信息。
我猜如果没有关于这些事情的统计数据可能会使优化器很难选择最佳计划。首先将它们转储到#temp 表中并在使用它们之前添加索引不是一种选择?
【讨论】:
您对 TVP 的看法是正确的:它们被转储到临时表中以获取统计信息。我不能使用简单的 WHERE IN (@param1, @param2, ...),因为 ID 太多。有没有办法在 C# 的临时表中转储大量 ID,而不使用 TVP?关于您使用 UNION ALL 的替代查询:我会尝试一下。顺便说一句,这样做会生成重复的文档 ID,所以我想我将不得不使用 UNION。 我已经尝试过创建一个唯一命名的 (GUID) ##table,使用 SqlBulkCopy 在其中转储值,然后在查询中使用它(在索引 ##table 之后)。它可以工作,但需要相当多的编码和动态 SQL。至于UNION
与UNION ALL
,假设返回的值的数量是lowish,它不应该有那么多开销。 (我假设那些是 int 或 bigint,所以应该很快)。
PS:您可能也可以使用“固定”#table 名称,但由于我在 dyn-sql 的辅助存储过程中创建了它,所以我需要走那条路,不要问=)
" 但经验表明 MSSQL 不喜欢 OR"。我不知道。好点子。我测试了您的解决方案,性能现在好多了。问题:假设我想向已经包含多个 UNION ALL 的查询添加额外的 AND 过滤器(如您的示例中所示)。最好的表现是什么? a) 将 UNION ALL 放在括号之间并使用 INTERSECT 运算符添加附加查询? b) 添加 WHERE 条件? (例如:SELECT * FROM (SELECT ... UNION ALL ... UNION ALL ... ) t WHERE condition
c)别的什么?
与 SQL 中的所有内容一样,唯一正确的答案是“视情况而定”。很可能这两种方法都被优化为完全相同的查询计划。确定的唯一方法是简单地尝试两者并查看会发生什么。查询计划是比较不同方法的非常有用的工具,可以让您深入了解 MSSQL 如何解释您的查询。 (我真的很喜欢“SQL Sentry Plan Explorer”)。寻找您不期望的扫描,检查估计和实际数字是否或多或少一致,并记住“成本”与“时间”不同。 gl.以上是关于除了使用临时表之外,有没有办法在单独的步骤中过滤/排序?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法获取 SQL Server 中所有当前临时表的列表?