TechNet マガジン > ホーム > 発行物 > 2007 > November >  SQL Server: SQL Server クエリのパフォーマンスを最適化する
SQL Server
SQL Server クエリのパフォーマンスを最適化する
Maciej Pilecki
 
概要:
  • 実行プランを分析する
  • クエリを最適化する
  • チューニングが必要なクエリを特定する

データベース サーバーを最適化するときは、個々のクエリのパフォーマンスをチューニングする必要があります。これは、ハードウェアとソフトウェアの構成など、パフォーマンスに影響するサーバーのその他の側面をチューニングすることと同じくらい (または、おそらくそれ以上に) 重要です。
現在利用できる最も強力なハードウェアでデータベース サーバーを実行しているとしても、いくつかの適切に動作しないクエリによってパフォーマンスが低下する可能性があります。実際、たった 1 つの適切に動作しないクエリ ("ランナウェイ クエリ" と呼ばれることもあります) が原因で、データベースのパフォーマンスに重大な問題が発生することもあります。
反対に、最もコストの高いクエリや最も頻繁に実行されるクエリをチューニングすることで、データベースの全体的なパフォーマンスが大幅に向上することもあります。この記事では、サーバー上の最もコストが高くパフォーマンスの低いクエリを特定してチューニングする際に使用できる手法のいくつかを取り上げます。

実行プランを分析する
通常、個々のクエリをチューニングするときは、まずクエリの実行プランを確認します。実行プランは、SQL ServerTM がクエリの要求を満たし、目的の結果セットを生成するために実行する一連の物理的な操作と論理的な操作を表します。実行プランは、クエリ オプティマイザと呼ばれるデータベース エンジンのコンポーネントにより、クエリ処理の最適化フェーズ中に生成されます。このとき、クエリ内で使用されている検索述語、クエリに記述されているテーブルとその結合条件、返される列の一覧、データへの効率のよいアクセス経路として使用できるインデックスの有無など、さまざまな要素が考慮されます。
複雑なクエリの場合、可能なすべてのプランを列挙すると膨大な数になる可能性があるため、クエリ オプティマイザはすべての可能性を評価するのではなく、そのクエリにとって "問題がない" プランを探します。これは、常に完璧なプランを見つけられるとは限らないためです。見つけられたとしても、完璧なプランを見つけるためにすべての可能性を評価するコストは、パフォーマンス上のメリットを容易に上回る可能性があります。DBA は、このプロセスとその制限を理解している必要があります。
クエリの実行プランを取得する方法は、いくつかあります。
  • Management Studio には、実際の実行プランを表示する機能や推定実行プランを表示する機能があり、プランがグラフィカルに表示されます。これらの機能は、直接プランを確認するソリューションとしては最適で、実行プランの表示と分析に、最も頻繁に使用されています (この記事では、この方法で生成されたグラフィカルなプランを使用して、例を示します)。
  • SHOWPLAN_XML や SHOWPLAN_ALL などのさまざまな SET オプションを使用すると、特殊なスキーマを使用してプランを記述する XML ドキュメントや、実行プランの各操作を記述するテキストの行セットとして、実行プランを取得できます。
  • Showplan XML などの SQL Server Profiler イベント クラスを使用すると、トレースにより収集されたステートメントの実行プランを収集できます。
実行プランの XML 表記は、人間が読むには最も簡単な形式とはいえないかもしれませんが、実行プランを分析できるプロシージャとユーティリティを記述して、パフォーマンスの問題や最適でないプランを探すのに役立ちます。また、XML 表記の実行プランを .sqlplan という拡張子のファイルに保存し、Management Studio で開くと、グラフィカルな実行プランを表示できます。これらのファイルは後で分析するために保存することもできるため、当然ながら、分析を行うたびに実行プランを再生成する必要はありません。これは、特に複数のプランを比較して、プランがどのように変更されてきたかを確認するときに便利です。

実行の推定コスト
実行プランについてまず理解しなければならないことは、その生成方法です。SQL Server では、コスト ベースのクエリ オプティマイザを使用します。つまり、推定コストが最も低い実行プランを生成しようとします。推定は、オプティマイザがクエリに記述されている各テーブルを評価するときに利用できるデータ分散統計を基に行われます。この統計がない場合や古い場合、クエリ オプティマイザはクエリの最適化処理に不可欠な情報を得られないので、おそらく正確でない推定結果が返されます。このような場合、さまざまなプランの実行コストが多くまたは少なく推定されるため、最適ではないプランがオプティマイザによって選択されます。
推定実行コストについては、よく誤解されていることがいくつかあります。特に、推定実行コストは、クエリの実行にかかる時間についての優れた指標であり、この推定を基に良いプランと悪いプランを見分けることができると考えられていることが多くあります。しかし、これは真実ではありません。まず、何を推定コストの表現単位とするか、およびこれらが実行時間と直接関連があるかどうかは十分に規定されています。また、このコストは推定であり、正しくない場合があるので、推定コストが高いプランでも、CPU、I/O、実行時間の面では、はるかに効率がよいことがあります。これは、テーブル変数が含まれたクエリの場合によく見られます。テーブル変数に利用できる統計はないため、クエリ オプティマイザは、テーブル変数に何倍もの行が含まれる場合でも、常にテーブル変数には 1 行しか含まれていないと想定します。この結果、クエリ オプティマイザは正確でない推定を基にプランを選択します。したがって、クエリの実行プランを比較するときは、推定クエリ コストのみを基準に判断しないようにする必要があります。STATISTICS I/O オプションと STATISTICS TIME オプションの結果も併せて分析し、I/O や CPU 時間を考慮すると実際の実行コストがどのようになるかを理解するようにします。
特殊な実行プランの 1 つである並列プランについて、ここで言及しておく価値があります。複数の CPU が搭載されたサーバー上でクエリを実行し、クエリが並列処理に対応できる場合、並列プランが選択されることがあります (通常、クエリ オプティマイザは、クエリのコストが構成可能な特定のしきい値を超えた場合のみ、並列プランを検討します)。実行時に複数の並列スレッドを管理する (つまり、複数のスレッドに作業を分散し、同期を取り、結果を集約する) ことで発生するオーバーヘッドが原因で、並列プランの実行コストは高くなり、推定コストにも反映されます。では、なぜコストが低く並列ではないプランよりも、並列プランが優先されるのでしょうか。複数の CPU の処理能力を利用することで、並列プランの方が標準のプランよりも高速になる傾向があります。利用可能なリソースや他のクエリからの同時実行負荷などの可変要素を考慮すると、シナリオによっては、使用している環境にとってこの状況が望ましい場合があります。その場合は、並列プランを生成できるクエリと、各クエリが使用できる CPU の数を制御します。それには、サーバー レベルで max degree of parallelism オプションを設定し、必要に応じて OPTION (MAXDOP n) を使用して個々のクエリ レベルでこれを上書きするようにします。

実行プランを分析する
では、簡単なクエリとその実行プラン、およびそのパフォーマンスを向上させるためのいくつかの方法について説明します。SQL Server 2005 の Management Studio で、[実際の実行プランを含める] をクリックした後、AdventureWorks サンプル データベースに対して次のクエリを実行します。
SELECT c.CustomerID, SUM(LineTotal)
FROM Sales.SalesOrderDetail od 
JOIN Sales.SalesOrderHeader oh
ON od.SalesOrderID=oh.SalesOrderID
JOIN Sales.Customer c ON oh.CustomerID=c.CustomerID
GROUP BY c.CustomerID
結果として、図 1 のような実行プランが表示されました。この簡単なクエリでは、Adventure Works の各顧客からの注文の合計金額が計算されます。この実行プランを見ると、データベース エンジンがどのようにクエリを処理し、結果を生成するかがわかります。グラフィカルな実行プランを読むときは、上から下、右から左へと進みます。各アイコンは、実行される論理操作と物理操作を表し、矢印は操作間のデータ フローを示します。矢印の太さは、操作間で渡される行数を表します。矢印が太くなるほど、処理される行数も多くなります。操作アイコン上にポインタを移動すると、その操作の詳細情報を示す黄色のヒント (図 2 参照) が表示されます。
図 1 サンプルの実行プラン (画像を拡大するには、ここをクリックします)
図 2 操作の詳細情報 (画像を拡大するには、ここをクリックします)
各操作を確認することで、実行される一連のステップを分析できます。
  1. データベース エンジンは、Clustered Index Scan 操作を Sales.Customer テーブルに対して実行し、このテーブル内のすべての行の CustomerID 列を返します。
  2. 次に、Index Scan (非クラスタ化インデックスのスキャン) を Sales.SalesOrderHeader テーブルの 1 つのインデックスに対して実行します。これは CustomerID 列のインデックスですが、暗黙的に SalesOrderID 列 (テーブルのクラスタ化キー) も含まれています。このスキャンでは、これら 2 つの列の値が返されます。
  3. CustomerID 列に基づき、両方のスキャンの結果を Merge Join 物理操作を使用して結合します (これは、論理結合操作を物理的に実行する 3 つの方法のうちの 1 つです。高速で処理されますが、結合される列を基準に両方の入力を並べ替える必要があります。この場合は、両方のスキャン操作によって、既に CustomerID を基準に並べ替えられた行が返されているので、追加で並べ替え操作を行う必要はありません)。
  4. 次に、データベース エンジンは、Sales.SalesOrderDetail テーブルに対してクラスタ化インデックスのスキャンを実行し、このテーブル内のすべての行から 4 つの列 (SalesOrderID、OrderQty、UnitPrice、および UnitPriceDiscount) の値を取得します (図 2 の [予測行数] および [実際の行数] の値からわかるように、この操作で返されると推定された行数は 123,317 で、実際にこの行数が返されたので、この推定は非常に正確でした)。
  5. Clustered Index Scan によって生成された行は、最初の Compute Scalar 操作に渡され、式に含まれている OrderQty、UnitPrice、および UnitPriceDiscount 列を基にして、計算列 LineTotal の値が行ごとに計算されます。
  6. 2 つ目の Compute Scalar 操作は、計算列の式で要求されているように、ISNULL 関数をこの前の計算結果に適用します。これで LineTotal 列の計算が完了し、結果が SalesOrderID 列と共に次の操作に渡されます。
  7. ステップ 3 の Merge Join 操作の結果が、Hash Match 物理操作によってステップ 6 の Compute Scalar 操作の結果と結合されます。
  8. 別の Hash Match 操作が Merge Join から返された列に適用され、CustomerID 列の値と計算済みの LineTotal 列の SUM 集計を基にグループ化されます。
  9. 最後のノードである SELECT は、物理操作でも論理操作でもなく、全体のクエリ結果とコストを表すプレースホルダです。
私のラップトップでは、この実行プランの推定コストは 3,31365 でした (図 3 参照)。STATISTICS I/O ON を指定してこのクエリを実行すると、クエリに記述されている 3 つのテーブル全体に対して 1,388 回の論理読み取り操作が実行されたことが報告されました。各操作の下に表示される割合は、実行プラン全体の推定コストに対する個々の操作のコストの割合を示しています。図 1 のプランを見ると、実行プラン全体のコストのほとんどは、次の 3 つの操作に関係していることがわかります。つまり、Sales.SalesOrderDetail テーブルに対する Clustered Index Scan 操作と、2 つの Hash Match 操作です。ただし、これらを最適化する前に、このクエリにわずかな変更を加えるだけで 2 つの操作を削除できることを説明したいと思います。
図 3 クエリの推定実行コストの合計 
Sales.Customer テーブルから返される唯一の列は CustomerID であり、この列は Sales.SalesOrderHeaderTable にも外部キーとして含まれているため、次のコードを使用すると、論理的な意味やクエリから生成される結果を変更することなく、クエリから Customer テーブルを完全に削除できます。
SELECT oh.CustomerID, SUM(LineTotal)
FROM Sales.SalesOrderDetail od JOIN Sales.SalesOrderHeader oh
ON od.SalesOrderID=oh.SalesOrderID
GROUP BY oh.CustomerID
この結果、図 4 のような別の実行プランが生成されます。
図 4 クエリから Customer テーブルを削除した後の実行プラン (画像を拡大するには、ここをクリックします)
Customer テーブルの Clustered Index Scan、および Customer と SalesOrderHeader の Merge Join という 2 つの操作が完全に削除され、Hash Match 結合が、はるかに効率的な Merge Join に置き換えられました。ただし、SalesOrderHeader テーブルと SalesOrderDetail テーブルの Merge Join を使用するには、結合列 SalesOrderID を基準に両方のテーブルの行を並べ替えて返す必要がありました。このため、クエリ オプティマイザは、I/O コストが低くなると思われる Index Scan (非クラスタ化インデックスのスキャン) ではなく、Clustered Index Scan を SalesOrderHeader テーブルに対して実行するプランを選択しました。これは、クエリ オプティマイザの実際の動作がわかる良い例です。結合操作を実行する物理的な方法を変更して削減できるコストの方が、Clustered Index Scan によって生じる追加の I/O コストよりも大きいため、クエリ オプティマイザでは、全体の推定実行コストが最も低くなる操作の組み合わせを選択しています。私のラップトップでは、論理読み取りの数は (1,941 回に) 増加しましたが、CPU 時間は短縮され、クエリの実行コストは推定値から約 13% 減少しました (2,89548)。
このクエリのパフォーマンスをさらに強化するとします。今度は、この実行プランで最もコストが高い操作になった、SalesOrderHeader テーブルに対する Clustered Index Scan に目を付けます。クエリの要求を満たすためにこのテーブルから必要になる列は 2 つのみなので、これら 2 つの列を含む非クラスタ化インデックスを作成できます。つまり、テーブル全体をスキャンするのではなく、大きく規模を縮小した非クラスタ化インデックスのスキャンを実行します。このインデックスの定義は、次のようになります。
CREATE INDEX IDX_OrderDetail_OrderID_TotalLine
ON Sales.SalesOrderDetail (SalesOrderID) INCLUDE (LineTotal)
作成したインデックスには計算列が含まれていることに注意してください。計算列の定義によっては、この方法を使用できないことがあります。
このインデックスを作成し、同じクエリを実行すると、図 5 のような新しい実行プランが作成されました。
図 5 最適化された実行プラン (画像を拡大するには、ここをクリックします)
SalesOrderDetail テーブルの Clustered Index Scan が、大幅に I/O コストが低下する Index Scan に置き換えられています。また、既に計算された LineTotal 列の値がインデックスに含まれているので、Compute Scalar 操作が 1 つ削除されています。これで推定実行プランのコストは 2,28112 となり、クエリの実行時に 1,125 回の論理読み取りが実行されます。

カバリング インデックス
顧客注文クエリの演習
Q ここでは、顧客注文クエリの演習を行います。インデックス定義を考えてみてください。このクエリのカバリング インデックスとなるには、どの列を含める必要があるでしょうか。また、インデックス定義内の列の順序によってパフォーマンスは変化するでしょうか。
A 皆さんに考えていただいたのは、この記事のサンプル クエリで使用されている Sales.SalesOrderHeader テーブル用の最適なカバリング インデックスです。これを作成するには、まずクエリがこのテーブルの 2 つの列しか使用していないことに注目する必要があります。これらの列は、CustomerID と SalesOrderID です。この記事をよく読んでいれば、SalesOrderHeader テーブルには、このクエリをカバーするインデックス、つまり CustomerID のインデックスが既に存在することに気が付くでしょう。このインデックスには、テーブルのクラスタ化キーである SalesOrderID 列が暗黙的に含まれています。
もちろん、クエリ オプティマイザが、このインデックスを使用しないプランを選択した理由についても説明しました。確かに、クエリ オプティマイザがこのインデックスを使用するように強制することもできますが、そのプランでは Clustered Index Scan 操作と Merge Join 操作を使用する既存のプランよりも効率が悪くなります。これは、追加の Sort 操作を実行して Merge Join を使用できるようにするか、効率の落ちる Hash Join を使用するようクエリ オプティマイザに強制することになるからです。どちらの場合も、既存のプランよりも推定実行コストが高くなります (特に Sort 操作を使用するプランはパフォーマンスが悪くなります)。このため、クエリ オプティマイザは、強制的に指定されない限り、これらのプランを使用しません。したがって、この場合、Clustered Index Scan よりも優れたパフォーマンスを実現するには、SalesOrderID と CustomerID に非クラスタ化インデックスを作成する必要があります。ただし、これらの列は、次の順番で指定する必要があることに注意してください。
CREATE INDEX IDX_OrderHeader_SalesOrderID_CustomerID
ON Sales.SalesOrderHeader (SalesOrderID, CustomerID)
このインデックスを作成すると、実行プランには Clustered Index Scan 操作ではなく、Index Scan 操作が含まれます。これは大きな違いです。この場合、2 列しか含まれない非クラスタ化インデックスは、テーブル全体が含まれるクラスタ化インデックスと比べて格段に規模が小さくなります。したがって、データの読み取りに必要な I/O が少なくなります。
この例は、インデックス内の列の順序が、クエリ オプティマイザにとっての有用性に大きく影響する場合があることを示しています。複数列のインデックスを設計するときは、このことに注意してください。

SalesOrderDetail に作成したインデックスは、いわゆる "カバリング インデックス" の一例です。これは、クエリの要求を満たすために必要なすべての列を含む非クラスタ化インデックスであるため、Table Scan 操作または Clustered Index Scan 操作を使用してテーブル全体をスキャンする必要がなくなります。このインデックスは、本質的にはテーブルの小さなコピーであり、テーブルの列のサブセットが含まれています。インデックスに含まれている列は、クエリの回答に必要な列のみです。つまり、クエリを "カバーする" ために必要なもののみが含まれています。
最も頻繁に実行されるクエリ用のカバリング インデックスを作成することは、クエリのチューニングに使用される最も簡単かつ一般的な手法の 1 つです。テーブルに多数の列が含まれているが、一部の列のみがクエリで頻繁に参照される場合、この手法は特に有効です。1 つ以上のカバリング インデックスを作成することで、その影響を受けるクエリのパフォーマンスを大幅に強化できます。これは、クエリがアクセスするデータの量が大幅に減少し、その結果 I/O も少なくなるからです。ただし、データ変更操作 (INSERT、UPDATE、および DELETE) では、追加のインデックスのメンテナンス作業という隠れたコストが発生します。環境および SELECT クエリとデータ変更操作の比率によっては、この追加のインデックスの保守作業によってオーバーヘッドが発生した場合でも、クエリ パフォーマンスの強化によって得られるメリットの方がそのオーバーヘッドより大きいかどうかを慎重に判断してください。
単一列のインデックスではなく、複数列のインデックスを積極的に作成してください。複数列のインデックスは、単一列のインデックスよりもはるかに役立ち、クエリ オプティマイザもクエリをカバーするために複数列のインデックスを優先的に使用します。ほとんどのカバリング インデックスは、複数列のインデックスです。
このサンプル クエリは、まだ強化する余地があります。SalesOrderHeader テーブルにカバリング インデックスを作成することで、このクエリをさらに最適化できます。これにより、Clustered Index Scan が削除され、Index Scan が代わりに使用されます。この方法は皆さんに演習として考えていただきます。インデックス定義を考えてみてください。このクエリのカバリング インデックスとなるには、どの列を含める必要があるでしょうか。また、インデックス定義内の列の順序によってパフォーマンスは変化するでしょうか。この回答については、補足記事「顧客注文クエリの演習」を参照してください。

インデックス付きビュー
サンプル クエリのパフォーマンスが非常に重要な場合は、もう一手間かけて、クエリの具体化された結果を物理的に格納するインデックス付きビューを作成できます。インデックス付きビューには特定の要件と制限がありますが、使用できればパフォーマンスを劇的に向上させることができます。ただし、インデックス付きビューは、標準のインデックスよりもメンテナンス コストがかかります。したがって、使用する状況を慎重に検討する必要があります。今回のサンプル クエリの場合、インデックス定義は次のようになります。
CREATE VIEW vTotalCustomerOrders
WITH SCHEMABINDING
AS
SELECT oh.CustomerID, SUM(LineTotal) AS OrdersTotalAmt, COUNT_BIG(*) AS TotalOrderLines
FROM Sales.SalesOrderDetail od 
JOIN Sales.SalesOrderHeader oh
ON od.SalesOrderID=oh.SalesOrderID
GROUP BY oh.CustomerID 
このようなビューのインデックスを作成する場合に必要な WITH SCHEMABINDING オプションと、インデックス定義に集計関数 (この例では SUM) が含まれる場合に必要な COUNT_BIG(*) 関数が使用されていることに注意してください。ビューを作成したら、次のように、このビューのインデックスを作成できます。
CREATE UNIQUE CLUSTERED INDEX CIX_vTotalCustomerOrders_CustomerID 
ON vTotalCustomerOrders(CustomerID)
このインデックスを作成すると、ビュー定義に含まれるクエリ結果が具体化されて、物理的にディスク上のインデックスに格納されます。また、ベース テーブルのデータ変更操作が行われた場合は、常に自動的にビューの値がその定義に従って更新されます。
ここで再びクエリを実行した場合、実行している SQL Server のエディションによって動作にどのような違いが生じるでしょうか。Enterprise Edition または Developer Edition の場合、クエリ オプティマイザは、自動的にこのクエリをインデックス付きビューの定義と照合し、ベース テーブルに対してクエリを実行する代わりにこのインデックス付きビューを使用します。図 6 は、この場合に生成される実行プランを示しています。これは、ビューに作成したインデックスに対する Clustered Index Scan という、たった 1 つの操作で構成されています。推定実行コストはわずか 0,09023 となり、実行される論理読み取りはわずか 92 回です。
図 6 インデックス付きビューを使用した場合の実行プラン (画像を拡大するには、ここをクリックします)
他のエディションの SQL Server でもこのインデックス付きビューを作成して使用できますが、同じ効果を得るには、次のように NOEXPAND ヒントを使用して、直接ビューを参照するようにクエリを変更する必要があります。
SELECT CustomerID, OrdersTotalAmt
FROM vTotalCustomerOrders WITH (NOEXPAND)
このように、インデックス付きビューは、適切に使用すれば、非常に強力な機能になり得ます。大量のデータに対して集計を実行するクエリの最適化には、最も威力を発揮します。Enterprise Edition で使用する場合、コードを変更しなくても、多くのクエリでメリットが得られます。

チューニングが必要なクエリを特定する
チューニングが必要なクエリを特定するには、どうしたらよいでしょうか。頻繁に実行されるクエリを探します。このようなクエリは、1 回の実行にかかるコストはそれほど高くないかもしれませんが、それが集まると、めったに実行されない大規模なクエリのコストよりもはるかに高くなることが考えられます。大規模なクエリをチューニングするべきでないと言っているわけではありませんが、まず最も頻繁に実行されるクエリに集中する必要があります。それでは、それらをどのように特定すればよいでしょうか。
残念ながら、最も信頼性の高い方法はやや複雑で、サーバーに対して実行されるすべてのクエリをトレースした後、各クエリの署名を基にクエリをグループ化する必要があります (つまり、クエリが異なるパラメータ値で実行されていたとしても、同じ種類のクエリを特定するために、クエリ テキストの実際のパラメータ値をプレースホルダで置き換えます)。クエリの署名を生成するのは難しいため、これは複雑な処理になります。Itzik Ben-Gan 氏は、同氏の著書『Inside Microsoft SQL Server 2005: T-SQL Querying』の中で、CLR ユーザー定義関数と正規表現を使用したソリューションについて説明しています。
信頼性はやや劣りますが、他にこれよりもはるかに簡単な方法があります。動的管理ビューを使用し、実行プラン キャッシュ内にある、すべてのクエリの統計に対してクエリを実行することができます。図 7 は、キャッシュに格納されている、論理読み取りの累計が最も多い 20 個のクエリのテキストと実行プランを取得するサンプル クエリを示しています。このクエリは、論理読み取り数が最も多いクエリをすばやく特定する場合に非常に役立ちますが、制限があります。つまり、このクエリを実行した時点でプランがキャッシュされているクエリしか取得されないということです。キャッシュされていないクエリは取得されません。
SELECT TOP 20 SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, 
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(qt.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2)+1), 
qs.execution_count, 
qs.total_logical_reads, qs.last_logical_reads,
qs.min_logical_reads, qs.max_logical_reads,
qs.total_elapsed_time, qs.last_elapsed_time,
qs.min_elapsed_time, qs.max_elapsed_time,
qs.last_execution_time,
qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qt.encrypted=0
ORDER BY qs.total_logical_reads DESC

パフォーマンスの悪いクエリを特定したら、クエリ プランを見直し、この記事に記載されている、インデックスを使用したいくつかの手法を使用して、パフォーマンスを強化する方法を探してください。それができれば、大きな見返りが得られるでしょう。
チューニングの成功を祈ります。

Maciej Pilecki は、トレーニング、メンタリング、コンサルティングを専門とする世界的な組織 Solid Quality Mentors のアソシエイト メンターです。マイクロソフト認定トレーナー (MCT) および SQL Server の Most Valuable Professional (MVP) であり、SQL Server とアプリケーション開発のさまざまなトピックについて、コースやカンファレンスでの講演を頻繁に行っています。
© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; 許可なしに一部または全体を複製することは禁止されています.
Page view tracker