web-dev-qa-db-fra.com

Après la mise à niveau vers SQL Server 2019, la fonction génère une erreur de "mémoire insuffisante"

J'ai déplacé une base de données SQL Server 2012 (taille de 8 Go) vers une machine virtuelle SQL Server 2019 nouvellement installée avec la même configuration de mémoire et de processeur et j'ai changé le niveau de compatibilité en SQL Server 2019.

Tout dans mon application fonctionne bien, sauf une procédure stockée qui consiste en une grande requête SQL avec deux paramètres (et aucune option sophistiquée). Lorsque ceci SP exécuté, il laisse la mémoire du processus SQL Server monter au niveau maximum spécifié puis renvoie une erreur:

"La mémoire est insuffisante pour exécuter cette requête"

Lorsque j'exécute la requête SQL (à l'intérieur de la procédure stockée) dans une fenêtre de requête distincte de SSMS, elle s'exécute en un rien de temps et renvoie les 300 lignes attendues. De plus, lorsque je change le niveau de compatibilité de la base de données en "SQL Server 2017" et exécute la procédure stockée, tout va bien.

J'ai d'abord pensé que cela pourrait être un problème de reniflement de paramètres, mais aucune des solutions de contournement n'a aidé (par exemple OPTION (RECOMPILE)).

J'ai exploré le problème à l'appel d'une fonction scalaire. Chaque fois que j'appelle cette fonction, l'erreur de mémoire se produit.

Voici le DDL de la fonction (désolé, en partie en allemand):

CREATE FUNCTION [dbo].[GetWtmTime] (
    @WorkTimeModelID uniqueidentifier,
    @Date DATETIME,
    @SequenceNo TINYINT)
  RETURNS VARCHAR(5)
AS
BEGIN
    -- SET DATEFIRST 7; has to be executed before calling this function
    DECLARE @WtmTime VARCHAR(5)
    DECLARE @WtmWeeks INT
    DECLARE @WtmTakeHolidays BIT
    DECLARE @WtmMaxMemberCount TINYINT
    SELECT @WtmWeeks = AnzahlWochen
         , @WtmTakeHolidays = ÜbernimmtFeiertage 
         , @WtmMaxMemberCount = MaxAnzahlMitglieder
    FROM Arbeitszeitmodelle 
    WHERE ArbeitszeitmodellID = @WorkTimeModelID;
    IF @WtmWeeks = 1
    BEGIN
        IF (dbo.IstFeiertag(@Date, 0) = 1     -- Holiday
            AND @WtmMaxMemberCount = 1)
        BEGIN
            IF @WtmTakeHolidays = 0
            BEGIN 
                IF @Date >= '20130901'
                    SET @WtmTime = 'KD'
                ELSE
                    SET @WtmTime = 'ZA';
            END ELSE
            BEGIN
                IF EXISTS ( SELECT *
                            FROM AzmWochen
                            WHERE ArbeitszeitmodellID = @WorkTimeModelID
                                AND Folgenummer = @SequenceNo
                                AND AzmZeitMo IN ('KD','T')
                                AND AzmZeitDi IN ('KD','T')
                                AND AzmZeitMi IN ('KD','T')
                                AND AzmZeitDo IN ('KD','T')
                                AND AzmZeitFr IN ('KD','T')
                                AND AzmZeitSa IN ('KD','T')
                                AND AzmZeitSo IN ('KD','T') )
                    SET @WtmTime = 'T';
                ELSE
                    SET @WtmTime = 'G';
            END
        END ELSE IF DATEPART(dw, @Date) = 1             -- Sunday
            SELECT @WtmTime = AzmZeitSo FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
        ELSE IF DATEPART(dw, @Date) = 2             -- Monday
            SELECT @WtmTime = AzmZeitMo FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
        ELSE IF DATEPART(dw, @Date) = 3             -- Tuesday
            SELECT @WtmTime = AzmZeitDi FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
        ELSE IF DATEPART(dw, @Date) = 4             -- Wednesday
            SELECT @WtmTime = AzmZeitMi FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
        ELSE IF DATEPART(dw, @Date) = 5             -- Thursday
            SELECT @WtmTime = AzmZeitDo FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
        ELSE IF DATEPART(dw, @Date) = 6             -- Friday
            SELECT @WtmTime = AzmZeitFr FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
        ELSE                                            -- Saturday
            SELECT @WtmTime = AzmZeitSa FROM AzmWochen 
                WHERE ArbeitszeitmodellID = @WorkTimeModelID
                    AND Folgenummer = @SequenceNo;
    END ELSE
    BEGIN
        DECLARE @NUMWEEKS INT
        SELECT @NUMWEEKS = DATEDIFF(week, CONVERT(CHAR(10), '01.01.2000', 104), @Date)
        IF DATEPART(dw, @Date) = 1 
            SET @NUMWEEKS = @NUMWEEKS - 1;
        DECLARE @WEEKNUMBER INT 
        IF @NUMWEEKS % 2 = 0
            SET @WEEKNUMBER = 1
        ELSE
            SET @WEEKNUMBER = 2;
        IF DATEPART(dw, @Date) = 1              -- Sunday
            SELECT @WtmTime = AzmZeitSo FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
        ELSE IF DATEPART(dw, @Date) = 2     -- Monday
            SELECT @WtmTime = AzmZeitMo FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
        ELSE IF DATEPART(dw, @Date) = 3     -- Tuedsay
            SELECT @WtmTime = AzmZeitDi FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
        ELSE IF DATEPART(dw, @Date) = 4     -- Wednesday
            SELECT @WtmTime = AzmZeitMi FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
        ELSE IF DATEPART(dw, @Date) = 5     -- Thursday
            SELECT @WtmTime = AzmZeitDo FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
        ELSE IF DATEPART(dw, @Date) = 6     -- Friday
            SELECT @WtmTime = AzmZeitFr FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
        ELSE                                    -- Saturday
            SELECT @WtmTime = AzmZeitSa FROM AzmWochen 
                WHERE Folgenummer = @SequenceNo AND Wochennummer = @WEEKNUMBER 
                  AND ArbeitszeitmodellID = @WorkTimeModelID
    END
    IF @Date >= '20130901' AND @WtmTime = 'ZA'
        SET @WtmTime = 'KD';

    RETURN @WtmTime;
END


CREATE FUNCTION [dbo].[IstFeiertag] (
    @Datum DATETIME,
    @IstEvangelisch BIT)
  RETURNS INT
AS
BEGIN
    DECLARE @I INT
    DECLARE @Y INT
    DECLARE @A INT
    DECLARE @B INT
    SET @I = DATEPART(year, @Datum) / 100 - DATEPART(year, @Datum) / 400 + 4;
    SET @Y = @I - DATEPART(year, @Datum) / 300 + 11;
    SET @A = (((DATEPART(year, @Datum) % 19) * 19) + @Y) % 30;
    SET @B = (((DATEPART(year, @Datum) % 4) * 2 + 4 * DATEPART(year, @Datum) + 6 * @A + @I) % 7) + @A - 9;
    DECLARE @OstTag INT
    DECLARE @OstMon INT
    IF @B < 1
    BEGIN
        SET @OstTag = 31 + @B
        SET @OstMon = 3
    END ELSE
    BEGIN
        IF ((@B = 26) OR ((@A = 28) AND (@B = 25) AND ((11 * (@Y + 1) % 30) < 19)))
        BEGIN
            SET @B = @B - 7;
        END
        SET @OstTag = @B
        SET @OstMon = 4
    END

    DECLARE @Ostersonntag DATETIME
    SET @Ostersonntag = dbo.CreateDate(DATEPART(year, @Datum), @OstMon, @OstTag)

    IF @Datum >= @Ostersonntag
    BEGIN
        DECLARE @TAGE INT
        SET @TAGE = DATEDIFF(day, @Ostersonntag, @Datum)
        IF @TAGE = 0 OR @TAGE = 1 OR @TAGE = 39 OR @TAGE = 50 OR @TAGE = 60
        BEGIN
            RETURN 1
        END
    END
    DECLARE @TEMP INT 
    SET @TEMP = DATEPART(month, @Datum) * 100 + DATEPART(day, @Datum)
    IF @TEMP = 101 OR @TEMP = 106 OR @TEMP = 501 OR @TEMP = 815 OR @TEMP = 1026
         OR @TEMP = 1101 OR @TEMP = 1208 OR @TEMP = 1225 OR @TEMP = 1226
    BEGIN
        RETURN 1
    END 
    RETURN 0
END
GO


CREATE FUNCTION [dbo].[CreateDate] (
    @Year int, 
    @Month int, 
    @Day int)
  RETURNS DATETIME
AS
BEGIN
    declare @d datetime;
    set @d = dateadd(year,(@Year - 1753),'1/1/1753');
    set @d = dateadd(month,@Month - 1,@d);
    return dateadd(day,@Day - 1,@d)
END
GO

Voici les définitions des tableaux (en allemand):

CREATE TABLE [dbo].[Arbeitszeitmodelle]
(
    [ArbeitszeitmodellID] uniqueidentifier ROWGUIDCOL NOT NULL 
        CONSTRAINT [DF_Arbeitszeitmodelle_ArbeitszeitmodellID] DEFAULT (newid()) 
        CONSTRAINT [PK_Arbeitszeitmodelle_ArbeitszeitmodellID] PRIMARY KEY CLUSTERED,
    [Name] nvarchar(25) NOT NULL,   
    [MaxAnzahlMitglieder]  tinyint NOT NULL 
    CONSTRAINT [CK_Arbeitszeitmodelle_MaxAnzahlMitglieder] CHECK (([MaxAnzahlMitglieder] > 0) AND ([MaxAnzahlMitglieder] < 10)), 
    [AnzahlWochen] tinyint NOT NULL
    CONSTRAINT [CK_Arbeitszeitmodelle_AnzahlWochen] CHECK (([AnzahlWochen] > 0) AND ([AnzahlWochen] < 5)),
    [ÜbernimmtFeiertage] bit 
);

CREATE TABLE [dbo].[AzmWochen]
(
    [AzmWochenID] uniqueidentifier ROWGUIDCOL NOT NULL 
        CONSTRAINT [DF_AzmWochen_AzmWochenID] DEFAULT (newid()) 
        CONSTRAINT [PK_AzmWochen_AzmWochenID] PRIMARY KEY CLUSTERED,
    [Folgenummer] tinyint NOT NULL
        CONSTRAINT [CK_AzmWochen_Folgenummer] CHECK (([Folgenummer] > 0) AND ([Folgenummer] < 10)),     
        [Wochennummer] tinyint NOT NULL
        CONSTRAINT [CK_AzmWochen_Wochennummer] CHECK (([Wochennummer] > 0) AND ([Wochennummer] < 3)),
        [ArbeitszeitmodellID] uniqueidentifier NOT NULL 
        CONSTRAINT [FK_AzmWochen_ArbeitszeitmodellID] FOREIGN KEY ([ArbeitszeitmodellID]) REFERENCES [dbo].[Arbeitszeitmodelle] ([ArbeitszeitmodellID]) ON UPDATE CASCADE ON DELETE CASCADE,    
    [AzmZeitMo] varchar(5) NOT NULL,
    [AzmZeitDi] varchar(5) NOT NULL,
    [AzmZeitMi] varchar(5) NOT NULL,
    [AzmZeitDo] varchar(5) NOT NULL,
    [AzmZeitFr] varchar(5) NOT NULL,
    [AzmZeitSa] varchar(5) NOT NULL,
    [AzmZeitSo] varchar(5) NOT NULL
);
ALTER TABLE AzmWochen ADD CONSTRAINT [UQ_AzmWochen_FolgeWochen] UNIQUE ([ArbeitszeitmodellID] ASC, [Folgenummer] ASC, [Wochennummer] ASC);

J'ai essayé les indices:

  • OPTION(USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'))
  • OPTION(USE HINT('QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140'))

... mais ils n'ont pas empêché l'erreur.

J'ai inséré les deux tables, les données de test et les fonctions (GetWtmTime dépend de deux autres fonctions scalaires) dans une base de données de test vide et j'ai pu exécuter la fonction deux fois. Puis j'ai de nouveau eu l'erreur de mémoire.

11
dsungaro

Cause

SQL Server essaie de incorporer la fonction mais échoue en raison de la complexité.

Utiliser autant de mémoire tout en le faisant est inattendu et presque certainement un bug.

Une définition pour la fonction imbriquée dbo.IstFeiertag serait nécessaire pour une reproduction complète.

Workaround

Ajouter WITH INLINE = OFF à la définition des fonctions. Une fois ce problème résolu, vous devriez être en mesure de supprimer cette option pour profiter des avantages de performance de la fonction inline.

Rapports et statut

Vous devez signaler ce problème à Microsoft. Si vous avez un contrat d'assistance, suivez cette voie. Vous pouvez également publier un rapport de bogue sur ser Voice et envoyer un e-mail à l'équipe Intelligent Query Processing à [email protected].

Joe Sack (Responsable de programme principal, équipe produit Microsoft SQL Server) a commenté:

Merci d'avoir signalé. Paul White m'a prévenu et je me suis présenté à notre équipe pour enquête.


Résolution

Un correctif pour ce problème a été publié dans le cadre de mise à jour cumulative 2 pour SQL Server 2019 .

11
Paul White 9