也就是说,为什么会这样:
select *
from tableA
/* Bunch of inner joins */
where
/* Bunch of clauses */
and (
exists (
select *
from tableB, tableC, tableD
where (tableB.fieldNameA = 'foo') and
/* More clauses */
) or
exists (
select *
from tableB, tableC, tableD
where (tableB.fieldNameA = 'bar') and
/* More clauses */
)
)
跑得快近500倍?
select *
from tableA
/* Bunch of inner joins */
where
/* Bunch of clauses */
and exists (
select *
from tableB, tableC, tableD
where (tableB.fieldNameA = 'foo' or tableB.fieldNameA = 'bar') and
/* More clauses */
)
我喜欢不重复代码的想法,所以想巩固到第二个版本。它们都产生相同的结果集,但第一个运行得更快,我不能不使用它。想法?
我一直在研究查询计划,但确实没有得到它的底部。我注意到的一件事是,对于一个因素的例子,[tree].[PK__tree__09746778]
的聚集索引搜索的实际行数接近93,000。另一个例子并不接近线数这么高。这是文本输出 - 不太确定这是多么有用。
第一个例子(未发现)产生:
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([initialCategory].[inode]))
|--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[tree].[child]))
| |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[inode])=([OPUSDev2].[dbo].[tree].[parent]), RESIDUAL:([OPUSDev2].[dbo].[course].[inode]=[OPUSDev2].[dbo].[tree].[parent]))
| | |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[section].[inode], [Expr1037]) WITH UNORDERED PREFETCH)
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[course_term].[inode], [Expr1036]) WITH UNORDERED PREFETCH)
| | | | |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[course_number])=([OPUSDev2].[dbo].[course_term].[course_number]), RESIDUAL:([OPUSDev2].[dbo].[course_term].[course_number]=[OPUSDev2].[dbo].[course].[course_number]))
| | | | | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course].[PK__course__31B762FC]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_web],0)=(1) AND CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_nav],0)=(1)))
| | | | | |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[term].[term_id])=([OPUSDev2].[dbo].[course_term].[term_id]))
| | | | | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[term].[PK__term__0697FACD]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[term].[active_on_web],0)=(1)))
| | | | | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course_term].[course_term_pk]))
| | | | |--Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[section_idx2]), SEEK:([OPUSDev2].[dbo].[section].[course_term_inode]=[OPUSDev2].[dbo].[course_term].[inode]) ORDERED FORWARD)
| | | |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[PK__section__7B264821]), SEEK:([OPUSDev2].[dbo].[section].[inode]=[OPUSDev2].[dbo].[section].[inode]), WHERE:([OPUSDev2].[dbo].[section].[status]='Active') LOOKUP ORDERED FORWARD)
| | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]))
| |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [initialCategory]), SEEK:([initialCategory].[inode]=[OPUSDev2].[dbo].[tree].[child]), WHERE:([OPUSDev2].[dbo].[category].[active] as [initialCategory].[active]=(1)) ORDERED FORWARD)
|--Concatenation
|--Nested Loops(Inner Join)
| |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [childCategory]), SEEK:([childCategory].[inode]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
| |--Nested Loops(Inner Join, OUTER REFERENCES:([parentCategory].[inode]))
| |--Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[idx_category_2] AS [parentCategory]), SEEK:([parentCategory].[category_key]='noncredit_subjects') ORDERED FORWARD)
| |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]), SEEK:([OPUSDev2].[dbo].[tree].[child]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode] AND [OPUSDev2].[dbo].[tree].[parent]=[OPUSDev2].[dbo].[category].[inode] as [parentCategory].[inode]) ORDERED FORWARD)
|--Nested Loops(Inner Join)
|--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [childCategory]), SEEK:([childCategory].[inode]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
|--Nested Loops(Inner Join, OUTER REFERENCES:([parentCategory].[inode]))
|--Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[idx_category_2] AS [parentCategory]), SEEK:([parentCategory].[category_key]='credit_subjects') ORDERED FORWARD)
|--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]), SEEK:([OPUSDev2].[dbo].[tree].[child]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode] AND [OPUSDev2].[dbo].[tree].[parent]=[OPUSDev2].[dbo].[category].[inode] as [parentCategory].[inode]) ORDERED FORWARD)
第二个例子(因素)产生:
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([initialCategory].[inode]))
|--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[tree].[child]))
| |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[inode])=([OPUSDev2].[dbo].[tree].[parent]), RESIDUAL:([OPUSDev2].[dbo].[course].[inode]=[OPUSDev2].[dbo].[tree].[parent]))
| | |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[section].[inode], [Expr1029]) WITH UNORDERED PREFETCH)
| | | |--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[course_term].[inode], [Expr1028]) WITH UNORDERED PREFETCH)
| | | | |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[course].[course_number])=([OPUSDev2].[dbo].[course_term].[course_number]), RESIDUAL:([OPUSDev2].[dbo].[course_term].[course_number]=[OPUSDev2].[dbo].[course].[course_number]))
| | | | | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course].[PK__course__31B762FC]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_web],0)=(1) AND CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[course].[show_on_nav],0)=(1)))
| | | | | |--Hash Match(Inner Join, HASH:([OPUSDev2].[dbo].[term].[term_id])=([OPUSDev2].[dbo].[course_term].[term_id]))
| | | | | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[term].[PK__term__0697FACD]), WHERE:(CONVERT_IMPLICIT(tinyint,[OPUSDev2].[dbo].[term].[active_on_web],0)=(1)))
| | | | | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[course_term].[course_term_pk]))
| | | | |--Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[section_idx2]), SEEK:([OPUSDev2].[dbo].[section].[course_term_inode]=[OPUSDev2].[dbo].[course_term].[inode]) ORDERED FORWARD)
| | | |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[section].[PK__section__7B264821]), SEEK:([OPUSDev2].[dbo].[section].[inode]=[OPUSDev2].[dbo].[section].[inode]), WHERE:([OPUSDev2].[dbo].[section].[status]='Active') LOOKUP ORDERED FORWARD)
| | |--Clustered Index Scan(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]))
| |--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [initialCategory]), SEEK:([initialCategory].[inode]=[OPUSDev2].[dbo].[tree].[child]), WHERE:([OPUSDev2].[dbo].[category].[active] as [initialCategory].[active]=(1)) ORDERED FORWARD)
|--Nested Loops(Inner Join)
|--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[PK__category__2739D489] AS [childCategory]), SEEK:([childCategory].[inode]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
|--Nested Loops(Inner Join, OUTER REFERENCES:([OPUSDev2].[dbo].[tree].[parent]))
|--Clustered Index Seek(OBJECT:([OPUSDev2].[dbo].[tree].[PK__tree__09746778]), SEEK:([OPUSDev2].[dbo].[tree].[child]=[OPUSDev2].[dbo].[category].[inode] as [initialCategory].[inode]) ORDERED FORWARD)
|--Index Seek(OBJECT:([OPUSDev2].[dbo].[category].[idx_category_2] AS [parentCategory]), SEEK:([parentCategory].[category_key]='credit_subjects' AND [parentCategory].[inode]=[OPUSDev2].[dbo].[tree].[parent] OR [parentCategory].[category_key]='noncredit_subjects' AND [parentCategory].[inode]=[OPUSDev2].[dbo].[tree].[parent]) ORDERED FORWARD)
答案 0 :(得分:5)
确定速度差异的唯一方法是查看每个查询的查询计划,并查看优化程序的不同操作。根据经验,其他人发现将OR
子句更改为UNION
子句有助于优化器更好地使用索引,从而缩短查询执行时间。
文章here对影响查询计划的一些变量(包括选择性)给出了非常好的解释。
为什么UNION会导致更多的搜索而不是扫描,因为每个操作都需要满足一定的选择性要求才有资格进行搜索。 (选择性是被过滤的特定列的唯一性)。 OR在单个操作中发生,因此当每个列的选择性合并并且超过一定百分比时,则认为扫描更有效。
您似乎认为以下区块会产生重大影响:
where (table.fieldNameA = 'foo' or table.fieldNameA = 'bar')
由于UNION默认情况下为每个语句执行单独的操作,因此不会合并每列的选择性,从而使其更有可能执行搜索。现在,由于UNION执行两个操作,因此需要使用上面的连接操作匹配其结果集。通常这不是一项昂贵的操作。
为什么我在这里提到UNION
是因为优化后的SQL与UNION
的行为非常相似。
exists (
select *
from tableB, tableC, tableD
where (table.fieldNameA = 'foo') and
/* More clauses */
) or
exists (
select *
from tableB, tableC, tableD
where (table.fieldNameA = 'bar') and
/* More clauses */
)
您有两个单独的子查询,每个子查询都可以并行化,然后在完成时连接在一起。每个子查询也具有更好的选择性比率,鼓励使用搜索而不是扫描。
您的(已更新)查询计划支持此功能。在你的第一个计划中,在底部,你有这个:
Concatenation
|--Nested Loops(Inner Join)
| |--Clustered Index Seek
| |--Nested Loops
| |--Index Seek
| |--Clustered Index Seek
|--Nested Loops(Inner Join)
|--Clustered Index Seek
|--Nested Loops
|--Index Seek
|--Clustered Index Seek
显示OR
条件已被拆分为两个可并行化的独立作业,索引搜索很可能表现更好,因为选择性将得到改善。
每当您的查询效果不佳时,都需要查看查询计划以查看哪些子部分需要很长时间,并尝试先修复这些部分。非常类似于优化程序的性能。
查询可能非常难以通过眼睛进行优化,因为许多变量会影响查询计划,例如表中的行数,可用的索引以及某些列的选择性。