web-dev-qa-db-ja.com

XP_CMDShellを有効にした後、サービスを再起動するまでSQL Serverの動作が遅くなる

2日前、INSERTUPDATEを必要とする関数を作成し、_XP_CMDShell_を有効にして、スクリプトを実行しました。

その後、SQL ServerはSELECTコマンドに対して非常に遅く動作します。分離されたコマンドを介してSELECTの後に実行される非常に単純なINSERTステートメントでも。

他のデータベースでこの動作をテストしたところ、数分後にSELECTが実行されるのと同じ結果が得られました。

また、SQL Server 2014を搭載した他の2つのホストでテストしたところ、結果は同じでした。

別の選択ステートメント内でその値を取得するために作成した関数:

_ALTER FUNCTION [Prg].[intCheckDelayedProcessProgram] 
(
     @SalesOrderProductID INT,
     @MainProductTreeID INT,
     @ProductTreeID INT,
     @ProcessId INT,
     @additionalDays INT = 2,
     @currentDateReverceString VARCHAR(10) = NULL
)
RETURNS INT
AS
BEGIN
    --declare @SalesOrderProductID INT = 40957,
    --  @MainProductTreeID INT = 93758,
    --  @ProductTreeID INT = 93758,
    --  @ProcessId INT = 4472,
    --  @additionalDays INT = 2,
    --  @currentDateReverceString VARCHAR(10) = null -- '30/09/1398'

DECLARE @ProduceDailyProgramProductTree_Id INT, @ProgramQuantity INT, @startDate JalaliDate,
        @CurrentDate JalaliDate, @lastDate JalaliDate, @additionalDate JalaliDate, @holiDaysCount INT;
IF(@currentDateReverceString IS NULL OR @currentDateReverceString = '')
    SET @CurrentDate = dbo.GetCurrentJalaliDate();
ELSE
    SET @CurrentDate = Gnr.RevercePersianDate(@currentDateReverceString);

IF (@additionalDays IS NULL)
    SET @additionalDays = 2;

DECLARE @allDelayedItemsCount INT = 0;
BEGIN
    DECLARE @sql NVARCHAR(4000), @cmd VARCHAR(4000);
    DECLARE cursor_pdppt CURSOR
        FOR SELECT pdppt.ID, pdppt.ProgramQuantity, pdppt.[Date] FROM Prg.ProduceDailyProgramProductTree pdppt
        WHERE pdppt.SalesOrderProductID = @SalesOrderProductID
                AND pdppt.MainProductTreeID = @MainProductTreeID
                AND pdppt.ProductTreeID = @ProductTreeID
                AND pdppt.Process = @ProcessId
                AND ISNULL(pdppt.IsDelayed, 0) = 0
    OPEN cursor_pdppt;
    FETCH NEXT FROM cursor_pdppt INTO @ProduceDailyProgramProductTree_Id, @ProgramQuantity, @startDate;
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @lastDate = Gnr.RevercePersianDate([Prg].[intGetLastDateForDelayedProcessProgram](@startDate, @additionalDays, @CurrentDate, 1));
            IF (@CurrentDate.GetDate() > @lastDate.GetDate())
            BEGIN
                IF ((SELECT COUNT(*) FROM Prg.ProduceDailyOperation pdo WHERE pdo.ProduceDailyProgramProductTreeID = @ProduceDailyProgramProductTree_Id AND pdo.Process = @ProcessId
                        AND pdo.[Date].GetDate() > @lastDate.GetDate()) = 0)
                BEGIN
                    SET @allDelayedItemsCount = @allDelayedItemsCount + @ProgramQuantity;
                END
                ELSE
                BEGIN
                    SET @allDelayedItemsCount = @allDelayedItemsCount +
                        (@ProgramQuantity - 
                            (SELECT SUM(pdo.ProducedQuantity) FROM Prg.ProduceDailyOperation pdo 
                                WHERE pdo.ProduceDailyProgramProductTreeID = @ProduceDailyProgramProductTree_Id 
                                    AND pdo.Process = @ProcessId AND pdo.[Date].GetDate() <= @lastDate.GetDate()));
                END;

                SELECT @sql = 'UPDATE [Prg].[ProduceDailyProgramProductTree] SET [IsDelayed] = 1 WHERE ID = ' + CONVERT(VARCHAR(10), @ProduceDailyProgramProductTree_Id);
                SELECT @cmd = 'sqlcmd -S ' + @@SERVERNAME + ' -d ' + DB_NAME() + ' -Q "' + @sql + '"'
                EXEC MASTER..XP_CMDSHELL @cmd , 'no_output'
            END;
            FETCH NEXT FROM cursor_pdppt INTO @ProduceDailyProgramProductTree_Id, @ProgramQuantity, @startDate;
        END;
    CLOSE cursor_pdppt;

    IF (@allDelayedItemsCount > 0)
    BEGIN
        IF (EXISTS(SELECT 1 FROM Prg.ProduceDailyProgramProductTreeDelayed pdppt
                        WHERE pdppt.SalesOrderProductID = @SalesOrderProductID
                            AND pdppt.MainProductTreeID = @MainProductTreeID
                            AND pdppt.ProductTreeID = @ProductTreeID
                            AND pdppt.Process = @ProcessId))
        BEGIN
            SELECT @allDelayedItemsCount = @allDelayedItemsCount 
                + (SELECT pdpptd.DelayedQuantity FROM Prg.ProduceDailyProgramProductTreeDelayed pdpptd
                    WHERE pdpptd.SalesOrderProductID = @SalesOrderProductID
                        AND pdpptd.MainProductTreeID = @MainProductTreeID
                        AND pdpptd.ProductTreeID = @ProductTreeID
                        AND pdpptd.Process = @ProcessId
                        AND ISNULL(pdpptd.Active, 0) = 1
                        AND ISNULL(pdpptd.IsDeleted, 0) = 0);

            SELECT @sql = 'UPDATE [Prg].[ProduceDailyProgramProductTreeDelayed] SET DelayedQuantity = ' 
                                + CONVERT(VARCHAR(10), @allDelayedItemsCount) + 'WHERE SalesOrderProductID = ' 
                                + CONVERT(VARCHAR(10), @SalesOrderProductID) + 'AND MainProductTreeID = ' 
                                + CONVERT(VARCHAR(10), @MainProductTreeID) + 'AND ProductTreeID = ' 
                                + CONVERT(VARCHAR(10), @ProductTreeID) + 'AND Process = ' 
                                + CONVERT(VARCHAR(10), @ProcessId) +';';
        END
        ELSE
        BEGIN
            SELECT @sql = 'INSERT INTO [Prg].[ProduceDailyProgramProductTreeDelayed] (SalesOrderProductID, MainProductTreeID, Process, ProductTreeID, DelayedQuantity, Active, IsDeleted) VALUES ('
                     + CONVERT(VARCHAR(10), @SalesOrderProductID) + ', ' 
                     + CONVERT(VARCHAR(10), @MainProductTreeID) + ', '
                     + CONVERT(VARCHAR(10), @ProcessId) + ', '
                     + CONVERT(VARCHAR(10), @ProductTreeID) + ', '
                     + CONVERT(VARCHAR(10), @allDelayedItemsCount) + ', ''1'', ''0'')';
        END;

        SELECT @cmd = 'sqlcmd -S ' + @@SERVERNAME + ' -d ' + DB_NAME() + ' -Q "' + @sql + '"'
        EXEC MASTER..XP_CMDSHELL @cmd , 'no_output'
    END
    ELSE
    IF(EXISTS(SELECT 1 FROM Prg.ProduceDailyProgramProductTreeDelayed pdpptd
            WHERE pdpptd.SalesOrderProductID = @SalesOrderProductID
            AND pdpptd.MainProductTreeID = @MainProductTreeID
            AND pdpptd.ProductTreeID = @ProductTreeID
            AND pdpptd.Process = @ProcessId
            AND ISNULL(pdpptd.Active, 0) = 1
            AND ISNULL(pdpptd.IsDeleted, 0) = 0))
    BEGIN
        SELECT @allDelayedItemsCount = 
                (SELECT pdpptd.DelayedQuantity FROM Prg.ProduceDailyProgramProductTreeDelayed pdpptd
                    WHERE pdpptd.SalesOrderProductID = @SalesOrderProductID
                        AND pdpptd.MainProductTreeID = @MainProductTreeID
                        AND pdpptd.ProductTreeID = @ProductTreeID
                        AND pdpptd.Process = @ProcessId
                        AND ISNULL(pdpptd.Active, 0) = 1
                        AND ISNULL(pdpptd.IsDeleted, 0) = 0);
    END;
END;
RETURN @allDelayedItemsCount;
END
_

JalaliDateは、アセンブリによってペルシャ日付時刻を保持するユーザー定義型です。

文字列のペルシャ日付をJalaliDateに変換する_Gnr.RevercePersianDate_関数。 _[Prg].[intGetLastDateForDelayedProcessProgram]_休日で指定された日付を計算する他の関数は発行しないと思います。

更新1

インライン宣言によってパラメーター値を渡すことで機能コードをテストし、テスト値を宣言して設定します。次に、Sql Server Profilerを使用して関数のスクリプトを確認します。アセンブリの関数を使用するこの行SET @CurrentDate = dbo.GetCurrentJalaliDate()でスクリプトが停止するのを確認します。実行はエラーなしで実行され、応答しません!!注:エラーなしで非常に高速に応答するSELECT dbo.GetCurrentJalaliDate()をテストします!

更新2

関数_[Prg].[intCheckDelayedProcessProgram]_をSP内で、複雑なSELECTステートメント内で呼び出します。SPselectで呼び出すことはできません。私は機能を定義しました。すべてのデータおよび他のSPおよび関数と一緒にあるローカルサーバーで実行されるコード。CURSORの代替手段がある場合は、それを使用して関数を書き直すのを手伝ってください。

xp_cmdshellを使用してSQL Serverに対してクエリを実行する代わりに、コードを変更して ストアドプロシージャユーザー定義関数 、または 動的T -SQLsp_executesqlを使用して呼び出されました。別のサーバーに対してコードを実行する必要がある場合は、 リンクサーバー を使用します。

最後に、可能であればカーソルを使用しないようにコードを書き直すことを検討してください。 SQL Serverのセットベースの操作は、はるかに効率的です。

これらのトピックはすべてそれ自体が非常に詳細なトピックであるため、ここで答えを具体化する方法はありませんが、これらのリンクが役立つことを願っています。幸運を。

10
Randolph West

関数を使用してDMLを実行するよりも確実に良い方法を見つける必要があります。このプロセスをSELECTステートメントにアタッチする必要はありません。クエリの結果を一時テーブルにダンプし、CURSORを使用してそれを反復し(この関数で行っているように)、ストアドプロシージャを呼び出して、現在この関数で行っていることを実行できます。

ただし、少なくともの場合、カーソルにオプションを追加して、ベーステーブルがロックされないようにする必要があります。

DECLARE cursor_pdppt CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
        FOR ...

そして、CLOSE cursor_pdppt;の直後に、以下を追加する必要があります。

DEALLOCATE cursor_pdppt;
3
Solomon Rutzky