シナリオ
かつては、ETLプロセスに参加し、多数のサードパーティソースからのさまざまな形式のファイルの受信カタログとして機能するステージングデータベースが小さな会社にありました。EはDTSパッケージを介して処理され、監査または制御のための制御構造はほとんどありませんでしたが、「十分十分」と見なされ、すべての意図および目的はそうでした。
E部分によって提供されるデータは、少数の若くて有能なプログラマーによって開発および管理される単一のアプリケーションによる消費を目的としています。当時のデータウェアハウジング技術に関する経験や知識はありませんが、アプリケーションコードから独自のTおよびLプロセスを設定および作成しました。驚いたことに、これらの未熟なソフトウェアエンジニアは、部外者が「理想的とは言えない車輪」と呼ぶものを発明しましたが、「現在十分」を常に存在するサービスレベルとして、運用フレームワークを提供することができました。
しばらくの間、密に結合された領域ではすべてが順調で、ステージングカタログは12のサードパーティのデータをごちそうし、アプリケーションによって供給されていました。アプリケーションが成長するにつれて、その欲求も大きくなりましたが、熟練した白人の騎士の開発者がシステムを監視しているため、これらの欲求は、多くの場合、迅速に対処されました。
しかし、もちろん黄金時代は永遠に続くことはできませんでした。アプリケーションの成功によって繁栄がもたらされ、ビジネスはどんどん成長しました。それが成長するにつれて、ステージング環境とアプリケーションはそれとともに成長することを余儀なくされました。すべての警戒のために、ほんの一握りのヒーロー開発者は、現在の拡張システムを維持するのに追いつくことができず、消費者は自分のデータを受け取る資格がありました。もはやそれは彼らが必要とするもの、あるいは望んでいるものの問題ではなかったが、大衆は彼らがそれに値するだけでもっと多くを要求すると感じた。
ビジネスは盗品でいっぱいの金庫で武装して、市場に手を差し伸べ、成長し続けるシステムをサポートするのを助けるために開発者と管理者を雇いました。あらゆる精神の傭兵が会社に集まりましたが、この成長により、利用可能な専門家のガイダンスにはほとんど影響がありませんでした。新しい開発者と管理者は、欲求不満のせいですべての戦争が起きるまで、自家製のスイートの複雑さを理解するのに苦労しました。各部門はすべての問題を単独で解決しようと試み始め、お互いに取り組むよりもお互いに取り組むためにより多くのことをしました。単一のプロジェクトまたはイニシアチブは、それぞれがわずかに異なるいくつかの異なる方法で実装されます。そのすべての緊張は白い騎士の一部にとっては大きすぎることが判明し、彼らが倒れると帝国は崩壊しました。間もなく、システムは混乱状態になりました。
スパゲッティコードの悪質な約束に対するこれらの分野の変化にもかかわらず、会社は耐えました。結局のところ、それは「十分に良い」でした。
チャレンジ
いくつかの政権交代と後の採用担当者の採用で、私は会社の雇用に気づきました。大戦から長い年月が経ちましたが、被害は依然として非常に目に見えます。システムのE部分のいくつかの弱点に対処し、DTSパッケージをSSISにアップグレードすることを装っていくつかのコントロールテーブルを追加できました。これらは現在、実際のデータウェアハウジングの専門家が通常の作成時に使用しています文書化されたTおよびLの交換。
最初のハードルは、値を切り捨てたり、ネイティブデータタイプを変更したりすることなく、再ロードとパージのためのいくつかの制御キーを含める方法で、サードパーティのファイルからデータをインポートすることでした。これはすべてうまくいきましたが、アプリケーションはこれらの新しいテーブルにシームレスで透過的な方法でアクセスできる必要がありました。DTSパッケージはテーブルにデータを入力し、アプリケーションによって直接読み取られます。SSISのアップグレードはQAの理由で並行して実行する必要がありますが、これらの新しいパッケージにはさまざまな制御キーが含まれ、パーティションスキームも活用します。実際のメタデータの変更だけでなく、いずれにしても新しいテーブルを完全に保証できるほど重要になる可能性があるため、新しいSSISパッケージには新しいテーブルが使用されました。
信頼性の高いデータインポートが機能し、倉庫チームによって使用されているため、実際の課題は、ステージング環境に直接アクセスするアプリケーションに新しいデータを提供することであり、アプリケーションコードへの影響は最小限(「いいえ」)です。このため、私はビューを使用することを選択しました。テーブルの名前をdbo.DailyTransaction
to などに変更し、ビューdbo.DailyTranscation_LEGACY
のdbo.DailyTransaction
オブジェクト名を再利用します。LEGACY
指定されたテーブル。これらのテーブルに含まれる長年のデータを再ロードすることはビジネスの観点からの選択肢ではないため、新しいSSISで作成されパーティション化されたテーブルが本番環境に移行するため、古いDTSインポートはオフになり、アプリケーションは新しいテーブルの新しいデータにもアクセスします。この時点で、ビューは更新され、新しいテーブル(dbo.DailyTransactionComplete
たとえば)からデータを選択できるようになります(使用できない場合はレガシーテーブルから選択します)。
実際、次のようなことが行われています。
CREATE VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransaction_LEGACY l
WHERE NOT EXISTS ( SELECT 1
FROM dbo.DailyTransactionComplete t
WHERE t.FileDate = l.FileDate );
論理的には健全ですが、これは多くの集計ケースではまったくうまく機能せず、一般に、レガシーテーブル内のデータに対してフルインデックススキャンを実行する実行プランになります。これはおそらく数千万のレコードでは問題ありませんが、数億のレコードではそれほどではありません。後者が実際に当てはまるので、私は「創造的」であることに頼らざるを得ませんでした。その結果、インデックス付きビューを作成することになりました。
ここで私が設定した小さなテストケースを示します。FileDate
制御キーがデータウェアハウス互換DateCode_FK
ポートに移植されており、当面は検索可能な新しいテーブルに対するクエリがどれほど少ないかを示しています。
USE tempdb;
GO
SET NOCOUNT ON;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction_LEGACY'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.DailyTransaction_LEGACY;
CREATE TABLE dbo.DailyTransaction_LEGACY
(
DailyTransaction_PK BIGINT IDENTITY( 1, 1 ) NOT NULL,
FileDate DATETIME NOT NULL,
Foo INT NOT NULL
);
INSERT INTO dbo.DailyTransaction_LEGACY ( FileDate, Foo )
SELECT DATEADD( DAY, ( 1 - ROW_NUMBER()
OVER( ORDER BY so1.object_id ) - 800 ) % 1000,
CONVERT( DATE, GETDATE() ) ),
so1.object_id % 1000 + so2.object_id % 1000
FROM sys.all_objects so1
CROSS JOIN sys.all_objects so2;
ALTER TABLE dbo.DailyTransaction_LEGACY
ADD CONSTRAINT PK__DailyTrainsaction
PRIMARY KEY CLUSTERED ( DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransactionComplete'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.DailyTransactionComplete;
CREATE TABLE dbo.DailyTransactionComplete
(
DailyTransaction_PK BIGINT IDENTITY( 1, 1 ) NOT NULL,
DateCode_FK INTEGER NOT NULL,
Foo INTEGER NOT NULL
);
INSERT INTO dbo.DailyTransactionComplete ( DateCode_FK, Foo )
SELECT TOP 100000
CONVERT( INTEGER, CONVERT( VARCHAR( 8 ), DATEADD( DAY,
( 1 - ROW_NUMBER() OVER( ORDER BY so1.object_id ) ) % 100,
GETDATE() ), 112 ) ),
so1.object_id % 1000
FROM sys.all_objects so1
CROSS JOIN sys.all_objects so2;
ALTER TABLE dbo.DailyTransactionComplete
ADD CONSTRAINT PK__DailyTransaction
PRIMARY KEY CLUSTERED ( DateCode_FK, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 100 );
END;
GO
私のローカルサンドボックスでは、上記により、約440万行のレガシーテーブルと、0.1万行を含む新しいテーブルが取得されますが、DateCode_FK
/のFileDate
値が一部重複しています。
MAX( FileDate )
追加のインデックスのないレガシーテーブルに対するA は、私が期待するものについて実行されます。
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput DATETIME;
SELECT @ConsumeOutput = MAX( FileDate )
FROM dbo.DailyTransaction_LEGACY;
SET STATISTICS IO, TIME OFF;
GO
テーブル「DailyTransaction_LEGACY」。スキャンカウント1、論理読み取り9228、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
SQL Server実行時間:CPU時間= 889ミリ秒、経過時間= 886ミリ秒。
テーブルに単純なインデックスを投げると、状況が大幅に改善されます。まだスキャンですが、440万レコードではなく1レコードをスキャンしています。私はそれでクールです。
CREATE NONCLUSTERED INDEX IX__DailyTransaction__FileDate
ON dbo.DailyTransaction_LEGACY ( FileDate );
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput DATETIME;
SELECT @ConsumeOutput = MAX( FileDate )
FROM dbo.DailyTransaction_LEGACY;
SET STATISTICS IO, TIME OFF;
GO
SQL Serverの解析およびコンパイル時間:CPU時間= 0ミリ秒、経過時間= 1ミリ秒。テーブル「DailyTransaction_LEGACY」。スキャンカウント1、論理読み取り3、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。
SQL Server実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。
そして今、ビューを作成することで、開発者がコードを変更する必要がないようにしています。一種の大変動。
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate = CONVERT(
DATETIME, CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ), Foo
FROM dbo.DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.DailyTransaction_LEGACY l
WHERE NOT EXISTS ( SELECT 1
FROM dbo.DailyTransactionComplete t
WHERE CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
t.DateCode_FK ), 112 ) = l.FileDate );
GO
はい、サブクエリはひどいですが、これは問題ではありません。おそらく、永続的な計算列を作成し、実際の問題が解決されたら、その目的のためにインデックスをスローします。それ以上の手間をかけずに
問題
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
SQL Serverの解析およびコンパイル時間:CPU時間= 0ミリ秒、経過時間= 4ミリ秒。テーブル「DailyTransaction_LEGACY」。スキャンカウント1、論理読み取り11972、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル「ワークファイル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル 'DailyTransactionComplete'。スキャンカウント2、論理読み取り620、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
SQL Server実行時間:CPU時間= 983ミリ秒、経過時間= 983ミリ秒。
わかりました、Sql Serverは私がやっていることはばかげていると私に伝えようとしています。私は概ね同意しますが、それは私の苦境を変えません。これはFileDate
、dbo.DailyTransaction
ビューのonが述語に含まれているクエリでは実際に見事に機能しますが、MAX
計画は十分に悪いTOP
ものですが、計画はすべてを南向きに送信します。真南。
SET STATISTICS IO, TIME ON;
SELECT TOP 10 FileDate
FROM dbo.DailyTransaction
GROUP BY FileDate
ORDER BY FileDate DESC
SET STATISTICS IO, TIME OFF;
GO
テーブル「DailyTransactionComplete」。スキャンカウント2、論理読み取り1800110、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル 'DailyTransaction_LEGACY'。スキャンカウント1、論理読み取り1254、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル「ワークファイル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。
SQL Server実行時間:CPU時間= 109559ミリ秒、経過時間= 109664ミリ秒。
先ほど「クリエイティブ」になることについて述べましたが、これはおそらく誤解を招くものでした。私が言うことは「もっと愚かだった」ので、集計操作中にこのビューを機能させるための私の試みはdbo.DailyTransactionComplete
、dbo.DailyTransaction_LEGACY
テーブルと テーブルにビューを作成し、スキーマバインドとインデックス付けを行い、NOEXPAND
ヒント付きで別のビューでそれらのビューを使用することでしたレガシービューで。それが今何をする必要があるかについては多かれ少なかれ働いていますが、私は「解決策」全体がかなり動揺していて、次のように最高潮に達していると思います:
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'v_DailyTransactionComplete'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.v_DailyTransactionComplete AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.v_DailyTransactionComplete
AS SELECT DailyTransaction_PK, FileDate = CONVERT( DATETIME,
CONVERT( VARCHAR( 8 ), DateCode_FK ), 112 ),
Foo
FROM dbo.DailyTransactionComplete;
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'v_DailyTransaction_LEGACY'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.v_DailyTransaction_LEGACY AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.v_DailyTransaction_LEGACY
WITH SCHEMABINDING
AS SELECT l.DailyTransaction_PK,
l.FileDate,
l.Foo,
CountBig = COUNT_BIG( * )
FROM dbo.DailyTransaction_LEGACY l
INNER JOIN dbo.DailyTransactionComplete n
ON l.FileDate <> CONVERT( DATETIME, CONVERT( VARCHAR( 8 ),
n.DateCode_FK ), 112 )
GROUP BY l.DailyTransaction_PK,
l.FileDate,
l.Foo;
GO
CREATE UNIQUE CLUSTERED INDEX CI__v_DailyTransaction_LEGACY
ON dbo.v_DailyTransaction_LEGACY ( FileDate, DailyTransaction_PK )
WITH ( DATA_COMPRESSION = PAGE, FILLFACTOR = 80 );
GO
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'DailyTransaction'
AND type = 'V' )
BEGIN
EXEC( 'CREATE VIEW dbo.DailyTransaction AS SELECT x = 1;' );
END;
GO
ALTER VIEW dbo.DailyTransaction
AS SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.v_DailyTransactionComplete
UNION ALL
SELECT DailyTransaction_PK, FileDate, Foo
FROM dbo.v_DailyTransaction_LEGACY WITH ( NOEXPAND );
GO
オプティマイザーにインデックス付きビューによって提供されるインデックスを使用するように強制すると、MAX
およびTOP
問題が解消されますが、ここで私がやろうとしていることを達成するためのより良い方法があるはずです。絶対にどんな提案/叱責も大歓迎です!!
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT @ConsumeOutput1 = MAX( FileDate )
FROM dbo.DailyTransaction;
SET STATISTICS IO, TIME OFF;
GO
テーブル 'v_DailyTransaction_LEGACY'。スキャンカウント1、論理読み取り3、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル 'DailyTransactionComplete'。スキャンカウント1、論理読み取り310、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。
SQL Server実行時間:CPU時間= 31ミリ秒、経過時間= 36ミリ秒。
SET STATISTICS IO, TIME ON;
DECLARE @ConsumeOutput1 DATETIME;
SELECT TOP 10 @ConsumeOutput1 = FileDate
FROM dbo.DailyTransaction
GROUP BY FileDate
ORDER BY FileDate DESC
SET STATISTICS IO, TIME OFF;
GO
テーブル 'v_DailyTransaction_LEGACY'。スキャンカウント1、論理読み取り101、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル「ワークファイル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。テーブル 'DailyTransactionComplete'。スキャンカウント1、論理読み取り310、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。
SQL Server実行時間:CPU時間= 63ミリ秒、経過時間= 66ミリ秒。
TL; DR:
私が述べた最初のビューで集計クエリを合理的な時間内に合理的なI / Oリソース使用率で実行するために何をする必要があるかを理解してください。