約32秒かかるクエリがあります。パフォーマンスの問題を解決するために、さまざまな方法を試しました。
最初に試みたのは、日付と時刻を整数として含む新しい列を追加することです。この列を非クラスター化インデックスに追加した後、クエリは2秒で実行されます。しかし、実行計画を見ると、いくつかの警告があることがわかります。
編集:
_DECLARE @StartDate int=2010010110,--2010-01-01 10:00
@EndDate int=2019101011 --2019-10-10 11:00
SELECT newDate,
IdFluide
IdUsage,
SUM(c.[Conso-KW]) AS ConsoKW
FROM fait.Consommation AS c WITH(FORCESCAN, INDEX(0))
where newDate >= @StartDate
AND newDate <= @EndDate
AND IdSource = 1
AND IdTypeFluide = 1
AND IdUsage > 0
AND exists(select 1 from geo.DimGeographie dg
INNER JOIN geo.ActivePerimeterDetail apd
ON dg.ContractId = apd.ContractId
AND dg.IdNiveau3=apd.PerimeterId
AND apd.PerimeterLevelId=3
AND dg.ContractId = 2
AND dg.ModeGeo='PA'
AND apd.ContractId = 2
AND UserId = 8
where c.IdGeographie = dg.IdGeographie
)
GROUP BY newDate,
IdFluide,
IdUsage
_
テーブル_fait.Consommation
_には、約450万行が含まれています。
テーブル_geo.DimGeographie
_には約20K行が含まれています。
テーブル_geo.ActivePerimeterDetail
_には約43K行が含まれています。
すべてのテーブルにクラスター化インデックスと非クラスター化インデックスがあります。
私は統計を更新しようとしました、そしてそのような問題について話している多くの記事を見ました、しかしそれらが示唆するすべてを試みても私の問題は解決しません。
_ update statistics [fait].[Consommation] with fullscan
update statistics [geo].[DimGeographie] with fullscan
update statistics [geo].[ActivePerimeterDetail] with fullscan
update statistics [fait].[Consommation] [IX_GeographicalConsumption] with fullscan
update statistics geo.DimGeographie [IX_Test] with fullscan
update statistics geo.ActivePerimeterDetail [UQ_ActivePerimeterDetail] with fullscan
_
このコードをクエリのテーブルに追加すると、新しい実行プランが作成されてクエリが高速化され、クエリは1秒で実行されますが、同じ警告が表示されますWITH(FORCESCAN, INDEX(0))
インデックス:編集
[fait].[Consommation]
_非クラスター化インデックス:
_ CREATE NONCLUSTERED INDEX [IX_GeographicalConsumption] ON [fait].[Consommation]
(
[IdSource] ASC,
[IdTypeFluide] ASC,
[IdUsage] ASC,
[IdFluide] ASC,
[NewDate] ASC
)
INCLUDE (
[Conso-KW],
[Conso-M3],
[Euros],
[CO2],
[Conso-EP],
[IdGeographie]
)
WHERE ([IdUsage]>(0)) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
_
CLUSTERED COLUMNSTORE INDEX:
_ CREATE CLUSTERED COLUMNSTORE INDEX [IX_Consommation] ON [fait].[Consommation] WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [PRIMARY]
_
[geo].[DimGeographie]
_非クラスター化インデックス:
_ CREATE NONCLUSTERED INDEX [IX_Test] ON [geo].[DimGeographie]
(
[ContractId] ASC,
[ModeGeo] ASC
)
INCLUDE (
[IdGeographie],
[IdNiveau3]
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
_
CLUSTERED COLUMNSTORE INDEX:
_ CREATE CLUSTERED COLUMNSTORE INDEX [IX_DimGeographie] ON [geo].[DimGeographie] WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [PRIMARY]
_
[geo].[ActivePerimeterDetail]
_非クラスター化インデックス:
_ CREATE UNIQUE NONCLUSTERED INDEX [UQ_ActivePerimeterDetail] ON [geo].[ActivePerimeterDetail]
(
[ContractId] ASC,
[UserId] ASC,
[PerimeterLevelId] ASC,
[PerimeterId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
_
UNIQUE NONCLUSTERED INDEX:
_ CREATE UNIQUE NONCLUSTERED INDEX [UQ_ActivePerimeterDetail] ON [geo].[ActivePerimeterDetail]
(
[ContractId] ASC,
[UserId] ASC,
[PerimeterLevelId] ASC,
[PerimeterId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
_
何度も試した結果、2つのテーブルを組み合わせる必要があることがわかりました[fait].[Consommation]
および[geo].[DimGeographie]
を1つのテーブルに追加し、この新しいテーブルに図表化されていないインデックスを追加しました。
これで、クエリは1秒未満で実行されます。
あなたの情報に基づいて、テーブルとデータをほぼ複製することができました
DDLおよびDML:
CREATE TABLE [dbo].[Consommation](IdRef bigint IDENTITY(1,1) PRIMARY KEY NOT NULL,IdInterface int,IdSource int,
IdUsage int,newDate int,[Conso-KW] decimal(19,6),IdTypeFluide int,IdFluide int,
IdGeographie int);
-- 2540563 matching rows
INSERT INTO [dbo].[Consommation] WITH(TABLOCK) (IdUsage,IdInterface ,IdSource,newDate,[Conso-KW],IdTypeFluide,IdFluide,IdGeographie)
SELECT TOP(2540563)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)),
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)),
1,
2010010110 + ROW_NUMBER() OVER(ORDER BY (SELECT NULL)), -- will technically be incorrect dates but still match
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)),
1,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)),
5
FROM MASTER..SPT_VALUES spt1
CROSS APPLY master..spt_values spt2;
CREATE TABLE [dbo].DimGeographie(IdGeographie int identity(1,1) PRIMARY KEY NOT NULL
,ContractId int,IdNiveau3 int,
ModeGeo varchar(10));
INSERT INTO [dbo].DimGeographie(ContractId,IdNiveau3,ModeGeo)
SELECT TOP(241)
2,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)),
'PA'
FROM MASTER..SPT_VALUES spt1
CROSS APPLY master..spt_values spt2;
CREATE TABLE [dbo].ActivePerimeterDetail(ActivePerimeterDetailId int identity(1,1) PRIMARY KEY NOT NULL
,ContractId int,PerimeterId int
,PerimeterLevelId int, UserId int)
INSERT INTO [dbo].ActivePerimeterDetail(ContractId,PerimeterId,PerimeterLevelId,UserId)
SELECT TOP(58)
2,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)),
3,
8
FROM MASTER..SPT_VALUES spt1
CROSS APPLY master..spt_values spt2;
推定の違いは、テーブル間の結合によるものと思われます。オプション(再コンパイル)を追加すると、推定値が100Kから600Kに高くなりました。しかし、明らかに、これではtempdbの停止を停止するには不十分でした。
ほぼ正しい見積もりを得ることができた1つの方法は、一時テーブルに値を格納することにより、ロジックを2つに分割することです。この方法では、tempdbの流出は発生しませんでした。
私の最後のクエリ
DECLARE @StartDate int=2010010110,--2010-01-01 10:00
@EndDate int=2019101011 --2019-10-10 11:00
SELECT
newDate,
IdFluide,
IdUsage,
c.[Conso-KW],
c.IdGeographie,
dg.ContractId,
dg.IdNiveau3
INTO #TEMP
FROM dbo.Consommation AS c
INNER JOIN dbo.DimGeographie dg
ON c.IdGeographie = dg.IdGeographie
where newDate >= @StartDate
AND newDate <= @EndDate
AND IdSource = 1
AND IdTypeFluide = 1
AND IdUsage > 0
AND dg.ContractId = 2
AND dg.ModeGeo='PA'
OPTION(RECOMPILE);
SELECT newDate,
IdFluide,
IdUsage,
SUM(c.[Conso-KW]) AS ConsoKW
FROM #TEMP c
WHERE exists(select 1 from dbo.ActivePerimeterDetail apd
WHERE c.ContractId = apd.ContractId
AND c.IdNiveau3=apd.PerimeterId
AND apd.PerimeterLevelId=3
AND apd.ContractId = 2
AND UserId = 8
)
GROUP BY newDate,
IdFluide,
IdUsage
OPTION(RECOMPILE);
DROP TABLE #TEMP;
FullScanで統計を更新しようとすると
例、
_Update STATISTICS [fait].[Consommation] IX_GeographicalConsumption WITH FULLSCAN
_
次に、predicate
でinequality (<>)
を使用しないでください。他の方法を使用してください。
ところで、IdUsage
のすべての可能な値は何ですか?
_geo.DimGeographie dg
_および_geo.ActivePerimeterDetail apd
_の列が結果セットで必要ない場合は、EXISTS
を使用します。
返すというこの保証は、結合からの行数のみを必要とします。
_ SELECT IdFluide,
newDate,
IdUsage,
SUM(c.[Conso-KW]) AS ConsoKW
FROM fait.Consommation AS c
where
IdSource = 1
AND IdTypeFluide = 1
AND UserId = 8
AND IdUsage > 0-- remove <>
AND newDate >= @StartDate
AND newDate <= @EndDate)
and exists(select 1 from geo.DimGeographie dg
INNER JOIN geo.ActivePerimeterDetail apd
ON dg.ContractId = apd.ContractId
AND dg.IdNiveau3=apd.PerimeterId
AND apd.PerimeterLevelId=3
AND dg.ContractId = 2
and dg.ModeGeo='PA'
AND apd.ContractId = 2
where c.IdGeographie = dg.IdGeographie)
GROUP BY IdFluide,
newDate,IdUsage
_
インデックスでは、最も選択的な列が最も左側にあるはずです。
同様に、クエリでは、最も選択的な述語を最初に記述します。場合によってはそれが役立ちます。
以下のオブジェクトの_Estimated rows
_と_Actual rows
_には大きな違いがあります
これは、統計が更新されていないか、特定のインデックスのデータ分布が歪んでいるために発生します。
新規編集:テーブル_[fait].[Consommation]
_の良い点は、すべての列がINT
、DateTime
タイプであることです。
悪い点は、テーブル間の関係を定義する非常に多くの複合キーがあることです。どの列が実際にPKであり、CIがどのように入力されるかは明らかではありません。
identity
int(または増え続ける)クラスター化インデックスである列が1つあるはずです。
各テーブルの各インデックス列の_data distribution
_、selectivity
を知っておくことが重要です。作成するインデックスと複合インデックスの順序を決定できるのは1人だけです。
_Composite Nonclustered Index
_で_Filtered Index
_を_IdUsage > 0
_に変更します。
_CREATE NONCLUSTERED INDEX IX_GeographicalConsumption
ON [fait].[Consommation] (IdSource,IdTypeFluide,IdUsage,IdGeographie,IdFluide,newDate) include(Conso-KW)
where IdUsage>0
GO
_
Selectivity
について知らないため、インデックスリストの列の順序についてはわかりません。
注意:Predicate
に参加する列はインデックスリストにありますが、Include
リストにはありません。
インデックス_[IX_Test] ON [geo].[DimGeographie]
_のように間違っています。
_CREATE NONCLUSTERED INDEX [IX_Test] ON [geo].[DimGeographie]
(
[ContractId] ASC,
[ModeGeo] ASC,
[IdGeographie],
[IdNiveau3]
)
_
再度注文して決定または説明します。
同じ順序で結論:
1)各テーブルから返される行数が正しいかどうかを判断します。正しくない場合は、Query/where条件を修正します。インデックスに関するものではありません。
2)適切なインデックス計画
3)更新された統計
4)クエリのメモリ許可は推定行数に基づいています。推定行は少ないため、許可されるメモリは少なくなります。これは、非常に巨大な実際の行数を処理するのに十分なメモリではありません。したがって、TempDBに流出します。
あなたの場合、単一レベルの流出があります。この流出レベルは、_Actual rows
_が増加し、_grant memory
_が減少するにつれて増加する可能性があります。
したがって、ポイント1)および2)の方が重要です。
最後に、
最新の実行プランから、コストはWITH(FORCESCAN, INDEX(0))
の後でのみ増加することが明らかです。したがって、インデックスヒントは必要ありません。