web-dev-qa-db-ja.com

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

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

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

必要な列:TABLEFIELDAFTERDATE、およびDATETABLE

必要なフィールドのみを含む一時テーブルを作成した後、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
11
Atieh

問題は主に、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'predicatesの推定選択性に依存します。シークからの推定行数が実際と一致するかどうかを確認します。そうでない場合は、統計を更新してください。インデックスの使用を強制するテーブルヒントを使用してクエリをテストしますその条件が合理的に選択的である場合。オプティマイザが現在インデックスシークを選択している場合は、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';

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

12
Paul White 9

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

  • 実行するタスクがあります(これらの複数のクエリ)
  • データウェアハウスのボリューム(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秒:

enter image description here

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

6
wBob