web-dev-qa-db-fra.com

SQL Server fonctionne lentement après avoir activé XP_CMDShell même désactivé jusqu'au service de redémarrage

Il y a 2 jours, j'ai créé une fonction qui a besoin de INSERT et UPDATE, et j'ai activé XP_CMDShell Et je l'ai fait en exécutant un script.

Par la suite, SQL Server fonctionne très lentement pour les commandes SELECT. Même une instruction SELECT très simple qui s'exécute après INSERT via une commande séparée.

J'ai testé ce comportement sur d'autres bases de données et il donne le même résultat que SELECT s'exécute après quelques minutes.

De plus, je l'ai testé sur 2 autres hôtes avec SQL Server 2014 et le résultat était le même.

Fonction que j'ai créée pour en obtenir la valeur dans une autre instruction select:

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 est un type défini par l'utilisateur pour contenir l'heure de date persane par Assembly.

Fonction Gnr.RevercePersianDate Pour convertir la chaîne de date persane en JalaliDate. [Prg].[intGetLastDateForDelayedProcessProgram] Autre fonction pour calculer la date spécifiée par les vacances que je pense que la dose ne pose pas.

MISE À JOUR 1

Je teste les codes de fonction en transmettant les valeurs des paramètres en déclarant et en définissant les valeurs de test. Ensuite, l'utilisateur Sql Server Profiler pour vérifier le script de la fonction, qui voit les scripts s'arrête sur cette ligne SET @CurrentDate = dbo.GetCurrentJalaliDate() qui utilise la fonction de Assembly. Exécution en cours sans erreur et sans réponse !! Remarque: je teste SELECT dbo.GetCurrentJalaliDate() qui répond très rapidement sans erreur!

MISE À JOUR 2

J'appelle la fonction [Prg].[intCheckDelayedProcessProgram] Dans SP et dans une instruction complexe SELECT, qui ne peut pas appeler SP dans select, donc juste une solution pour moi, c'était définir la fonction. Code exécuté sur le serveur local avec toutes les données et autres SP et fonctions. S'il existe une alternative pour CURSOR aidez-moi à réécrire la fonction avec.

1

À la place d'utiliser xp_cmdshell pour exécuter des requêtes sur SQL Server, modifiez votre code pour utiliser procédures stockées , fonctions définies par l'utilisateur , ou même T-SQL dynamique appelé en utilisant sp_executesql. Si vous devez exécuter du code sur un autre serveur, utilisez un serveur lié .

Enfin, pensez à réécrire votre code pour ne pas utiliser de curseur si possible. Les opérations basées sur des ensembles dans SQL Server sont beaucoup plus efficaces.

Étant donné que ces sujets sont tous des sujets extrêmement approfondis à eux seuls, je ne peux pas étoffer une réponse ici, mais j'espère que ces liens vous aideront. Bonne chance.

10
Randolph West

Vous devriez certainement trouver un meilleur moyen que de faire du DML via une fonction. Vous n'avez pas besoin d'attacher ce processus à une instruction SELECT. Vous pouvez simplement vider les résultats de la requête dans une table temporaire et utiliser un CURSEUR pour itérer dessus (comme vous le faites dans cette fonction), en appelant une procédure stockée pour faire ce que vous faites actuellement dans cette fonction.

Cependant, à tout le moins, vous devez ajouter quelques options à votre curseur afin qu'elles ne verrouillent pas les tables de base:

DECLARE cursor_pdppt CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
        FOR ...

Et, immédiatement après le CLOSE cursor_pdppt;, vous devez ajouter:

DEALLOCATE cursor_pdppt;
3
Solomon Rutzky