免責事項:私は、開発チームが運用データベースを高速化できるように支援しようとしている上級システム管理者です。私たちのユーザーは傷つき、悪いです。 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を雇うように頼む以外に、次に何をすべきですか?
小さなテーブルを作成できます:
_CREATE TABLE OvertimePeriodsDates
(
dStart SMALLDATETIME,
dEnd SMALLDATETIME
)
_
...次に、1日1回get_overtime_periods_dates()
を呼び出して、このテーブルにデータを入力します。次に、_get_overtime_from_pl
_の先頭にあるget_overtime_periods_dates()
の呼び出しをOvertimePeriodsDates
に置き換えます。原則として、これはほとんど何も変更すべきではありませんが、18秒もかからない新しいクエリプランを作成するのに十分な場合があります。