【问题标题】:SQL Union All is too slowSQL Union All 太慢了
【发布时间】:2013-03-21 21:10:19
【问题描述】:

我正在重写旧的遗留系统。它有一个名为checkExisting() 的函数。旧系统使用查询从 MSSQL 数据库中提取对象,如下所示(使用 ADO DB):

SELECT ObjectId, Name..... 
FROM tblRegisteredIncludes   
WHERE UPPER("Name") IN ('PROGA.H', 'PROGB.H'...............  list)

有很多像tblRegisteredIncludes 这样的表,但是 SQL 是按表名分组的,并且使用带有对象名称列表的 IN 子句。

这执行得很快,因为 SQL Server 在一次扫描中收集了所有对象,并且在表中的 Name 列上有一个索引。

但是,在新系统中,我不能使用相同的 SQL,因为WHERE 条件更复杂。它还使用 Source 字段,有时还使用条件中的其他字段。我有大量的单个 SQL 查询:

SELECT ObjectId, Name..... FROM tblRegisteredIncludes   
WHERE UPPER("Name") = 'PROGA.H' AND UPPER("Source") = "..."

SELECT ObjectId, Name..... FROM tblRegisteredIncludes   
WHERE UPPER("Name") = ('PROGB.H') AND UPPER("Source") = "..."

我已将tblRegisteredIncludes 表中的名称索引替换为(Name,Source) 上的复合索引。

即使如此,我也预计 SQL 的总执行速度会慢一些,但不会超过 15-20%。相反,它要慢得多,有时甚至高达 100%。我尝试使用 UNION ALL 将 SQL 组合到一个大型 SQL 查询中:

SELECT ObjectId, Name..... FROM tblRegisteredIncludes   
WHERE UPPER("Name") = 'PROGA.H' AND UPPER("Source") = "..."
UNION ALL
SELECT ObjectId, Name..... FROM tblRegisteredIncludes   
WHERE UPPER("Name") = ('PROGB.H') AND UPPER("Source") = "..."

然后再处理生成的 ADO DB 记录集,但速度更慢!

我想知道是否有一些有效的方法可以更快地执行这些查询?在使用 IN 子句和名称列表时,我需要达到与旧案例类似的性能。我可以提供执行计划。

【问题讨论】:

  • 我想看看执行计划。你为什么使用upper?您是否检查过排序规则是否区分大小写?
  • 您能否详细说明“我不能使用相同的 SQL,因为 WHERE 条件更复杂。”在您给出的示例中,没有理由将查询分开。
  • 你真的需要UPPER吗?出于这个原因,很少使用区分大小写的排序规则。使用 UPPER 会破坏您的查询计划,因为该函数对 SQL Server 是不透明的 - 它不知道该函数的作用,因此它不会使用索引并进行表扫描。
  • 在两边都使用 Upper 可能是问题之一:WHERE UPPER("Name") = ('PROGB.H') AND UPPER("Source") = "...".. . 在Oracle 中,当转换为Upper/Lower 时,可以创建基于函数的索引。不确定在 SQL Server 中是否有同样的可能。
  • @lserni 肯定 PERSISTED 计算列会比通过触发器保持列更新更好?例如CREATE TABLE T (A VARCHAR(50) COLLATE Latin1_general_cs_as NOT NULL, B AS A COLLATE Latin1_general_ci_as PERSISTED, C AS LOWER(A) PERSISTED)

标签: sql sql-server performance union


【解决方案1】:

union all 版本中,每个子查询都会导致对表的单独扫描。

您应该使用or 条件引入所有行:

SELECT ObjectId, Name.....
FROM tblRegisteredIncludes   
WHERE (UPPER("Name") = 'PROGA.H' AND UPPER("Source") = "...") or
      (UPPER("Name") = ('PROGB.H') AND UPPER("Source") = "...") or
      . . .

如果您遇到所有比较都在 NameSource 上的情况,我建议使用 CTE 创建一个动态表:

with toinclude as (
   select 'PROGA.H' as name, 'SOURCE' as source union all
   select . . .
)
select ri.ObjectId, ri.Name
from tblRegisteredIncludes join
     toinclude
     on ri.name = toinclude.name and ri.source = toinclude.source

您可以省略toupper(),除非您特别担心您的实现或字段覆盖了默认的不区分大小写行为。在where 子句中使用函数通常会阻止使用索引。

【讨论】:

  • 非常感谢您的回复。如果我只是将所有条件与 OR 粘合在一起,我需要检查性能。从执行计划中,我可以看到只使用一次扫描,就像使用 IN 子句一样。而对于 UNION ALL 则有许多扫描。我认为 UNION ALL 更聪明。我会衡量表现。非常愚蠢,我第一次没有那样做。我唯一担心的是 WHERE 可能会变得太长并且 SQL 解析器会导致延迟。也感谢有关临时表的建议,但我正在从几个表中收集数据,这可能会拖累性能。
【解决方案2】:

根据您的描述,我假设该表有大量行,在这种情况下,几乎可以肯定是 UPPER 导致了速度问题,因为这意味着它无法正确使用您似乎正确拥有的索引放。存储的数据真的区分大小写吗? - 检查 db 设置,默认情况下通常不会,在这种情况下您可以删除 UPPER。

如果它是区分大小写的,那么如果存储名称的大小写是一致的,您仍然可以删除 Upper 并使用一致的大写/小写名称,例如名称 = 'ProgB.H'

【讨论】:

  • 非常感谢您的回复。
  • 非常感谢您的回复。整个数据库区分大小写。是的,它可以包含非常多的行,但即使只有几千行,我也能看到很大的延迟。如果使用 UPPER(),我如何检查我的复合索引是否正常工作?索引在两列或三列上定义,在本例中为 - 名称 (ASC)、源 (ASC)。是否有一些复合索引设置使其明确不区分大小写并使用 f UPPER?
  • 不可能正确使用索引,因为它是区分大小写的顺序,所以实际上它必须进行表扫描并将每个名称转换为大写才能进行比较。正如我所说,如果您可以保证表中的每个名称在使用大小写时保持一致,您仍然可以删除 UPPER。或者您可以重命名它们以使它们保持一致吗?这是查看执行计划的链接msdn.microsoft.com/en-us/library/ms178071(v=sql.105).aspx
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多