web-dev-qa-db-ja.com

SQL Server 2019にアップグレードした後、関数が「メモリ不足」エラーをスローする

SQL Server 2012データベース(サイズが8GB)を、同じメモリとCPU構成で新しくセットアップされたSQL Server 2019仮想マシンに移動し、互換性レベルをSQL Server 2019に変更しました。

アプリケーション内のすべてが正常に機能しますが、2つのパラメーターを持つ1つの大きなSQLクエリで構成される1つのストアドプロシージャ(および特別なオプションはありません)を除きます。これがSP実行されると、SQL Serverプロセスのメモリが指定された最大レベルまで上がり、エラーが返されます。

「このクエリを実行するにはメモリが不足しています」

SSMSの別のクエリウィンドウで(ストアドプロシージャ内の)SQLクエリを実行すると、すぐに実行され、予想される300行が返されます。また、DBの互換性レベルを「SQL Server 2017」に変更し、ストアドプロシージャを実行すると、すべて問題ありません。

私は最初にパラメータスニッフィングの問題の可能性があると考えましたが、回避策のどれも役に立ちませんでした(例:OPTION (RECOMPILE))。

スカラー値関数の呼び出しまで問題を掘り下げました。この関数を呼び出すたびに、メモリエラーが発生します。

これが関数のDDLです(申し訳ありませんが、一部はドイツ語です)。

_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
_

以下は、テーブル定義(ドイツ語)です。

_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);
_

私はヒントを試しました:

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

...しかし、エラーを防ぐことはできませんでした。

2つのテーブル、テストデータ、および関数(GetWtmTimeは他の2つのスカラー関数に依存)を空のテストデータベースに挿入し、関数を2回実行することができました。その後、再びメモリエラーが発生しました。

11
dsungaro

原因

SQL Serverは 関数のインライン化 を試みていますが、複雑なため失敗しました。

実行中に大量のメモリを使用することは予期せぬことであり、ほぼ間違いなくバグです。

ネストされた関数の定義dbo.IstFeiertag完全な再現には必要です。

Workaround

追加 WITH INLINE = OFFを関数定義に追加します。この問題が解決されたら、そのオプションを削除して、関数のインライン展開によるパフォーマンス上のメリットを享受できるはずです。

レポートとステータス

report この問題をマイクロソフトに報告する必要があります。サポート契約がある場合は、そのルートに進んでください。または、バグレポートを ser Voice に投稿し、Intelligentqp @ Microsoft.comのIntelligent Query Processingチームにメールを送信してください。

Joe Sack (プリンシパルプログラムマネージャー、Microsoft SQL Server製品チーム)はコメントしました:

ご報告ありがとうございます。ポールホワイトは私にヘッドアップを与え、私は調査のために私たちのチームに報告しました。


解決

この問題の修正SQL Server 2019の累積的な更新2 の一部としてリリースされました。

11
Paul White 9