我有以下代碼重複運行存儲過程。當我逐字運行SQL語句時,它工作得很好,所以我創建了一個存儲過程,它封裝了我正在做的事情。巨大的減速C#存儲過程調用,參數嗅探/優化問題?
foreach (string worker in workers)
{
_gzClasses.ExecuteCommand("EXEC dbo.Session_Aggregate @workerId = {0}, @timeThresh = {1}", worker, SecondThreshold);
Console.WriteLine("Inserted sessions for {0}", worker);
}
然後,我想知道每個呼叫有多少行產生,所以我改變了SP略微返回@@rowcount
作爲輸出參數。我can't use the DataContext to execute commands with output parameters,所以我不得不改變內部的上述代碼迴路以下幾點:
using (var cn = new SqlConnection(CnStr))
{
cn.Open();
using (var cmd = new SqlCommand("Session_Aggregate",
cn) {CommandTimeout = 300})
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@workerId", worker);
cmd.Parameters.AddWithValue("@timeThresh", SecondThreshold);
SqlParameter sessions = cmd.Parameters.Add("@sessions", SqlDbType.Int);
sessions.Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
Console.WriteLine("Inserted {1} sessions for {0}", worker, sessions.Value);
}
}
這工作,但它運行要比其他查詢慢。我認爲這可能是參數嗅探的一種情況,所以我將其更改爲CommandType.Text
並使用字符串EXEC Session_Aggregate ... WITH RECOMPILE
。但在這種情況下,我一直收到出錯參數@session
未定義的錯誤。在任何情況下,即使SQL命令在1秒內在SSMS中運行<,現在查詢幾乎不運行。
這裏是存儲過程,以防萬一任何人都可以幫忙弄清楚發生了什麼,或者可以找出加快速度的方法。我也會指導如何正確描述這裏發生的事情。與CommandType.StoredProcedure
我什至不能看到由VS發送到SQL的實際命令。
PROCEDURE [dbo].[Session_Aggregate]
-- Add the parameters for the stored procedure here
@workerId varchar(64) = 0,
@timeThresh dateTime = '13 July 2007 11:27:46'
@sessions INT OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO e_activeSessions
SELECT *
FROM (
SELECT workerId, startTime, COUNT(*) as totalTasks, MAX(timeInSession) as totalTime,
MIN(dwellTime) as minDwell, MAX(dwellTime) as maxDwell, AVG(dwellTime) as avgDwell, STDEV(dwellTime) as stdevDwell,
SUM(CAST(wrong80 as INT)) + SUM(CAST(correct80 as INT)) as total80, SUM(CAST(correct80 as INT)) as correct80,
SUM(CAST(correct80 as FLOAT))/NULLIF(SUM(CAST(wrong80 as INT)) + SUM(CAST(correct80 as INT)), 0) as percent80
FROM (
SELECT *, (SELECT MAX(timeStamp)
FROM workerLog w where dwellTime is null AND timeInSession = 0 AND workerId = @workerId AND w.timeStamp <= workerLog.timeStamp
AND w.timeStamp >= @timeThresh) as startTime
FROM workerLog where workerId = @workerId) t
GROUP BY startTime, workerId) f
WHERE startTime is NOT NULL AND f.totalTasks > 1 AND totalTime > 0;
SET @sessions = @@ROWCOUNT;
END
編輯:不管原始查詢的執行計劃的,它是由創建臨時表加速顯著。我以爲SQL會通過分析查詢來完成這項工作,但我可能是錯的。 另外,我發現了OPTIMIZE FOR UNKNOWN
提示,它在新版本的SQL Server中減輕了執行計劃針對大量不同數據大小時參數嗅探的影響。
PROCEDURE [dbo].[Session_Aggregate]
-- Add the parameters for the stored procedure here
@workerId varchar(64) = 0,
@timeThresh dateTime = '13 July 2007 11:27:46',
@sessions INT OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
CREATE TABLE #startTimes
(
startTime DATETIME
);
CREATE INDEX Idx_startTime ON #startTimes(startTime);
INSERT INTO #startTimes
SELECT timeStamp FROM workerLog
WHERE dwellTime is null AND timeInSession = 0
AND workerId = @workerId AND timeStamp >= @timeThresh;
INSERT INTO e_activeSessions
SELECT *
FROM (
SELECT workerId, startTime, COUNT(*) as totalTasks, MAX(timeInSession) as totalTime,
MIN(dwellTime) as minDwell, MAX(dwellTime) as maxDwell, AVG(dwellTime) as avgDwell, STDEV(dwellTime) as stdevDwell,
SUM(CAST(wrong80 as INT)) + SUM(CAST(correct80 as INT)) as total80, SUM(CAST(correct80 as INT)) as correct80,
SUM(CAST(correct80 as FLOAT))/NULLIF(SUM(CAST(wrong80 as INT)) + SUM(CAST(correct80 as INT)), 0) as percent80
FROM (
SELECT *, (SELECT MAX(startTime) FROM #startTimes where startTime <= workerLog.timeStamp) as startTime
FROM workerLog where workerId = @workerId) t
GROUP BY startTime, workerId) f
WHERE startTime is NOT NULL AND f.totalTasks > 1 AND totalTime > 0
OPTION (OPTIMIZE FOR UNKNOWN);
SET @sessions = @@ROWCOUNT;
END;
附加簡化:拖動SP到你的DBML文件,你可以做到以下幾點:
foreach (string worker in workers)
{
int? rows = 0;
_gzClasses.Session_Aggregate(worker, SecondThreshold, ref rows);
Console.WriteLine("Inserted {1} sessions for {0}", worker, rows);
}
現在,我只想讓一個查詢快速運行,然後我可以考慮一次爲所有的工作人員執行此操作。對我來說,當我調用它時,它可以在<1秒內運行,然後在5分鐘後超時以執行不同的執行。一些執行計劃是否可以真正進行掃描,而其他執行計劃則不是? SQL服務器分析器文章真的很老......我究竟在哪裏找到應用程序來運行它? – 2012-08-07 23:51:43
Sql server profiler應該位於您正在使用的SQL Server版本的工具文件夾中。上面的開放和關閉建議是降低查詢運行時間的好主意,但它不應該導致您描述的問題。您最好的選擇是使用查詢分析器。因爲它會給你一個關於爲什麼一個查詢的運行時間比另一個長的想法。它還將幫助您查明它是否是您的查詢或您的代碼。 – 2012-08-08 03:05:12
您可能想要考慮的另一件事是您的兩個不同查詢之間的數據量差異。查看查詢1的數據列表,並將其與查詢2進行比較。有可能是查詢2正在拉動一個度量標準對齊更多數據。我之前遇到過這樣的情況,在那裏我有一個有數百萬人的記錄,或者是記錄中只有10,000個記錄的記錄。還有別的想法。 – 2012-08-08 03:14:07