今月のT-SQL火曜日はMikeFal(blog | twitter)によってホストされており、トピックはTrick Shotsです。ここでは、少なくとも私たちにとっては、SQLServerで使用したソリューションについてコミュニティに説明するよう招待されています。一種の「トリックショット」として–ビリヤードやスヌーカーでマセ、「イングリッシュ」、または複雑なバンクショットを使用するのと似ています。 SQL Serverを15年間使用した後、非常に興味深い問題を解決するための秘訣を思いつく機会がありましたが、非常に再利用可能で、多くの状況に簡単に適応でき、実装が簡単なものです。私が「schemaswitch-a-roo」と呼んでいるもの。
定期的に更新する必要のある大きなルックアップテーブルがあるシナリオがあるとします。このルックアップテーブルは多くのサーバーで必要であり、外部またはサードパーティのソースから入力されるデータを含めることができます。 IPまたはドメインデータ、または独自の環境内のデータを表すことができます。
このための解決策が必要だった最初の2つのシナリオは、メタデータと非正規化データを読み取り専用の「データキャッシュ」で利用できるようにすることでした。実際には、さまざまなWebサーバーにインストールされたSQL Server MSDE(およびそれ以降のExpress)インスタンスだけなので、Webサーバーはプルされました。これは、プライマリOLTPシステムを煩わせる代わりに、ローカルにデータをキャッシュしました。これは冗長に見えるかもしれませんが、読み取りアクティビティをプライマリOLTPシステムからオフロードし、ネットワーク接続を完全に排除できるため、全体的なパフォーマンスが大幅に向上し、特にエンドユーザーにとっては。
これらのサーバーは、データの最新のコピーを必要としませんでした。実際、多くのキャッシュテーブルは毎日更新されるだけでした。しかし、システムは24時間365日であり、これらの更新の一部には数分かかる可能性があるため、実際の顧客がシステムで実際のことを行うのを妨げることがよくありました。
元のアプローチ
当初、コードはかなり単純でした。ソースから削除された行を削除し、変更されたことがわかるすべての行を更新し、すべての新しい行を挿入しました。これは次のようになりました(簡潔にするためにエラー処理などは削除されました):
BEGIN TRANSACTION; DELETE dbo.Lookup WHERE [key] NOT IN (SELECT [key] FROM [source]); UPDATE d SET [col] = s.[col] FROM dbo.Lookup AS d INNER JOIN [source] AS s ON d.[key] = s.[key] -- AND [condition to detect change]; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source] WHERE [key] NOT IN (SELECT [key] FROM dbo.Lookup); COMMIT TRANSACTION;
言うまでもなく、このトランザクションは、システムの使用中に実際のパフォーマンスの問題を引き起こす可能性があります。確かにこれを行う方法は他にもありましたが、私たちが試したすべての方法は同じように遅く、費用がかかりました。どのくらい遅くて高価ですか? 「スキャンを数えさせてください…」
この古いMERGEであり、DTSのような「外部」アプローチをすでに破棄していたため、いくつかのテストを通じて、ソースに同期するよりも、テーブルをワイプして再入力する方が効率的であると判断しました。 :
BEGIN TRANSACTION; TRUNCATE TABLE dbo.Lookup; INSERT dbo.Lookup([cols]) SELECT [cols] FROM [source]; COMMIT TRANSACTION;
さて、私が説明したように、[ソース]からのこのクエリは、特にすべてのWebサーバーが並行して更新されている場合(可能な限りずらそうとしました)、数分かかる可能性があります。また、顧客がサイトにいて、ルックアップテーブルを含むクエリを実行しようとした場合、そのトランザクションが完了するのを待つ必要がありました。ほとんどの場合、このクエリを深夜に実行している場合、昨日のルックアップデータのコピーを取得したのか、今日のコピーを取得したのかは問題ではありません。そのため、更新を待たせるのはばかげているように見え、実際には多くのサポートコールにつながりました。
したがって、これは優れていましたが、確かに完璧にはほど遠いものでした。
私の最初の解決策:sp_rename
SQL Server 2000がクールだった頃の私の最初の解決策は、「シャドウ」テーブルを作成することでした。
CREATE TABLE dbo.Lookup_Shadow([cols]);
このようにして、ユーザーにまったく影響を与えずにシャドウテーブルにデータを入力し、データの入力が完了した後でのみ、3方向の名前変更(高速なメタデータのみの操作)を実行できました。このようなもの(ここでも、大幅に簡略化されています):
TRUNCATE TABLE dbo.Lookup_Shadow; INSERT dbo.Lookup_Shadow([cols]) SELECT [cols] FROM [source]; BEGIN TRANSACTION; EXEC sp_rename N'dbo.Lookup', N'dbo.Lookup_Fake'; EXEC sp_rename N'dbo.Lookup_Shadow', N'dbo.Lookup'; COMMIT TRANSACTION; -- if successful: EXEC sp_rename N'dbo.Lookup_Fake', N'dbo.Lookup_Shadow';
この最初のアプローチの欠点は、sp_renameに、オブジェクトの名前を変更することの危険性について警告する抑制できない出力メッセージがあることでした。この場合、SQL Serverエージェントジョブを介してこのタスクを実行し、多くのメタデータやその他のキャッシュテーブルを処理したため、ジョブ履歴はこれらの役に立たないメッセージで溢れ、実際には履歴の詳細から実際のエラーが切り捨てられました。 (私は2007年にこれについて不平を言いましたが、私の提案は最終的に却下され、「修正されません」として閉じられました。)
より良い解決策:スキーマ
SQL Server 2005にアップグレードすると、CREATESCHEMAと呼ばれるこの素晴らしいコマンドを発見しました。テーブルの名前を変更する代わりにスキーマを使用して同じタイプのソリューションを実装するのは簡単でした。今では、エージェントの履歴がこれらの役に立たないメッセージのすべてで汚染されることはありません。基本的に、2つの新しいスキーマを作成しました:
CREATE SCHEMA fake AUTHORIZATION dbo; CREATE SCHEMA shadow AUTHORIZATION dbo;
次に、Lookup_Shadowテーブルをキャッシュスキーマに移動し、名前を変更しました:
ALTER SCHEMA shadow TRANSFER dbo.Lookup_Shadow; EXEC sp_rename N'shadow.Lookup_Shadow', N'Lookup';
(このソリューションを実装するだけの場合は、既存のテーブルをそこに移動して名前を変更するのではなく、スキーマにテーブルの新しいコピーを作成することになります。)
これらの2つのスキーマを配置し、シャドウスキーマにルックアップテーブルのコピーを配置すると、3方向の名前変更が3方向のスキーマ転送になりました。
TRUNCATE TABLE shadow.Lookup; INSERT shadow.Lookup([cols]) SELECT [cols] FROM [source]; -- perhaps an explicit statistics update here BEGIN TRANSACTION; ALTER SCHEMA fake TRANSFER dbo.Lookup; ALTER SCHEMA dbo TRANSFER shadow.Lookup; COMMIT TRANSACTION; ALTER SCHEMA shadow TRANSFER fake.Lookup;
この時点で、もちろんテーブルのシャドウコピーを空にすることができますが、トラブルシューティングの目的でデータの「古い」コピーを残しておくと便利な場合があります。
TRUNCATE TABLE shadow.Lookup;
さらにシャドウコピーを使用する場合は、トランザクションの外部で実行する必要があります。2つの転送操作は、可能な限り簡潔かつ迅速である必要があります。
いくつかの警告
-
外部キー
ルックアップテーブルが外部キーによって参照されている場合、これはそのままでは機能しません。この場合、これらのキャッシュテーブルに制約を指定しませんでしたが、指定する場合は、MERGEなどの煩わしい方法に固執する必要があります。または、append-onlyメソッドを使用し、データ変更を実行する前に外部キーを無効化または削除します(その後、それらを再作成または再有効化します)。 MERGE / UPSERTの手法に固執し、サーバー間で、またはさらに悪いことに、リモートシステムからこれを行う場合は、サーバー間でこれらの方法を使用するのではなく、ローカルで生データを取得することを強くお勧めします。
- 統計
テーブルを切り替えると(名前の変更またはスキーマ転送を使用)、テーブルの2つのコピー間で統計が前後に反転します。これは、明らかに計画の問題になる可能性があります。したがって、このプロセスの一部として明示的な統計更新を追加することを検討してください。
- その他のアプローチ
もちろん、これを行う方法は他にもありますが、私はこれを試す機会がありませんでした。パーティションの切り替えとビュー+シノニムの使用は、トピックをより徹底的に扱うために将来調査する可能性のある2つのアプローチです。あなたの経験と、あなたの環境でこの問題をどのように解決したかを聞いてみたいと思います。はい、この問題はSQL Server 2012の可用性グループと読み取り可能なセカンダリによって大部分が解決されることを認識していますが、問題にハイエンドライセンスをスローしたり、複製したりせずに問題を解決できる場合は、「トリックショット」と見なします。データベース全体を使用して、いくつかのテーブルを冗長化します。 :-)
結論
ここでの制限に耐えられる場合、このアプローチは、基本的にSSISまたは独自のMERGE / UPSERTルーチンを使用してテーブルをオフラインにするシナリオよりもパフォーマンスが優れている可能性がありますが、必ず両方の手法をテストしてください。最も重要な点は、テーブルにアクセスするエンドユーザーは、定期的な更新の最中にテーブルにアクセスした場合でも、1日中いつでもまったく同じエクスペリエンスを提供する必要があるということです。