目次


SQLプロシージャーのパフォーマンス:そのヒントと秘訣

Comments

はじめに

データベース・システムとアプリケーションのチューニング方法に関するアドバイスを提供する情報ソースは多数存在します。IBM® DB2® Developer Domainで公開されている「DB2 Tuning Tips for OLTP Applications(英文)」(OLTPアプリケーションのためのDB2チューニングの秘訣)などの記事は、テーブル・スペース、インデックス設計から、バッファー・プールへのメモリー割り当て、トランザクションとデータ並行性の利用、照会プランの分析まで、広範なトピックスに関するアドバイスを提供しています。こうした事項は、パフォーマンス・チューニングの基本となるものです。

しかし、パフォーマンスに重点を置き、ストアード・プロシージャーのロジックそのものを構成する方法についての具体的なアドバイスとなると、それほど数多くは見つけることができません。この記事は、そのようなアドバイスを提供します。この記事は、主にSQLプロシージャーに焦点を当てたものですが、ここで示される情報の大半はアプリケーションに組み込まれたSQLロジック、あるいは他の言語で書かれたストアード・プロシージャーにも適応することができます。

背景と用語

詳細を述べる前に、まず、DB2のプロシージャー型SQLに関連するいくつかの基本的な用語と概念をご紹介しましょう。プロシージャー型SQL構造(スカラー変数、IFステートメント、WHILEループなど)がDB2に採用されたのは、DB2 Universal DatabaseTM(UDB) Version 7以降です。それ以前のリリースのDB2では、ストアード・プロシージャーの言語としてCおよびJavaTMをサポートしていました。Version 7は、OLTPアプリケーションの開発を円滑化するその他の数多くの機能(たとえば、一時テーブル、アプリケーション・セーブポイント、IDENTITY列など)とともに、SQLストアード・プロシージャーを導入しました。

SQLプロシージャーの作成時に、DB2は、プロシージャー・ボディ中のSQL照会をプロシージャー・ロジックから分離します。パフォーマンスを最大化するため、SQL照会は静的にコンパイルされ、パッケージのセクションとして編成されます(静的にコンパイルされる照会については、各セクションの内容は、当該照会用にDB2オプティマイザーが選択するアクセス・プランが中心です。パッケージは、セクションの集合です。パッケージとセクションに関する詳細は、(DB2 SQL リファレンス−のボリューム1を参照してください)。一方、プロシージャー・ロジックはダイナミック・リンク・ライブラリ(DLL)にコンパイルされます。

プロシージャーの実行中、プロシージャー・ロジックからSQLステートメントへ制御がフローするごとに、DLLとDB2エンジン間で「コンテキスト切り替え」が行われます(DB2 V8では、SQLプロシージャーは、「unfenced」モードで、つまりDB2エンジンと同じアドレス・スペースで実行されます)。したがって、ここでいうコンテキスト切り替えは、オペレーティング・システム・レベルでのコンテキスト切り替えではなく、DB2内部でのレイヤーの切り替えです。きわめて頻繁に呼び出されるプロシージャー(OLTPアプリケーションのプロシージャーなど)、または多数の行を処理するプロシージャー(たとえば、データ・クレンジングを実行するプロシージャー)のコンテキスト切り替えの回数を減らせば、パフォーマンスに顕著な効果を及ぼすことが期待されます。この記事で紹介する秘訣のいくつかは、まさしく、このコンテキスト切り替えの数を減らすことを目的としています。

SQLプロシージャー言語(一般にSQL PLと呼ばれる)は、当初、SQLプロシージャー(DB2 Universal Database V7 GA)内でのみ許容されていました。後に(DB2 UDB V7.2で)、SQL関数とトリガー・ボディーでこの言語のサブセットがサポートされるようになりました。このSQL PLのサブセットはインラインSQL PLと呼ばれています。「インライン」という言葉は、フル言語とは大きな相違があることを意味しています。SQL PLプロシージャーは、個々のSQL PLをパッケージのセクションに静的にコンパイルすることにより実装されます。これに対して、インラインSQL PL関数は、その名前が示すとおり、関数のボディーを、それを使用する照会にインライン化することによって実装されます。インラインSQL PLに関しては、その使用例とともに、後ほど再度説明します。

ここで、SQLプロシージャー言語を使用する際、具体的に何を行えばパフォーマンスを強化することができるのか見ていきましょう。

単一のステートメントで充分なときは複数のステートメントを避ける

最初に簡単なコーディングのヒントから始めます。次のような単一行INSERTのシーケンスがあります。

INSERT INTO tab_comp VALUES (item1, price1, qty1);
INSERT INTO tab_comp VALUES (item2, price2, qty2);
INSERT INTO tab_comp VALUES (item3, price3, qty3);

これは、次のように書き直すことができます。

INSERT INTO tab_comp VALUES	(item1, price1, qty1),
                     		(item2, price2, qty2),
                     		(item3, price3, qty3);

複数行INSERTは、最初の3つのステートメントの実行と比較して所要時間がおよそ3分の1です。これだけでは大きな改善は得られませんが、たとえば、ループやトリガー・ボディーなど、コード・フラグメントが繰り返し実行される場合には、大きなパフォーマンスの向上が期待できます。

同様に、次のようなSETステートメントのシーケンスがあります。

SET A = expr1;
SET B = expr2;
SET C = expr3;

これは、単一のVALUESステートメントとして書き直すことができます。

VALUES expr1, expr2, expr3 INTO A, B, C;

2つのステートメント間に依存がなければ、この書き換えは、オリジナルの順番の意味を維持します。また、逆の例として、次のコードを考えてみましょう。

SET A = monthly_avg * 12;
SET B = (A / 2) * correction_factor;

前の2つのステートメントを次のように書き換えます。

VALUES (monthly_avg * 12, (A / 2) * correction_factor) INTO A, B;

このステートメントでは、INTOキーワードの前の式が「並列に」評価されるため、最初のセマンティクスは維持されません。つまり、Bに割り当てられる値がAに割り当てられる値を基礎にしないということであり、最初のステートメントで意図したセマンティクスからは外れます。

複数のSQLステートメントから単一のSQL式へ

他のプログラミング言語と同様に、SQL言語には、2つのタイプの条件構文 − プロシージャー型(IFおよびCASEステートメント)と関数型(CASE式) − があります。計算を表すのにどちらのタイプも使えるような状況では、ほとんどの場合、どちらを使うかは好みの問題です。しかし、CASE式を使って書かれたロジックは、CASEまたはIFステートメントを使って書かれたロジックよりも、コンパクトであり、かつ、効率的でもあります。

次のSQL PLコードのフラグメントを考えてみましょう。

IF (Price <= MaxPrice) THEN
  INSERT INTO tab_comp(Id, Val) VALUES(Oid, Price);
ELSE
  INSERT INTO tab_comp(Id, Val) VALUES(Oid, MaxPrice);
END IF;

IFクローズの条件は、tab_comp.Val列にどのような値を挿入するかを判定することのみを目的としています。プロシージャー・レイヤーとデータフロー・レイヤー間のコンテキスト切り替えを避けるために、同じロジックをCASE式を含む単一INSERTとして表すことができます。

INSERT INTO tab_comp(Id, Val)
       VALUES(Oid,
              CASE
                 WHEN (Price <= MaxPrice) THEN Price
                 ELSE MaxPrice
              END);

CASE式はスカラー変数が期待されるどのようなコンテキストでも使用できるという点が注目に値します。特に、代入の右側に使用することができます。次はその例です。

IF (Name IS NOT NULL) THEN
  SET ProdName = Name;
ELSEIF (NameStr IS NOT NULL) THEN
  SET ProdName = NameStr;
ELSE
  SET ProdName = DefaultName;
END IF;

これは、次のように書き直すことができます。

SET ProdName = (CASE
                  WHEN (Name IS NOT NULL) THEN Name
                  WHEN (NameStr IS NOT NULL) THEN NameStr
                  ELSE  DefaultName
                END);

実際には、この例にはさらに優れた解があります。

SET ProdName = COALESCE(Name, NameStr, DefaultName);

SQLのセットアットアタイム(set-at-a-time)セマンティクスを利用する

ループ、代入、カーソルなどのプロシージャー型構造を使うと、SQL DMLステートメントだけでは表すことのできないような計算を表すことができます。ただし、プロシージャー型ステートメントの使用に慣れてしまうと、実際にはSQL DMLステートメントのみで表せる計算にもプロシージャー型構造を使ってしまうようになるリスクもあります。前述のように、プロシージャー型計算のパフォーマンスは、DMLステートメントによる同等の計算のパフォーマンスに比べて桁違いに速度が遅くなります。次のコード・フラグメントを考えてみましょう。

DECLARE cur1 CURSOR FOR SELECT col1, col2 FROM tab_comp;
OPEN cur1;
FETCH cur1 INTO v1, v2;
WHILE SQLCODE <> 100 DO
  IF (v1 > 20) THEN
    INSERT INTO tab_sel VALUES (20, v2);
  ELSE
    INSERT INTO tab_sel VALUES (v1, v2);
  END IF;
  FETCH cur1 INTO v1, v2;
END WHILE;

まず、ループ・ボディは、前のセクションで示した書き換え方法を利用して、次のように改めることができます。

DECLARE cur1 CURSOR FOR SELECT col1, col2 FROM tab_comp;
OPEN cur1;
FETCH cur1 INTO v1, v2;
WHILE SQLCODE <> 100 DO
  INSERT INTO tab_sel VALUES (CASE
                                  WHEN v1 > 20 THEN 20
                                  ELSE v1
                                END, v2);
  FETCH cur1 INTO v1, v2;
END WHILE;

さらによく見ると、コード・ブロックの全体をsub-SELECTを含むINSERTとして書き直すことができます。

INSERT INTO tab_sel (SELECT (CASE
                               WHEN col1 > 20 THEN 20
                               ELSE col1
                             END),
                             col2
                     FROM tab_comp);

最初の書き換えでは、SELECTステートメントの各行についてプロシージャー・レイヤーとデータフロー・レイヤー間のコンテキスト切り替えがありました。その後の書き換えでは、コンテキスト切り替えはまったくなく、オプティマイザーは、計算全体をグローバルに最適化することができます。ただし、次に示すように、各INSERTステートメントが異なるテーブルをターゲットとする場合には、このような劇的な単純化は難しくなります。

DECLARE cur1 CURSOR FOR SELECT col1, col2 FROM tab_comp;
OPEN cur1;
FETCH cur1 INTO v1, v2;
WHILE SQLCODE <> 100 DO
  IF (v1 > 20) THEN
    INSERT INTO tab_default VALUES (20, v2);
  ELSE
    INSERT INTO tab_sel VALUES (v1, v2);
  END IF;
  FETCH cur1 INTO v1, v2;
END WHILE;

しかし、ここでSQLのセットアットアタイム(set-at-a-time)の性質を利用するという方法もあります。

INSERT INTO tab_sel (SELECT col1, col2
                     FROM tab_comp
                     WHERE col1 <= 20);
INSERT INTO tab_default (SELECT col1, col2
                         FROM tab_comp
                         WHERE col1 > 20);

既存のプロシージャー・ロジックでのパフォーマンスの向上から考えて、カーソル・ループを解消するための作業に時間を費やしても、おそらくそれ相応の価値はあると言えるでしょう。

カーソル・パフォーマンスを改善する

ストアード・プロシージャーのロジックがカーソルを要求していないケースで、パフォーマンスを最大化するために覚えておきたいいくつかのことがあります。

まず第1に、決して必要以上に高分離レベルを使用しないようにします。分離レベルは、プロシージャーの読み出すまたは更新する行に対してDB2が適用するロッキングの量を決定します。分離レベルが高いほど、DB2が実行するロッキングが多くなり、したがって同じリソースを共有しようとするアプリケーション間の並列性が低下します。たとえば、Cursor Stability(カーソル固定、CS)分離レベルを使用するプロシージャーでは、更新可能なカーソルの現在行がロックされるだけであるのに対して、Repeatable Read (反復可能読み出し、RR)を使用するプロシージャーでは、読み出しを行うすべての行に共有ロックが発生します。SQLプロシージャーの分離レベルを指定するには、DB2_SQLROUTINE_PREPOPTSレジストリー変数を使用します。たとえば、SQLプロシージャーの分離レベルをUncommitted Read(非コミット読み出し)(最下位レベル − 読み出し専用データにアクセスするプロシージャーに使用)に設定するには、次のコマンドを使用します。

db2set  DB2_SQLROUTINE_PREPOPTS="ISOLATION UR"

注記:この設定を有効にするには、db2インスタンスを再起動する必要があります。

DB2のデフォルトの分離レベルは、Cursor Stability(カーソル固定)です。しかしもちろん、アプリケーションの正確性を保全するためにRepeatable Readの使用が必要となる場合があります。覚えておくべき重要なことは、Repeatable Read を要求するプロシージャーを作成したあと、DB2_SQLROUTINE_PREPOPTSを下位の分離レベルに戻しておくということです。

もう1つ、 分離に関して指摘しておきたいのは、DB2では、次のように個々の照会でデフォルトの分離レベルをオーバーライドできるということです。

DECLARE cur1 CURSOR FOR SELECT col1 FROM tab_comp WITH UR;

上記の照会は、DB2_SQLROUTINE_PREPOPTSで指定した分離レベルに関係なく、分離レベルURを実行することになります。

これに関連して、カーソル・パフォーマンスを改善しようとするときに心に留めておきたいことは、カーソルの更新可能性です。UPDATE またはDELETEステートメントでWHERE CURRENT OFクローズを使ってカーソルの範囲の行を更新または削除可能なときに、カーソルは削除可能です。カーソルが削除可能なとき、DB2は、行の排他的ロック(共有ロックとは反対)を取得する必要があり、行のブロック化を実行することはできません。行に排他的ロックをかけると、他のアプリケーションからは、その行を読み出すことさえできません(分離レベルがURでない限り、ロックが解除されるまで待機する)。逆に、行をブロック化すると、単一の操作で行のブロックが取り出され、カーソルについてデータベース・マネージャーのオーバーヘッドが軽減されます。

行のブロック化を実行できるのは、削除不能のカーソルについてのみです。そのため、カーソルをどのように使用するのかDB2に知らせることが重要となります。カーソルを削除不能として明示的に宣言するにはSELECTステートメントでFOR READ ONLYクローズを指定します。あるいは削除可能として宣言するには、SELECTステートメントでFOR UPDATEクローズを使用します。この情報(および、後述するBLOCKINGオプション)に基づいて、DB2は、指定されたカーソルについて行ブロック化を使用するかどうかを判断することになります。

デフォルトでは、DB2は、FOR READ ONLYクローズを使って定義されたカーソルについては、BLOCKING NOバインド・オプションが指定されていない限り、常に行ブロック化を使用します。あいまいなカーソル (FOR READ ONLYまたは FOR UPDATEのどちらにも定義されていないカーソル)については、BLOCKING ALLバインド・オプションが使用されている場合に、行ブロック化を使用します。

要するに、可能なときは、カーソル定義にFOR READ ONLYクローズを使用します。プロシージャーにあいまいカーソルが含まれているときは、BLOCKING ALLバインド・オプションを使用します。BLOCKINGバインド・オプションの値を設定するため、DB2_SQLROUTINE_PREPOPTSレジストリー変数も使用します。たとえば、SQLプロシージャーの分離レベルをUncommitted Readに、行ブロック化をBLOCKING ALLに設定するには、次のコマンドを使用します。

db2set  DB2_SQLROUTINE_PREPOPTS="ISOLATION UR BLOCKING ALL"

大きな結果セットを返すプロシージャーでは、ブロック化が特に重要となることがあります。

DB2_SQLROUTINE_PREPOPTSレジストリーを使って、ストアード・プロシージャーに他のバインド・オプションを指定することもできます。詳しくは、『アプリケーション開発ガイド:アプリケーションの構築と実行』の「SQLプロシージャー環境のセットアップ」を参照してください。また、分離レベル、ロッキング、ブロック化の詳細な説明は、この記事の範囲ではありません。『DB2管理ガイド:パフォーマンス』と『SQLリファレンス』の「カーソルの宣言」の項を参照してください。

副作用がないときはSQL関数を使用する

「はじめに」で述べたように、SQLプロシージャーとSQL関数は異なる技術を使って実装されます。SQLプロシージャーでの照会は個別にコンパイルされて、それぞれがパッケージの各セクションに編成されます。コンパイルはプロシージャー作成時に実行され、プロシージャーが再作成されるまで、または関連パッケージが再バインドされるまで、再コンパイルは行われません。

他方、SQL関数での照会は、関数ボディーが単一照会であるのと同じように、すべて一緒にコンパイルされます。コンパイルは、関数を使用するステートメントがコンパイルされるたびに実行されます。

SQLプロシージャーでの場合と違い、SQL関数でのプロシージャー・ステートメントは、データフロー・ステートメントと別のレイヤーで実行されるわけではありません。したがって、プロシージャー・ステートメントからデータフロー・ステートメントへ、あるいはその逆方向に制御がフローするたびに毎回コンテキスト切り替えが行われることはありません。

このような違いにより、プロシージャー・コードは、一般に、プロシージャーとして実行されるときよりも関数として実行されるときの方が格段に高速です。しかしもちろん、いいことばかりではありません。関数には、データベースの状況を変更しないステートメントしか使えません(たとえば、INSERT、UPDATE、DELETEステートメントは許可されません)。また、SQL関数で許可されるのは、フルSQL PL言語のうちのサブセットのみです(CALLステートメントなし、カーソルなし、条件処理なし)。

こうした制約はありますが、副作用のないSQLプロシージャーの大半はSQL関数に書き換えることができます。たとえば、次のプロシージャーを見てみましょう。

CREATE PROCEDURE GetPrice (IN Vendor CHAR(20),
                           IN Pid INT,
                           OUT price DECIMAL(10,3))
LANGUAGE SQL
BEGIN
   IF Vendor = 'Vendor 1' THEN
         SET price = (SELECT ProdPrice
                     FROM V1Table  WHERE Id = Pid);
   ELSE IF Vendor = 'Vendor 2' THEN
         SET price = (SELECT Price
                     FROM V2Table  WHERE Pid = GetPrice.Pid);
   END IF;
END

これは、次の関数と等値です。

CREATE FUNCTION GetPrice (Vendor CHAR(20), PId INT)
RETURNS DECIMAL(10,3)
LANGUAGE SQL
BEGIN
   DECLARE price DECIMAL(10,3);
   IF Vendor = 'Vendor 1' THEN
         SET price = (SELECT ProdPrice
                     FROM V1Table  WHERE Id = Pid);
   ELSE IF Vendor = 'Vendor 2' THEN
         SET price = (SELECT Price
                     FROM V2Table  WHERE Pid = GetPrice.Pid);
   END IF;
   RETURN price;
END

プロシージャー呼び出しにはCALLステートメントを使用しますが、コマンド行から関数を呼び出すにはVALUESステートメントが必要である点に注意してください。

VALUES (GetPrice('IBM', 324))

他方、プロシージャーとは違って、式が許可されるコンテキストであれば、関数を呼び出すことができます。

SELECT VName FROM Vendors WHERE GetPrice(Vname, Pid) < 100;

SET price =  GetPrice(Vname, Pid);

したがって、このセクションの見出しからも分かるように、何も変更せずにデータベースからデータを取り出すのみの場合には、SQLプロシージャーではなくSQL関数の使用を検討してください。

一時データ用に一時テーブルを使用する

DB2は、Version 7において一時テーブルを採用しました。一時テーブルの動作は、通常、正規テーブルよりも段違いに高速です。その理由をいくつか見ていきましょう。

  • まず、一時テーブルを作成するためには、カタログにエントリーを挿入する必要がなく、一時テーブルを使用するためにカタログにアクセスする必要もありません。したがって、カタログに対する競合が発生しません。
  • 一時テーブルは作成元のアプリケーションからしかアクセスできないため、その操作に関連するロッキングは必要ありません。
  • NOT LOGGEDオプションを指定した場合、一時テーブルに対する操作は、ロギングされません(当然、その代わりに、変更をロールバックできるメリットは失われます)。したがって、ストアード・プロシージャーが当該のセッション内でのみ使用するために大量の一時データを生成する場合、そのデータを一時テーブルに格納するようにすれば、おそらく、きわめて大きなパフォーマンスの向上がもたらされることになります。

SQLプロシージャーで一時テーブルを使用するには、その前に、コンパイル環境でテーブル定義が利用できなければなりません。たとえば、(「%」をステートメント終了文字として使用している)次のCLPスクリプトでは、SQLプロシージャーの作成を可能にすることのみがテーブル定義の目的となります。

CONNECT TO sample %
DECLARE GLOBAL TEMPORARY TABLE TT(C1 INT, C2 CHAR(20)) %

CREATE PROCEDURE INSTT(P1 INT, P2 CHAR(20))
BEGIN
  INSERT INTO SESSION.TT VALUES(P1, P2);
END %

CONNECT RESET %

CONNECT RESETコマンド後、一時テーブルはもはや存在しません。ランタイムにおいて、アプリケーションは、テーブルを使用する最初の照会が実行される前にテーブルが存在するようにしなければなりません。この点に関して、まだ説明していない重要なポイントがあります。つまり、一時テーブルを参照する照会は、静的SQLとして書かれた照会の場合であっても、動的にコンパイルされるということです。照会がコンパイルされたあと、他のすべての動的照会がそうであるように、照会はパッケージ・キャッシュにコンパイルされたかたちで留まることになります。次回同じ照会が実行される際には、DB2は、キャッシュにその項目が見当たらない場合にのみ再コンパイルを行います。

比較的大きな一時テーブルを作成し、そのテーブルを参照する照会を複数回実行する場合、インデックスを定義して、そのインデックスにranstatを実行することを検討してください(後者はもちろんテーブルに値が挿入された後)。これについては、次のセクションに詳述します。

SQLプロシージャーでの一時テーブルの使用について最後にコメントを1つ加えます。同じプロシージャーで作成される一時テーブルに基づいて結果セットを返す必要がある場合、次に示すように、結果セットを入れ子の複合コンポーネント内で定義することが必要です。

CREATE PROCEDURE INSTT2(P1 INT, P2 CHAR(20))
BEGIN
  DECLARE GLOBAL TEMPORARY TABLE TT(C1 INT, C2 CHAR(20)) %
  INSERT INTO SESSION.TT VALUES(P1, P2);
  BEGIN
    DECLARE C1 CURSOR WITH RETURN FOR SELECT * FROM SESSION.TT;
  END;
END %

結果セットを入れ子の複合コンポーネント内で定義しなければならないのは、DECLARE GLOBAL TEMPORARY TABLEは実行可能ステートメントであり、実行可能ステートメントを記述するには、その前にDECLARE CURSORなどの宣言ステートメントが必要となるためです。カーソル定義の後の外部スペースでテーブルを宣言した場合には、DECLARE CURSORコンパイル時に、コンパイル環境でテーブルが利用できず、したがって、コンパイルは失敗することになります。

DB2オプティマイザーの情報を最新に維持する

プロシージャーを作成すると、個別のSQL照会はパッケージを構成する各セクションとしてコンパイルされます。DB2オプティマイザーは、特に、統計統計(たとえば、表サイズ、列のデータ値の相対頻度など)、および照会のコンパイル時に利用可能なインデックスに基づいて照会の実行プランを選択します。テーブルに大きな変更が加えられるときは、DB2によって当該テーブルに関する統計を再収集するのがよいかもしれません。また、統計の更新時に、あるいは新しいインデックスの作成時には、DB2に最新の統計とインデックスを利用するプランを作成させるために、テーブルを使用するSQLプロシージャーに関連付けされたパッケージを再バインドすることも賢明であるかもしれません。

統計統計を更新するには、RUNSTATSコマンドを使用します。SQLプロシージャーに関連付けされたパッケージを再バインドするには、REBIND_ROUTINE_PACKAGEビルトイン・プロシージャーを利用することができます(DB2 Version 8で提供)。たとえば、次のコマンドを使って、MYSCHEMA.MYPROCプロシージャーに対応するパッケージを再バインドすることができます。

CALL SYSPROC.REBIND_ROUTINE_PACKAGE('P', 'MYSCHEMA.MYPROC', 'ANY')

上記で、「P」は、パッケージがプロシージャーに対応していること、「ANY」は、関数とタイプの解決にSQLパス上の任意の関数およびタイプが考慮されることを意味しています(詳しくは『コマンド解説書』の「REBIND」コマンドの項を参照)。

結論

この記事では、SQLプロシージャーのパフォーマンスの改善に役立つと思われるヒントと秘訣を提示しました(SQL PLの優れた入門書としてYipその他の著書が参考になります)。原則として、最初に、システム・パフォーマンス(ハードウェアとOS)とデータベース・マネージャー(バッファー・プール、コンテナー、テーブル・スペースなど)の基本事項がカバーされていることを確認してください。データベース・マネージャーについては、DB2構成アドバイザーが大いに役立つと思われます。その後、実際のアプリケーションにおいて中心となる照会プランが適切であるかどうか確認を行ってください。最後に、この記事のアドバイスを参考にして、ストアード・プロシージャーとアプリケーションの改善を試みてください。パフォーマンスの向上が実現できますよう願っています。

謝辞

この記事のドラフトにコメントを提供してくださったLee Johnson、Paul Yip、Drew Bradstock、Clara Liuに感謝を申し上げます。


ダウンロード可能なリソース


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Information Management
ArticleID=326542
ArticleTitle=SQLプロシージャーのパフォーマンス:そのヒントと秘訣
publish-date=062003