我们在做SqlServer的查询调优的时候,经常会在语句末尾用到option(loop/merge/hash join)或在join语句前直接声明loop/merge/hash,来强制SqlServer使用某一特定类型的join方式。但是有些时候经过查询优化器优化后的执行计划可能会和你声明的join方式产生冲突,导致执行计划生成失败,我们来看一下下面这个典型案例。
declare @id1 int=1,@id2 int=1 select * from [dbo].[T_People] a inner join [dbo].[T_People_II] b on a.id=b.id where a.id=@id1 and b.id=@id2 option(hash join)
我们在上面的查询语句中使用了option提示符强制让SqlServer使用Hash Join来做join查询,在执行该语句时我们会得到如下错误信息:
消息 8622,级别 16,状态 1,第 3 行
由于此查询中定义了提示,查询处理器未能生成查询计划。请重新提交查询,并且不要在查询中指定任何提示,也不要使用 SET FORCEPLAN。
为什么SqlServer会产生这么一个信息呢?
主要是因为查询优化器在分析上面这个语句的时候发现了inner join的条件是
a.id=b.id
然后又在where条件中发现了
a.id=@id1 and b.id=@id2
那么在查询优化器看来,@id1和@id2可能为任何int类型的值,甚至有可能@id1=@id2,比如@id1=@id2=1
那么这个时候查询优化器会认为inner join完全是多余的,所以上面的语句就被查询优化器优化为了cross join的方式,如下方式,然后在这个结果上再做了a.id=1 and b.id=1的过滤
select * from [dbo].[T_Peopl] a cross join [dbo].[T_People_II] b
这个时候你去观察上面这个语句的查询计划,发现在Nested Loops有一个红色的小叉,表示这个join是没有Predicate的,也就是没有join条件的(Cross Join就没有条件),术语叫非equijoin,实际上下面这个执行计划中join两边的索引,只要有一边没有Predicate,那么该join都是非equijoin。
而非equijoin是不能使用Merge/Hash join的,换句话说cross join是不能用Merge/Hash join的,只能用Loop join,所以最上面的那条语句会报错,要求去掉option提示符。所以SqlServer的提示符不是什么时候都可以用的,甚至with(index(...))这种强制使用索引的提示符也不能随便乱用,因为执行计划中的某些步骤可能会和你声明的提示符产生冲突,从而导致执行计划生成失败而报错,要根据具体情况具体分析合理使用各种SqlServer的提示符。