web-dev-qa-db-ja.com

テーブル値関数は高速に実行されますが、別の関数からは低速です

免責事項:私は、開発チームが運用データベースを高速化できるように支援しようとしている上級システム管理者です。私たちのユーザーは傷つき、悪いです。 SQLが苦手な理由で私を撃たないでください

データベースサーバーは、最新の更新が適用されたWindows Server 2008 R2であり、SQL Server 2008 R2 Enterprise Edition with SP2 + CU6を実行しています。同じOS/SQLサーバーがインストールされたテストサーバーもあります。本番サーバーには96 GBのRAMが搭載されていますが、現時点では76 GBが無料です。テストサーバーには16 GBのRAMが搭載されていますが、現時点では8 GBが無料です。本番サーバーは最大90 GBのRAMを使用するように設定されていますが、テストサーバーは最大12 GBのRAMを使用するように設定されています。

編集:重要な部分について言及するのを忘れていました..本番データベースはオフサイトでミラーリングされていますが(非同期)、テストDBはミラーリングされていません。

以下の機能があります。

pl.get_overtime_from_pl(メイン関数)

USE [Customapp]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER FUNCTION [pl].[get_overtime_from_pl]
    (
      -- Add the parameters for the function here
      @dt SMALLDATETIME ,
      @officeId INT = 9999 ,
      @candId INT = 0 ,
      @onlyOverTime BIT = 0
    )
RETURNS @OvertimeRows TABLE
    (
      -- Add the column definitions for the TABLE variable here
      CandId INT ,
      CandName VARCHAR(100) ,
      OfficeName VARCHAR(60) ,
      OfficeId INT ,
      DateFrom CHAR(8) ,
      DateTo CHAR(8) ,
      ConsName VARCHAR(100) ,
      ConsId INT ,
      NormalTime DECIMAL(9, 2) ,
      OverTime DECIMAL(9, 2) ,
      TwoWeekTendency DECIMAL(9, 2) ,
      MonthTendency DECIMAL(9, 2) ,
      PeriodId VARCHAR(10) ,
      CompanyId INT
    )
AS 
    BEGIN   
        IF @candId > 0 
            SET @officeId = 9999
        IF @officeId = 0 
            SET @officeId = 9999

        DECLARE @dStart SMALLDATETIME ,
            @dEnd SMALLDATETIME ,
            @dStart2weekTendency SMALLDATETIME ,
            @dStartMonthTendency SMALLDATETIME
        SELECT  @dStart = dStart ,
                @dEnd = dEnd
        FROM    pl.get_overtime_periods_dates(GETDATE())



    --Find Start Date for this 2-week period
        SET @dStart2weekTendency = DATEADD(dd, -( DAY(@dEnd) - 1 ), @dEnd)
        IF DAY(@dEnd) >= 16 
            SET @dStart2weekTendency = DATEADD(dd, 15, @dStart2weekTendency)

    --Find start date for the current monthperiod
        SET @dStartMonthTendency = DATEADD(m, 11, @dStart) 

    --Bitwise
    -- 1 = OverTime
    -- 2 = Normal Time
    -- 3 = 1 or 2 - both
        DECLARE @iWageType INT
        IF @onlyOverTime = 1 
            SET @iWageType = 1
        ELSE 
            SET @iWageType = 3

        DECLARE @2weekPeriodFrom INT ,
            @monthPeriodFrom INT

        SELECT  @2weekPeriodFrom = MIN(yearperiod)
        FROM    pl.get_periods(@dStart2weekTendency, @dEnd) d

        SELECT  @monthPeriodFrom = MIN(yearperiod)
        FROM    pl.get_periods(@dStartMonthTendency, @dEnd) d




        INSERT  INTO @OvertimeRows
                SELECT  h.employeenumber CandId ,
                        ka.FNAME + ' ' + ka.SNAME CandName ,
                        a.NAME OfficeName ,
                        a.ID OfficeId ,
                        p.toUkeLonnFDT DateFrom ,
                        p.toUkeLonnTDT DateTo ,
                        ko.firstname + ' ' + ko.lastname ConsName ,
                        ko.id ConsId ,
                        SUM(h.NormalTime) NormalTime ,
                        SUM(OverTime) OverTime ,
                        SUM(CASE WHEN h.yearperiod >= @2weekPeriodFrom
                                 THEN OverTime
                                 ELSE 0
                            END) TwoWeekTendency ,
                        SUM(CASE WHEN h.yearperiod >= @monthPeriodFrom
                                 THEN OverTime
                                 ELSE 0
                            END) MonthTendency ,
                        h.yearperiod ,
                        h.companyid
                FROM    pl.get_worktime_from_pl(@dStart, @dEnd, @iWageType) h
                        INNER JOIN SalaryDatabase.dbo.Period p ON p.yearperiod = h.yearperiod
                                                              AND h.companyid = p.companyid
                        INNER JOIN ( SELECT employeenumber ,
                                            companyid ,
                                            CAST(homedept AS SMALLINT) homedept_int ,
                                            persNa
                                     FROM   SalaryDatabase.dbo.personal WITH ( NOLOCK )
                                     WHERE  ISNUMERIC(homedept) = 1
                                   ) k ON k.employeenumber = h.employeenumber
                                          AND k.companyid = h.companyid
                        INNER JOIN AVDELING a ON a.ID = k.homedept_int
                        INNER JOIN KANDIDAT ka ON h.employeenumber = ka.ID
                        INNER JOIN KONSULENT ko ON ko.ID = ka.KONSULENT_ID
                        INNER JOIN dbo.fn_get_groupoffices(@officeid) offices ON offices.id = a.id
                WHERE   ( @candId = 0
                          OR @candId = h.employeenumber
                        )
                GROUP BY h.employeenumber ,
                        ka.FNAME + ' ' + ka.SNAME ,
                        h.yearperiod ,
                        p.toUkeLonnFDT ,
                        p.toUkeLonnTDT ,
                        a.ID ,
                        a.NAME ,
                        ko.ID ,
                        ko.fornavn + ' ' + ko.etternavn ,
                        h.companyid
                ORDER BY h.employeenumber

        RETURN 
    END

pl.get_overtime_periods_dates(前の関数はこれを使用します)

USE [Customapp]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [pl].[get_overtime_periods_dates] 
(
    -- Add the parameters for the function here
    @dt smalldatetime
)
RETURNS 
@dates TABLE 
(
    -- Add the column definitions for the TABLE variable here
    dStart smalldatetime, 
    dEnd smalldatetime
)
AS
BEGIN
    -- Fill the table variable with the rows for your result set
    declare @dStart smalldatetime, @dEnd smalldatetime
        --Find Start date for 12 month period
    if day(@dt)>=23 
    begin
        set @dEnd=dateadd(d,14,DATEADD(mm, DATEDIFF(mm, 0, @dt ), 0))
    end
    else if day(@dt)<9
    begin
        set @dEnd=dateadd(mm,-1, dateadd(d,14,DATEADD(mm, DATEDIFF(mm, 0, @dt ), 0)))
    end
    else
    begin
        set @dEnd=dateadd(d,-1,DATEADD(mm, DATEDIFF(mm, 0, @dt  ), 0))
    end 
    set @dStart=dateadd(d,1,dateadd(yyyy,-1,@dEnd))

    insert into @dates
    values(@dStart, @dEnd)

    RETURN 
END

この関数をクエリとして直接実行すると、1秒以内に結果が得られます。同時に、サーバーのI/Oを監視していますが、tempdbには触れていません。

この関数をクエリから呼び出すと、次のようになります。

USE [Customapp]
GO
DECLARE @date DATETIME = '20131104', @office INT = 9999, @candidate INT = 0, @onlyovertime BIT = 1;
SELECT * FROM pl.get_overtime_from_pl (@date,@office,@candidate,@onlyovertime)

その後、突然結果が出るまで18秒かかります。 tempdb(またはDB、それらを4つのファイルに分割しました-コアごとに1つ)が、利用可能なすべてのIOPSで屋上を走っています。

ここでのvery奇妙な部分は、関数と上記のクエリがテストサーバーで1秒以内に実行されることです。私はサーバーのオプションを見てきましたが、それらは同じに見えます。唯一の大きな違いは、運用サーバーで毎晩の保守計画(インデックスの再編成、統計の更新)を行うことです。

私はこれを理解するためにSQLプロファイラーに座っています。 SSMSは、カスタムアプリケーションとは少し異なる接続設定、つまり「SET ARITHABORT OFF」を使用していますが、SSMSのデフォルトはARITHABORT ONです。数日前、私はそれを前後に切り替えてみましたが、突然両方のクエリが高速になりました(1秒未満)。私が今日同じことを試みているとき、ARITHABORTはまったく効果がないようです。2番目のクエリは、always本番サーバーで低速です。

私の非DBAの観点からは、関数はキャッシュされたメモリ内の結果を使用できますが、クエリは使用できないようですか?関数が使用していないのに、2番目のクエリがtempdbを使用している理由を理解できません。

CTOにDBAを雇うように頼む以外に、次に何をすべきですか?

4
pauska

小さなテーブルを作成できます:

_CREATE TABLE OvertimePeriodsDates
(
dStart SMALLDATETIME,
dEnd   SMALLDATETIME
)
_

...次に、1日1回get_overtime_periods_dates()を呼び出して、このテーブルにデータを入力します。次に、_get_overtime_from_pl_の先頭にあるget_overtime_periods_dates()の呼び出しをOvertimePeriodsDatesに置き換えます。原則として、これはほとんど何も変更すべきではありませんが、18秒もかからない新しいクエリプランを作成するのに十分な場合があります。

2