3

現在、約 1 億行を含むテーブルで特定の日付の最小 ID を照会しているときに、データベースで奇妙な動作に直面しています。クエリは非常に単純です。

SELECT MIN(Id) FROM Connection WITH(NOLOCK) WHERE DateConnection = '2012-06-26'

このクエリは決して終了しません。少なくとも何時間も実行させました。DateConnection 列はインデックスではなく、インデックスにも含まれていません。したがって、このクエリがかなり長く続く可能性があることは理解できます。しかし、数秒で実行される次のクエリを試しました。

SELECT Id FROM Connection WITH(NOLOCK) WHERE DateConnection = '2012-06-26'

30万行を返します。

私のテーブルは次のように定義されています:

CREATE TABLE [dbo].[Connection](  
    [Id] [bigint] IDENTITY(1,1) NOT NULL,  
    [DateConnection] [datetime] NOT NULL,  
    [TimeConnection] [time](7) NOT NULL,  
    [Hour]  AS (datepart(hour,[TimeConnection])) PERSISTED NOT NULL,  
    CONSTRAINT [PK_Connection] PRIMARY KEY CLUSTERED   
    (  
        [Hour] ASC,  
        [Id] ASC  
    )  
)

また、次のインデックスがあります。

CREATE UNIQUE NONCLUSTERED INDEX [IX_Connection_Id] ON [dbo].[Connection]  
(  
    [Id] ASC  
)ON [PRIMARY]

この奇妙な動作を使用して見つけた解決策の 1 つは、次のコードを使用することです。しかし、このような単純なクエリにはかなり重いように思えます。

create table #TempId
(
    [Id] bigint
)
go

insert into #TempId
select id from partitionned_connection with(nolock) where dateconnection = '2012-06-26'

declare @displayId bigint
select @displayId = min(Id) from #CoIdTest

print @displayId 
go

drop table #TempId
go

誰かがこの行動に直面したことがありますか?その原因は何ですか? 最小集計はテーブル全体をスキャンしていますか? そして、これがなぜ単純な選択がそうでないのですか?

4

4 に答える 4

5

この問題の根本的な原因は、整列されていない非クラスター化インデックスと、Martin Smith が指摘する統計上の制限との組み合わせにあります(詳細については、別の質問に対する彼の回答を参照してください)。

テーブルは[Hour]次の行に沿って分割されます。

CREATE PARTITION FUNCTION PF (integer)
AS RANGE RIGHT
FOR VALUES (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23);

CREATE PARTITION SCHEME PS
AS PARTITION PF ALL TO ([PRIMARY]);

-- Partitioned
CREATE TABLE dbo.Connection
(
    Id              bigint IDENTITY(1,1) NOT NULL,
    DateConnection  datetime NOT NULL,
    TimeConnection  time(7) NOT NULL,
    [Hour]  AS (DATEPART(HOUR, TimeConnection)) PERSISTED NOT NULL,
    
    CONSTRAINT [PK_Connection]
    PRIMARY KEY CLUSTERED
    (  
        [Hour] ASC,  
        [Id] ASC  
    )
    ON PS ([Hour])
);

-- Not partitioned
CREATE UNIQUE NONCLUSTERED INDEX [IX_Connection_Id]
ON dbo.Connection
(  
    Id ASC
)ON [PRIMARY];

-- Pretend there are lots of rows
UPDATE STATISTICS dbo.Connection WITH ROWCOUNT = 200000000, PAGECOUNT = 4000000;

クエリと実行プランは次のとおりです。

SELECT 
    MinID = MIN(c.Id)
FROM dbo.Connection AS c WITH (READUNCOMMITTED) 
WHERE
    c.DateConnection = '2012-06-26';

選択したプラン

オプティマイザはインデックス ( で順序付け) を利用して集計を-Idに変換します。これは、最小値が定義上、順序付けられたストリームで検出される最初の値であるためです。(非クラスター化インデックスもパーティション化されている場合、必要な順序が失われるため、オプティマイザーはこの戦略を選択しません)。MINTOP (1)

少し複雑なのは、句に述語を適用する必要があることです。これには、値WHEREを取得するためにベース テーブルへのルックアップが必要です。DateConnectionMartin が言及している統計上の制限は、オプティマイザが、順序付けられたインデックスから 119 行をチェックするだけで、DateConnection一致する値を持つ行を見つけることができると見積もる理由を説明していWHERE clauseます。DateConnectionとの値の間の隠れた相関関係Idは、この推定値が非常に遠いことを意味します。

興味がある場合は、Compute Scalar がキー ルックアップを実行するパーティションを計算します。非クラスター化インデックスの各行に対して、 のような式を計算し、[PtnId1000] = Scalar Operator(RangePartitionNew([dbo].[Connection].[Hour] as [c].[Hour],(1),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)))これがルックアップ シークの先頭キーとして使用されます。ネストされたループの結合にはプリフェッチ (先読み) がありますが、TOP (1)最適化に必要な並べ替えを維持するには、順序付けされたプリフェッチが必要です。

解決

Id各値の最小値を見つけてHourから、1 時間あたりの最小値の最小値を取得することで、(クエリ ヒントを使用せずに) 統計上の制限を回避できます。

-- Global minimum
SELECT 
    MinID = MIN(PerHour.MinId)
FROM 
(
    -- Local minimums (for each distinct hour value)
    SELECT 
        MinID = MIN(c.Id)
    FROM dbo.Connection AS c WITH(READUNCOMMITTED) 
    WHERE
        c.DateConnection = '2012-06-26' 
    GROUP BY
        c.[Hour]
) AS PerHour;

実行計画は次のとおりです。

シリアルプラン

並列処理が有効になっている場合は、次のようなプランが表示されます。これは、並列インデックス スキャンとマルチスレッド ストリーム集計を使用して結果をさらに高速に生成します。

並行計画

于 2012-12-16T06:30:11.913 に答える
1

インデックス ヒントを必要としない方法で問題を修正するのが賢明かもしれませんが、簡単な解決策は次のとおりです。

SELECT MIN(Id) FROM Connection WITH(NOLOCK, INDEX(PK_Connection)) WHERE DateConnection = '2012-06-26'

これにより、テーブル スキャンが強制されます。

または、これを試してみてください。おそらく同じ問題が発生します。

select top 1 Id
from Connection
WHERE DateConnection = '2012-06-26'
order by Id
于 2012-07-26T21:46:31.733 に答える
-1

すべてのレコードを調べるよりも最小値を見つけるのに時間がかかることは理にかなっています。並べ替えられていない構造の最小値を見つけるには、それを 1 回トラバースするよりもはるかに時間がかかります (MIN() は ID 列を利用しないため、並べ替えられません)。ID列を使用しているため、ネストされた選択があり、指定された日付のレコードセットから最初のレコードを取得できます。

于 2012-07-25T08:29:31.933 に答える
-2

あなたの場合、NCインデックススキャンが問題になります。一意の非クラスター化インデックススキャンを使用しており、1億行の各行に対してクラスター化インデックスをトラバースするため、数百万のioが発生します(通常、インデックスの高さは4です次に、非クラスター化インデックス リーフ ページの 1 億 *4 IO の +index スキャンが発生する可能性があります。オプティマイザーは、最小値を取得するために strem 集計を回避するために、このインデックスを選択したに違いありません。 min が必要な列 (インデックスがある場合は効率的であり、その場合、返される行を取得するとすぐに計算は必要ありません)、2番目はハッシュ集計を使用できます(ただし、通常はグループ化すると発生します)。3番目はストリーム集計です。ここでは、修飾されたすべての行をスキャンし、最小値を常に保持し、すべての行がスキャンされたときに最小値を返します..

ただし、min を指定しないクエリでクラスター化インデックス スキャンが使用された場合は、読み取るページ数が少なくなり、IO も少なくなるため、高速になります。

オプティマイザーがクラスター化されていないインデックスでインデックス スキャンを選択した理由は次のとおりです。ストリーム集計に関連する計算を回避して、ストリーム集計を使用して最小値を見つけることは確かですが、この場合、ストリーム集計を使用しないと、はるかにコストがかかります。これは推定に依存するため、表の統計は最新ではないと思います。

まず、統計が最新かどうかを確認します。統計が最後に更新されたのはいつですか?

したがって、問題を回避するには、次の手順を実行します。1. 最初にテーブルの統計情報を更新します。問題を解決する必要があると確信しています。2. update stats を使用できない場合、または update stats が計画を変更せず、まだ NC インデックス スキャンを使用している場合は、クラスター化インデックス スキャンを強制して、使用する IO を減らし、その後にストリーム集計を使用して最小値を取得できます。

于 2012-07-26T20:33:55.933 に答える