关于强制HASH语句报错,100分,解决马上给
问题描述,我在做SQL语句优化的时候发现使用HASH匹配,当时没有报错,可能条件不一样,,运行了一段时间发现报如下错误
SELECT COUNT(*)
FROM vcb_ContractGrid
LEFT JOIN ( SELECT DISTINCT
BusinessGUID
FROM vjd_Work2Business
WHERE Type = '合同'
AND BeginDateJh != ConfirmedBeginDate
) b ON vcb_ContractGrid.ContractGUID = b.BusinessGUID
WHERE vcb_ContractGrid.BUGUID = ( '993dcc44-7297-415f-9f4a-6c87e7f49387' )
AND vcb_ContractGrid.IfDdhs = 1
AND vcb_ContractGrid.IfConfirmFkPlan = 0
AND vcb_ContractGrid.JbrGUID = ( '39957769-229b-442e-b839-7ff710433c17' )
AND ( (( ( 1 = 1
AND ( JbDeptCode = 'zb.c1006'
OR JbDeptCode LIKE 'zb.c1006.%'
)
)
AND ( 1 = '1' )
)
AND ( 88 = 88 ))
)
AND ( ( vcb_ContractGrid.ContractGUID IN (
SELECT cb_ContractProj.ContractGUID
FROM cb_ContractProj
INNER HASH JOIN ( SELECT ContractGUID
FROM cb_Contract2HTType aa
INNER JOIN cb_ContractAction bb ON aa.HtTypeGUID = bb.RefGUID
AND ( bb.isView = 1 )
AND bb.BUGUID = '993dcc44-7297-415f-9f4a-6c87e7f49387'
AND aa.BUGUID = '993dcc44-7297-415f-9f4a-6c87e7f49387'
INNER JOIN MyUserToOrg ku2o ON bb.OrgGuid = ku2o.OrgGuid
AND ku2o.UserGUID = '39957769-229b-442e-b839-7ff710433c17'
) b ON b.ContractGUID = cb_ContractProj.ContractGUID
WHERE cb_ContractProj.ProjGUID IN (
'7e826468-fc51-44bd-a291-a8f5f5996848',
'490a5165-f4bc-4265-b5bb-4d4fd8d4c1c4',
'90a09346-9d7a-4746-a465-897655158e04',
'bf9593a2-af37-4a65-907b-395e13c1cac1',
'8e913ee3-e991-458d-8321-d9f76a3433d2',
'd314b067-603e-4691-a785-2cb384004c94',
'eaa6f096-9f6d-4f2b-87df-d95e44f42a5c',
'37327427-e1e5-4e56-bd87-62cb98bfb392',
'14e29305-1c19-492f-a6f9-cfb9c16cde8c',
'e6689bc2-8995-43cf-a497-561122bfea7f',
'b4243753-0d76-49d7-a22c-567022fc1188',
'bd69b6cf-5b30-462a-a485-bbdcd7ede872',
'839933cd-8669-4e30-bc76-0cbf5515b206',
'00000000-0000-0000-0000-000000000000' ) ) )
AND vcb_ContractGrid.ContractGUID = '9a11ee59-0345-4f3e-8c49-21474c95d980'
) ;
语句中HASH,对性能影响非常明显,但现在报错了
消息 8622,级别 16,状态 1,第 1 行
由于此查询中定义了提示,查询处理器未能生成查询计划。请重新提交查询,并且不要在查询中指定任何提示,也不要使用 SET FORCEPLAN。
错误如下,后来吧HASH放到INNER JOIN cb_ContractAction 中间问题解决,但不知道是什么原因造成的,为什么报错,请专家解释原理。 sql 优化 DBA 运维 性能
[解决办法]
当你的环境变了,如数据量、索引等等,SQLServer可能会选择另外一种关联方式,比如合并、嵌套循环,而不是哈希,但是你强制用hash却没有强制更改执行计划,就报错咯。
[解决办法]
对了,关于你的那个错,估计是之前有预编译所以可以用之前生成好的计划,后来某次数据库大扫除把预编译的计划清掉了,然后再生成的时候编译器就抽了,参考我上面的语句。
比如说我报错的语句放到存储过程里执行就可以,当然也不是全可以,用=的目前看来是肯定不行的。
USE TEMPDB
GO
IF OBJECT_ID('TA') IS NOT NULL DROP TABLE TA
IF OBJECT_ID('TB') IS NOT NULL DROP TABLE TB
IF OBJECT_ID('P1') IS NOT NULL DROP PROCEDURE P1
GO
CREATE TABLE TA(ID INT,VAL VARCHAR(50))
CREATE TABLE TB(ID INT,AID INT,VAL VARCHAR(50))
GO
SET NOCOUNT ON
GO
INSERT INTO TA
SELECT 1,'A' UNION ALL
SELECT 2,'B' UNION ALL
SELECT 3,'C' UNION ALL
SELECT 4,'D' UNION ALL
SELECT 5,'E'
GO
INSERT INTO TB
SELECT 1,1,'AA' UNION ALL
SELECT 2,1,'AB' UNION ALL
SELECT 1,2,'BA' UNION ALL
SELECT 2,2,'BB' UNION ALL
SELECT 2,3,'BC'
GO
GO
PRINT '-----------------------'
PRINT 'TA.ID>=1 AND TA.ID<2可以'
GO
SELECT *
FROM TA
INNER HASH JOIN TB ON TA.ID=TB.AID
WHERE TA.ID>=1 AND TA.ID<2
GO
PRINT '-----------------------'
PRINT '加=1就不行'
GO
SELECT *
FROM TA
INNER HASH JOIN TB ON TA.ID=TB.AID
WHERE TA.ID>=1 AND TA.ID<=1
GO
PRINT '-----------------------'
PRINT '同样报错的语句,用存储过程就可以'
GO
CREATE PROCEDURE P1(@MIN INT,@MAX INT)
AS
SELECT *
FROM TA T1
INNER HASH JOIN TB T2 ON T1.ID=T2.AID
WHERE T1.ID >=@MIN AND T2.ID<=@MAX
GO
EXEC P1 1,1
/*
-----------------------
TA.ID>=1 AND TA.ID<2可以
警告: 由于使用了本地联接提示,联接次序得以强制实施。
-----------------------
加=1就不行
消息 8622,级别 16,状态 1,第 1 行
由于此查询中定义了提示,查询处理器未能生成查询计划。请重新提交查询,并且不要在查询中指定任何提示,也不要使用 SET FORCEPLAN。
-----------------------
同样报错的语句,用存储过程就可以
警告: 由于使用了本地联接提示,联接次序得以强制实施。
*/
[解决办法]
首先根据MSDN ,Hash Join必须要满足一个特性
“Similar to a merge join, a hash join can be used only if there is at least one equality (WHERE) clause in the join predicate”
http://msdn.microsoft.com/en-us/library/aa178403(v=sql.80).aspx
下面来逐个解释
1 ,'加=1就不行'
SELECT *FROM TA INNER HASH JOIN TB ON TA.ID=TB.AIDWHERE TA.ID =1
我们把先 HASH JOIN 改为nested Loop,
SET statistics profile on
go
SELECT *FROM TA INNER HASH JOIN TB ON TA.ID=TB.AIDWHERE TA.ID =1
生成如下的计划
[解决办法]
--Nested Loops(Inner Join)
[解决办法]
--Table Scan(OBJECT:([tempdb].[dbo].[TA]), WHERE:([tempdb].[dbo].[TA].[ID]=(1)))
[解决办法]
--Table Scan(OBJECT:([tempdb].[dbo].[TB]), WHERE:([tempdb].[dbo].[TB].[AID]=(1)))
上面会发现什么?
Table Scan(OBJECT:([tempdb].[dbo].[TB]) 多了一个[TB].[AID]=1 ,而在原T-SQL中是没有的,这是优化器优化的结果,排除更多的行嘛,提高性能,
而Nested Loops(Inner Join)却没有任何谓词,这是容易理解的,既然Table Scan的时候 [TA].[ID] ,[TB].[AID] 都等于1,
那就没有必要在Nested Loops(Inner Join)再加上谓词了,不幸的是hash join是至少需要一个谓词的。
所以你会发现无论是 TA.ID =1 还是2,3,4,5.或者 把TA.ID =1 改为TA.ID =100 and [TB].[AID]=100 ,都会发生这样的错误。
2, SELECT *FROM TA INNER HASH JOIN TB ON TA.ID=TB.AID WHERE TA.ID >0 AND TA.ID<2
[解决办法]
--Hash Match(Inner Join, HASH:([tempdb].[dbo].[TA].[ID])=([tempdb].[dbo].[TB].[AID]), RESIDUAL:([tempdb].[dbo].[TB].[AID]=[tempdb].[dbo].[TA].[ID]))
[解决办法]
--Table Scan(OBJECT:([tempdb].[dbo].[TA]), WHERE:([tempdb].[dbo].[TA].[ID]>(0) AND [tempdb].[dbo].[TA].[ID]<(2)))
[解决办法]
--Table Scan(OBJECT:([tempdb].[dbo].[TB]), WHERE:([tempdb].[dbo].[TB].[AID]>(0) AND [tempdb].[dbo].[TB].[AID]<(2)))
很明显,这个时候跟上面的情况不同,Hash Match(Inner Join的谓词是绝对不能省略的,这就给hash join创造了条件。
3,SQL SERVER 2012 为什么没有错,看一下执行计划
SELECT *FROM TA INNER HASH JOIN TB ON TA.ID=TB.AIDWHERE TA.ID =1
------解决方案--------------------
--Hash Match(Inner Join, HASH:([tempdb].[dbo].[TA].[ID])=([tempdb].[dbo].[TB].[AID]), RESIDUAL:([tempdb].[dbo].[TA].[ID] = [tempdb].[dbo].[TB].[AID]))
[解决办法]
--Table Scan(OBJECT:([tempdb].[dbo].[TA]), WHERE:([tempdb].[dbo].[TA].[ID]=(1)))
[解决办法]
--Table Scan(OBJECT:([tempdb].[dbo].[TB]), WHERE:([tempdb].[dbo].[TB].[AID]=(1)))
虽然在Table Scan的时候 [TA].[ID] ,[TB].[AID] 都等于1的情况下,没有必要再在Hash Match(Inner Join操作符中添加谓词,
但是SQL SERVER为了FIX这个BUG,还是多此一举的加上去了,这就为HASH JOIN创造了条件,这也就是为什么SQL SERVER 2012没有报错的原因。