web-dev-qa-db-ja.com

オペレーターはtempdbを使用してデータを流出させました。推定行が実際の行より少ない

約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行が含まれています。

すべてのテーブルにクラスター化インデックスと非クラスター化インデックスがあります。 ![![enter image description here![enter image description here

私は統計を更新しようとしました、そしてそのような問題について話している多くの記事を見ました、しかしそれらが示唆するすべてを試みても私の問題は解決しません。

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

XML実行計画

インデックス:編集

  1. テーブル_[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]
_
  1. テーブル_[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]
_
  1. テーブル_[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
Omar AMEZOUG

何度も試した結果、2つのテーブルを組み合わせる必要があることがわかりました[fait].[Consommation]および[geo].[DimGeographie]を1つのテーブルに追加し、この新しいテーブルに図表化されていないインデックスを追加しました。

これで、クエリは1秒未満で実行されます。

1
Omar AMEZOUG

あなたの情報に基づいて、テーブルとデータをほぼ複製することができました

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;

一時テーブル挿入の計画

最終クエリの計画

2
Randi Vertongen

FullScanで統計を更新しようとすると

例、

_Update STATISTICS [fait].[Consommation] IX_GeographicalConsumption WITH FULLSCAN
_

次に、predicateinequality (<>)を使用しないでください。他の方法を使用してください。

ところで、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_には大きな違いがあります

  1. IX_GeographicalConsumption
  2. IX_Test

これは、統計が更新されていないか、特定のインデックスのデータ分布が歪んでいるために発生します。

新規編集:テーブル_[fait].[Consommation]_の良い点は、すべての列がINTDateTimeタイプであることです。

悪い点は、テーブル間の関係を定義する非常に多くの複合キーがあることです。どの列が実際に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))の後でのみ増加することが明らかです。したがって、インデックスヒントは必要ありません。

2
KumarHarsh