2,500万行以上のクエリの最適化


11

私はMS SQLを使用しており、同じテーブルに対して異なる基準でいくつかのクエリを実行する必要があります。最初は元のテーブルで各クエリを実行しましたが、それらはすべて何らかのフィルタリング(つまり、日付、ステータス)を共有しています。これには長い時間がかかりました(約2分)。

データ行に重複があり、すべてのインデックスがクラスタリングされていません。私の基準では4列のみに関心があり、結果はすべてのクエリについてカウントのみを出力するはずです。

:列は、必要に応じてTABLEFIELDAFTERDATE、とのそれぞれにインデックスがあるDATEとはTABLE

必要なフィールドのみを含む一時テーブルを作成した後、1:40分になりましたが、それでも非常に悪いです。

CREATE TABLE #TEMP
(
    TABLE VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    AFTER VARCHAR(1000) NULL,
    DATE DATETIME,
    SORT_ID INT IDENTITY(1,1)
)
CREATE CLUSTERED INDEX IX_ADT ON #TEMP(SORT_ID)

INSERT INTO #TEMP (TABLE, FIELD, AFTER, DATE)
    SELECT TABLE, FIELD, AFTER, DATE 
    FROM mytbl WITH (NOLOCK)
    WHERE TABLE = 'OTB' AND
    FIELD = 'STATUS'

これを実行->(216598行が影響を受けます)

すべてのクエリが日付範囲に依存しているわけではないため、クエリに含めませんでした。問題は、挿入するのに1分をはるかに超える時間がかかることです。上記の挿入には1:19分かかりました

私はいくつかのクエリに対してこのようなものを実行したいと思います:

SELECT COUNT(*) AS COUNT
FROM #TEMP
WHERE AFTER = 'R' AND
DATE >= '2014-01-01' AND
DATE <= '2015-01-01'

これは、選択よりも挿入の問題ですが、tempには元のテーブルよりも行がはるかに少ないため、テーブルを何度も通過するよりも優れている場合があります。

これをどのように最適化できますか?

編集

ソートIDを削除しましたが、問題は主に選択ではなく挿入にあると考えました。それは推測でした。

一意のフィールドまたは行がないため、どのインデックスにも一意を作成できません。

SQL Server 2012を使用しています。

テーブル情報:これはヒープであり、次のスペース使用量があります。

name    rows        reserved    data        index_size  unused
mytbl   24869658    9204568 KB  3017952 KB  5816232 KB  370384 KB

私は、生産のテーブルを変更することはできません@MikaelEriksson ...
Atieh

最適化しようとしているクエリがの形式である場合SELECT COUNT(*) AS COUNT FROM original_table WHERE AFTER = 'R' AND DATE >= '2014-01-01' AND DATE < '2015-01-01'、各(クエリ)を個別に最適化してみませんか?テーブルにインデックスを追加することはできませんか?
ypercubeᵀᴹ

2
なぜ遅いのかを判断する必要があります。ブロックされていますか?tempdbが大きくなるのを待っていますか?実行計画はひどいですか?詳細がなければ、「私のクエリが遅い」という問題を修正することはできません...
アーロンバートランド

3
まあ、私にとっては失われた原因のようです(「何も最適化することは許可されていないので、クエリを実行する必要があるたびに、一時テーブルに200K行をプッシュすることができます」)。しかし、テーブルからTABLEおよびFIELD列を削除することもでき#tempます(結局、すべての行はTABLE = 'OTB' AND FIELD = 'STATUS'特定の一時テーブルに対して持っています。)
ypercubeᵀᴹFeb

2
詳細な(そして丁寧な)コメントを追加して、編集と改善をお願いしました。それがコメントの目的です。また、使用しているSQL Serverのバージョン(SQL Server 2014など)で質問にタグを付ける必要があります。テーブルのDDLも役に立ちます(CREATE TABLEステートメント)。反対票は、質問が明確ではなかったためです。
ポールホワイト9

回答:


12

問題は主に、selectステートメントを最適化する方法です。

SELECT [TABLE], [FIELD], [AFTER], [DATE]
FROM mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB' AND
[FIELD] = 'STATUS'

冗長なプロジェクションを削除し、推定dboスキーマを追加します。

SELECT [AFTER], [DATE] 
FROM dbo.mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB'
AND FIELD = 'STATUS';

([TABLE],[FIELD]) INCLUDE ([AFTER],[DATE])SQL Serverのようなインデックスがない場合、2つの主要なオプションがあります。

  1. ヒープ全体をスキャンします(3GB以上)。または
  2. 行一致見つける[TABLE] = 'OTB'[FIELD] = 'STATUS'(使用しIDX6、その後、ヒープ(RID)ルックアップ実行)行ごとに取得する[AFTER]と、[DATE]列を。

オプティマイザがRIDルックアップでヒープスキャンまたはインデックスシークを選択するかどうかは、[TABLE] = 'OTB'および[FIELD] = 'STATUS'述部の推定選択性に依存します。シークからの推定行数が実際と一致するかどうかを確認します。そうでない場合は、統計を更新してください。その条件が合理的に選択的である場合、インデックスの使用を強制するテーブルヒントでクエリをテストします。オプティマイザが現在インデックスシークを選択している場合は、INDEX(0)またはFORCESCANヒントを使用してパフォーマンスをテストし、ヒープをスキャンします。

さらに、未使用のスペース(370MB)の一部を削除することにより、ヒープのスキャンを少し改善することができます。SQL Server 2008では、これはヒープを再構築することで実行できます。ヒープ内の未使用スペースは、多くの場合、テーブルロックを取得せずに削除を実行すると発生します(テーブルロックがない場合、空のページはヒープから割り当て解除されません)。このため、頻繁に削除が行われるテーブルは、クラスター化されたテーブルとして保存する方が適切です。

ヒープスキャンのパフォーマンスは、メモリに格納されているテーブルの量、ディスクから読み取る必要のある量、ページがいっぱいであること、永続ストレージの速度、スキャンがI / OであるかCPUバウンドであるかによって異なります(並列処理が役立ちます)。

上記のすべてを調査した後もパフォーマンスが許容できない場合は、新しいインデックスのケースを作成してみてください。使用しているSQL Serverのバージョンで使用可能な場合、指定されたクエリに対してフィルター処理可能なインデックスは次のようになります。

CREATE INDEX index_name
ON dbo.mytbl ([DATE],[AFTER])
WHERE [TABLE] = 'OTB'
AND [FIELD] = 'STATUS';

また、インデックスの圧縮が利用可能で有益な場合は、それも検討してください。ある種の新しいインデックスがなければ、特定のクエリのパフォーマンスを向上させるためにできることは比較的ほとんどありません。


Paulさん、申し訳ありませんIDX6 nonclustered located on PRIMARY TABLE, FIELD。たぶんこれはあなたが言及したことを変えるでしょうか?
Atieh 2015

6

ここでインデックスを変更する場合があると思います:

  • 実行するタスクがあります(これらの複数のクエリ)
  • データウェアハウスのボリューム(2,500万行以上)および
  • パフォーマンスの問題。

これは、SQL Server 2012で導入された非クラスター化列ストアインデックスの良いユースケースでもあります。

これらのインデックスには、テーブルを読み取り専用にする(パーティションの切り替えを除く)副作用がありますが、適切な条件下で集計クエリのパフォーマンスを変換できます。読み取り専用の側面は、インデックスまたは単純なパーティションスイッチデータをテーブルにドロップして再作成することによって管理できます。

私はあなたのセットアップを模倣するために簡単なテストリグをセットアップしましたが、パフォーマンスが大幅に向上しました。

USE tempdb
GO

SET NOCOUNT ON
GO

-- Create a large table
IF OBJECT_ID('dbo.largeTable') IS NOT NULL
DROP TABLE dbo.largeTable
GO
CREATE TABLE dbo.largeTable ( 

    [TABLE] VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    [AFTER] VARCHAR(1000) NULL,
    [DATE] DATETIME,
    SORT_ID INT IDENTITY(1,1),

    pad VARCHAR(100) DEFAULT REPLICATE( '$', 100 )
)
GO

-- Populate table
;WITH cte AS (
SELECT TOP 100000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
SELECT 
    x.tableName, 
    y.field,
    z.[after],
    DATEADD( day, rn % 1111, '1 Jan 2012' )
FROM cte c
    CROSS JOIN ( VALUES ( 'OTB' ), ( 'AAA' ), ( 'BBB' ), ( 'CCCC' ) ) x ( tableName )
    CROSS JOIN ( VALUES ( 'STATUS' ), ( 'TIME' ), ( 'POWER' ) ) y ( field )
    CROSS JOIN ( VALUES ( 'R' ), ( 'X' ), ( 'Z' ), ( 'A' ) ) z ( [after] )

CHECKPOINT

GO 5

EXEC sp_spaceused 'dbo.largeTable'
GO

SELECT MIN([DATE]) xmin, MAX([DATE]) xmax, FORMAT( COUNT(*), '#,#' ) records
FROM dbo.largeTable
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff1
GO

-- Add the non-clustered columnstore
CREATE NONCLUSTERED COLUMNSTORE INDEX _cs ON dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

-- Check query again
DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff2
GO

私の結果、6秒v 0.08秒:

ここに画像の説明を入力してください

要約すると、上司と一緒にケースを作成してインデックスを変更するか、少なくともこれらのレコードが読み取り専用のレポートテーブル/データベースに切り分けられて作業を行うことができる何らかの夜間プロセスを作成し、インデックスを追加します。そのワークロードに適しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.