我有一個Linq
查詢,在我的開發服務器上運行「快速」,在生產服務器上運行速度非常慢(本地2秒,生產3分鐘)。 在試圖調查和比較兩者的執行計劃時,我決定從「sp_executesql
」語句中「解開」Linq查詢,並將其作爲本地(快速)環境的動態查詢運行。與sp_executesql一起運行時,同樣的SQL查詢很快,如果作爲查詢分析器中的動態查詢執行,速度很慢,爲什麼?
現在同樣的服務器,同樣的查詢,但一個包裹在sp_executesql
裏面,另一個動態在運行時間上有很大的差距:前者運行在2秒後者需要14分鐘。
我讀這篇文章 http://technet.microsoft.com/en-au/library/cc966425.aspx 它解釋了sp_executesql
如何使用緩存的執行計劃,將組成13分鐘和58sec區別?
我認爲發佈實際查詢可能無關緊要(而且看起來很醜陋和令人費解),但是如果能夠提供更多見解,我將它們粘貼到此處。
首先,在2秒鐘內執行:
exec sp_executesql N'SELECT [t0].[id] AS [Id], (CONVERT(Float,[dbo].[RankWords]((
SELECT [t18].[searchable_1]
FROM (
SELECT TOP (1) [t17].[searchable_1]
FROM [dbo].[contents] AS [t17]
WHERE [t17].[navigation_tree_id] = [t0].[id]
) AS [t18]
), @p12, @p13, @p14))) + (CONVERT(Float,[dbo].[RankWords]((
SELECT [t20].[name]
FROM (
SELECT TOP (1) [t19].[name]
FROM [dbo].[contents] AS [t19]
WHERE [t19].[navigation_tree_id] = [t0].[id]
) AS [t20]
), @p15, @p16, @p17))) AS [GlobalRank], [t0].[modified_date] AS [ModifiedDate], [t0].[parent_id] AS [ParentId], [t0].[template_id] AS [TemplateId], (
SELECT [t22].[name]
FROM (
SELECT TOP (1) [t21].[name]
FROM [dbo].[contents] AS [t21]
WHERE [t21].[navigation_tree_id] = [t0].[id]
) AS [t22]
) AS [Name], [t0].[id] AS [id2], [t0].[uri], [t0].[site_id], [t0].[list_order], [t0].[pointed_node_id], [t0].[start_date], [t0].[expiry_date], [t0].[is_external_link], [t0].[priority_level], [t0].[is_active], [t0].[power_level_required]
FROM [dbo].[navigation_trees] AS [t0]
WHERE (((
SELECT COUNT(*)
FROM [dbo].[label__navigation_tree] AS [t1]
WHERE [t1].[navigation_tree_id] = [t0].[id]
)) > @p0) AND ([t0].[template_id] IS NOT NULL) AND (([t0].[template_id]) IN (@p1, @p2, @p3)) AND (EXISTS(
SELECT TOP (1) NULL AS [EMPTY]
FROM [dbo].[contents] AS [t2]
WHERE [t2].[navigation_tree_id] = [t0].[id]
)) AND (((CONVERT(Bit,[dbo].[HasMatch](
(CASE
WHEN ((
SELECT [t4].[name]
FROM (
SELECT TOP (1) [t3].[name]
FROM [dbo].[contents] AS [t3]
WHERE [t3].[navigation_tree_id] = [t0].[id]
) AS [t4]
)) IS NOT NULL THEN CONVERT(NVarChar(MAX),(
SELECT [t6].[name]
FROM (
SELECT TOP (1) [t5].[name]
FROM [dbo].[contents] AS [t5]
WHERE [t5].[navigation_tree_id] = [t0].[id]
) AS [t6]
))
WHEN (@p4 + ((
SELECT [t8].[title]
FROM (
SELECT TOP (1) [t7].[title]
FROM [dbo].[contents] AS [t7]
WHERE [t7].[navigation_tree_id] = [t0].[id]
) AS [t8]
))) IS NOT NULL THEN CONVERT(NVarChar(MAX),@p5 + ((
SELECT [t10].[title]
FROM (
SELECT TOP (1) [t9].[title]
FROM [dbo].[contents] AS [t9]
WHERE [t9].[navigation_tree_id] = [t0].[id]
) AS [t10]
)))
WHEN (@p6 + ((
SELECT [t12].[description]
FROM (
SELECT TOP (1) [t11].[description]
FROM [dbo].[contents] AS [t11]
WHERE [t11].[navigation_tree_id] = [t0].[id]
) AS [t12]
))) IS NOT NULL THEN @p7 + ((
SELECT [t14].[description]
FROM (
SELECT TOP (1) [t13].[description]
FROM [dbo].[contents] AS [t13]
WHERE [t13].[navigation_tree_id] = [t0].[id]
) AS [t14]
))
ELSE CONVERT(NVarChar(MAX),@p8)
END), @p9))) = 1) OR ((CONVERT(Bit,[dbo].[HasMatch]((
SELECT [t16].[searchable_1]
FROM (
SELECT TOP (1) [t15].[searchable_1]
FROM [dbo].[contents] AS [t15]
WHERE [t15].[navigation_tree_id] = [t0].[id]
) AS [t16]
), @p10))) = 1)) AND ([t0].[pointed_node_id] IS NULL) AND ([t0].[site_id] = @p11) AND ([t0].[is_active] = 1)',N'@p0 int,@p1 int,@p2 int,@p3 int,@p4 nvarchar(4000),@p5 nvarchar(1),@p6 nvarchar(4000),@p7 nvarchar(1),@p8 nvarchar(4000),@p9 nvarchar(3),@p10 nvarchar(3),@p11 int,@p12 nvarchar(3),@p13 int,@p14 bit,@p15 nvarchar(3),@p16 int,@p17 bit',@p0=0,@p1=158,@p2=159,@p3=160,@p4=N'',@p5=N' ',@p6=N'',@p7=N' ',@p8=N'',@p9=N'actor',@p10=N'actor',@p11=15,@p12=N'actor',@p13=3,@p14=0,@p15=N'actor',@p16=3,@p17=0
第二個在14分鐘內執行:
DECLARE @p0 int,@p1 int,@p2 int,@p3 int,@p4 nvarchar(4000),@p5 nvarchar(1),@p6 nvarchar(4000),@p7 nvarchar(1),@p8 nvarchar(4000),@p9 nvarchar(3),@p10 nvarchar(3),@p11 int,@p12 nvarchar(3),@p13 int,@p14 bit,@p15 nvarchar(3),@p16 int,@p17 bit
SET @p0=0
SET @p1=158
SET @p2=159
SET @p3=160
SET @p4=N''
SET @p5=N' '
SET @p6=N''
SET @p7=N' '
SET @p8=N''
SET @p9=N'actor'
SET @p10=N'actor'
SET @p11=15
SET @p12=N'actor'
SET @p13=3
SET @p14=0
SET @p15=N'actor'
SET @p16=3
SET @p17=0
SELECT
[t0].[id] AS [Id],
(
CONVERT(Float,[dbo].[RankWords]
((
SELECT [t18].[searchable_1]
FROM (
SELECT TOP (1) [t17].[searchable_1]
FROM [dbo].[contents] AS [t17]
WHERE [t17].[navigation_tree_id] = [t0].[id]
) AS [t18]
),
@p12,
@p13,
@p14
))
) +
(
CONVERT(Float,[dbo].[RankWords]((
SELECT [t20].[name]
FROM (
SELECT TOP (1) [t19].[name]
FROM [dbo].[contents] AS [t19]
WHERE [t19].[navigation_tree_id] = [t0].[id]
) AS [t20]
),
@p15,
@p16,
@p17
))
)
AS [GlobalRank],
[t0].[modified_date] AS [ModifiedDate],
[t0].[parent_id] AS [ParentId],
[t0].[template_id] AS [TemplateId],
(
SELECT [t22].[name]
FROM (
SELECT TOP (1) [t21].[name]
FROM [dbo].[contents] AS [t21]
WHERE [t21].[navigation_tree_id] = [t0].[id]
) AS [t22]
) AS [Name],
[t0].[id] AS [id2],
[t0].[uri],
[t0].[site_id],
[t0].[list_order],
[t0].[pointed_node_id],
[t0].[start_date],
[t0].[expiry_date],
[t0].[is_external_link],
[t0].[priority_level],
[t0].[is_active],
[t0].[power_level_required]
FROM [dbo].[navigation_trees] AS [t0]
WHERE
(
((
SELECT COUNT(*)
FROM [dbo].[label__navigation_tree] AS [t1]
WHERE [t1].[navigation_tree_id] = [t0].[id]
)) > @p0
)
AND
([t0].[template_id] IS NOT NULL)
AND
(([t0].[template_id]) IN (@p1, @p2, @p3))
AND
(EXISTS(
SELECT TOP (1) NULL AS [EMPTY]
FROM [dbo].[contents] AS [t2]
WHERE [t2].[navigation_tree_id] = [t0].[id]
))
AND
(
((
CONVERT(Bit,[dbo].[HasMatch](
(CASE
WHEN ((
SELECT [t4].[name]
FROM (
SELECT TOP (1) [t3].[name]
FROM [dbo].[contents] AS [t3]
WHERE [t3].[navigation_tree_id] = [t0].[id]
) AS [t4]
)) IS NOT NULL
THEN
CONVERT(NVarChar(MAX),
(
SELECT [t6].[name]
FROM (
SELECT TOP (1) [t5].[name]
FROM [dbo].[contents] AS [t5]
WHERE [t5].[navigation_tree_id] = [t0].[id]
) AS [t6]
)
)
WHEN
(@p4 + ((SELECT [t8].[title]
FROM (
SELECT TOP (1) [t7].[title]
FROM [dbo].[contents] AS [t7]
WHERE [t7].[navigation_tree_id] = [t0].[id]
) AS [t8]
))
) IS NOT NULL
THEN CONVERT(NVarChar(MAX),@p5 + ((
SELECT [t10].[title]
FROM (
SELECT TOP (1) [t9].[title]
FROM [dbo].[contents] AS [t9]
WHERE [t9].[navigation_tree_id] = [t0].[id]
) AS [t10]
)))
WHEN (@p6 + ((
SELECT [t12].[description]
FROM (
SELECT TOP (1) [t11].[description]
FROM [dbo].[contents] AS [t11]
WHERE [t11].[navigation_tree_id] = [t0].[id]
) AS [t12]
))) IS NOT NULL THEN @p7 + ((
SELECT [t14].[description]
FROM (
SELECT TOP (1) [t13].[description]
FROM [dbo].[contents] AS [t13]
WHERE [t13].[navigation_tree_id] = [t0].[id]
) AS [t14]
))
ELSE CONVERT(NVarChar(MAX),@p8)
END), @p9))) = 1)
OR ((CONVERT(Bit,[dbo].[HasMatch]((
SELECT [t16].[searchable_1]
FROM (
SELECT TOP (1) [t15].[searchable_1]
FROM [dbo].[contents] AS [t15]
WHERE [t15].[navigation_tree_id] = [t0].[id]
) AS [t16]
), @p10))) = 1)
) AND ([t0].[pointed_node_id] IS NULL) AND ([t0].[site_id] = @p11) AND ([t0].[is_active] = 1)
感謝。
編輯(添加的溶液的解釋):
我已經重寫的LINQ表達式,以產生具有較少的內選擇,其結果已取下來的動態查詢從14分鐘至35秒有一個精簡查詢在運行sp_executesql的驅動器和動態驅動器之間仍有33秒的差異。
然後我用了執行計劃指標建議創建以下指標:
CREATE NONCLUSTERED INDEX [JH_contents_navigation_tree_id]
ON [dbo].[contents] ([navigation_tree_id])
CREATE NONCLUSTERED INDEX [JH_label_navigation_tree_navigation_tree_id]
ON [dbo].[label__navigation_tree] ([navigation_tree_id])
CREATE NONCLUSTERED INDEX [JH_navigation_trees_sid_pnid_isactv_tmplid]
ON [dbo].[navigation_trees] ([site_id],[pointed_node_id],[is_active],[template_id])
這帶來了兩個查詢在幾百毫秒大關。
這也解決了我的開發服務器和生產服務器之間的執行時間差異的其他問題,但是,雖然我可以解釋生產和開發服務器之間的執行時間差異,直到服務器安裝特性我仍然不'不知道爲什麼在同一臺服務器上,使用sp_executesql
運行的查詢仍然非常快,儘管底層表仍然缺少索引!? (即使我已經標記爲回答,請添加評論,這將是有趣的知道)
所有的建議幫助我縮小了問題,所以謝謝大家。不過,我覺得Aaron Kempf的回答關閉確定了實際問題。
你可以在sql分析器中捕獲兩個運行並比較IO嗎?我不認爲這是計劃緩存(應該不需要幾分鐘來生成計劃)。我最好的猜測是一次運行使用並行,另一次不會由於連接設置。 – 2011-02-28 15:32:58
在稍微修改(優化)的查詢中,我得到:在sp_executesql中,375101次讀取,4253次寫入,持續時間爲1727年,而在動態中,我得到20762153次讀取,0次寫入,持續時間爲33947。這是否證明不使用並行化很快就會看到這個)。您能否進一步解釋「由於連接設置」,您的意思是什麼? – 2011-02-28 17:01:37
假設您使用的是SQL 2005+,那麼您應該向自己(以及不幸的同事)瞭解公共表表達式和用戶定義的函數。 – 2011-02-28 20:03:23