3

ストアド プロシージャを繰り返し実行する次のコードがあります。SQL ステートメントを文字どおりに実行するとうまく機能するので、実行していることをカプセル化したストアド プロシージャを作成しました。

foreach (string worker in workers)
{
    _gzClasses.ExecuteCommand("EXEC dbo.Session_Aggregate @workerId = {0}, @timeThresh = {1}", worker, SecondThreshold);
    Console.WriteLine("Inserted sessions for {0}", worker);
}

次に、各呼び出しが生成する行数を知りたかったので@@rowcount、出力パラメーターとして返すように SP を少し変更しました。DataContext を使用して出力パラメーターを使用してコマンドを実行することはできないため、上記の for ループ内のコードを次のように変更する必要がありました。

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。しかし、その場合、out パラメータ@sessionが定義されていないというエラーが表示され続けます。いずれにせよ、SQL コマンドは SSMS で 1 秒未満で実行されますが、クエリはほとんど実行されません。

誰かが何が起こっているのかを理解するのを手伝ったり、物事をスピードアップする方法を見つけたりできる場合に備えて、ストアド プロシージャを次に示します。また、ここで何が起こっているかを適切にプロファイルする方法についての指針も示します。CommandType.StoredProcedureVSによって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 がこれを行うと思っていましたが、おそらく間違っていました。また、 SQL Server の新しいバージョンでは、実行プランが非常に異なるサイズのデータ​​に対するパラメーター スニッフィングの影響を軽減するヒントを発見しました。OPTIMIZE FOR UNKNOWN

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);
}
4

1 に答える 1

1

SQLServerProfiler を起動すると、単一のクエリと現在実行している方法との違いがわかります。

http://www.techrepublic.com/article/step-by-step-an-introduction-to-sql-server-profiler/5054787

しかし、もっと重要なことは、SSMS で [クエリ] タイルを介して有効にし、[実行プランの表示] を選択できるクエリ実行プランを確認する必要があることです。

http://www.mssqltips.com/sqlservertip/1856/sql-server-query-execution-plans-in-sql-server-management-studio/

あなたが SSMS に本当に慣れていない場合は、私が提供したものに加えていくつかの記事を読むと思いますが、クエリ実行計画は、クエリが遅れている場所を実際に示します。(基本的な経験則では、完全なテーブル スキャンを実行したくない、シークを実行したい、つまり、インデックスや主キーで検索したいということです) 私はデータベース管理者ではありませんが、それはクエリをデバッグするときにおそらく取りたいと思うルート。

かなり簡単に見えるので、確認した後、それがあなたのクエリであるかどうかはわかりません。ただし、呼び出し回数に関係している可能性があります。すべてのワーカー データをクエリに渡す方法を見つけて、クエリ自体を 1 回だけ実行するのではなく、workers.count 回実行する方法を見つけたいと思うかもしれません......HTH

于 2012-08-07T22:15:34.380 に答える