15 パフォーマンスのガイドライン

LOBデータ型を使用するアプリケーションのパフォーマンスに関するガイドラインがあります。

LOBパフォーマンスのガイドライン

LOBには、データ・インタフェースを使用するかLOB APIを使用してアクセスできます。この項では、LOBを使用する際のパフォーマンス・ガイドラインを示します。

関連トピック

すべてのLOB

次のガイドラインは、LOB使用時のパフォーマンスを最大にし、サーバーとのラウンドトリップの回数を最小にするために役立ちます。
  • I/Oを最小限にするには:

    • ブロック境界でデータの読取りおよび書込みを行います。これにより、UNDO生成を最小限にするなどの様々な方法でI/Oが最適化されます。一時LOBおよびSecureFile LOBの場合、表領域ブロック・サイズの使用可能データ領域は、PLSQLではDBMS_LOB.GETCHUNKSIZEというAPI、OCIではOCILobGetChunkSize()によって戻されます。ループで書き込む場合は、1つのデータベース・ブロックに書き込む必要があるすべての内容を1回の書込みコールで書き込むようにコードを設計し、それによって、同じブロックに連続的に書き込まれることがないようにします。

    • 一度に大量のデータの読取りおよび書込みを行います。

    • 前述の2つの推奨事項を組み合せて行うには、DBMS_LOB.GETCHUNKSIZE/OCILobGetChunkSize() APIによって返されたデータベース・ブロック・サイズに大きい整数を乗算したサイズで、読取りおよび書込みを行います。

  • サーバーとのラウンドトリップの回数を最小限にするには:
    • LOBデータの最大サイズを把握しており、LOB全体を読み取るか書き込む場合は、次に説明するように、データ・インタフェースを使用します。LOB全体のサイズを単一のバッファとして割り当てるか、ピース単位方式またはコールバック方式を使用することができます。

      • 読取り操作の場合は、OCIでOCIDefineByPos()関数、JDBCでDefineColumnType()関数を使用して、LOBを文字型またはバイナリ型として定義します。

      • 書込み操作の場合は、OCIでOCIBindByPos()関数、JDBCでsetString()メソッドまたはsetBytes()メソッドを使用して、LOBを文字型またはバイナリ型としてバインドします。

    • それ以外の場合は、次のようにLOB APIを使用します。

      • 読取りにはLOBのプリフェッチを使用します。その列に大部分のLOB値を含めることができるようにLOBプリフェッチ・サイズを定義します。

      • OCILobRead2操作またはOCILobWrite2操作を使用する際にピース単位方式またはコールバック方式を使用し、サーバーとのラウンドトリップを最小限にします。

永続LOB

前述の、すべてのLOBに当てはまるパフォーマンス・ガイドラインに加え、ここでは、永続LOBを使用する際のパフォーマンス・ガイドラインを示します。

  • 1つのトランザクション内でコールを連続して行うことで、単一のLOBへの書込みを最大限にします。頻繁にLOBを切り替えるかDML文をインターリーブすると、キャッシュが最大効率になるのを防ぐことができます。
  • セーブポイントの作成やコミットは頻繁に実行しないでください。これにより、書込みの間のキャッシュの利点がなくなります。

注意:

永続LOBの格納にはSecurefile LOBを使用することをお薦めしています。そのため、この章では、Securefileストレージのみに焦点を当てます。永続LOBに関する文脈では、LOBとは、他に示されていないかぎり、Securefile LOBを意味します。

一時LOB

一般的なLOBパフォーマンスに関するガイドラインLOBパフォーマンスのガイドラインに加えて、一時LOBの使用に対するガイドラインを次に示します。

  • パフォーマンスを向上させるために、PGAメモリーを使用して一時LOBを格納します。

  • デフォルトのシステム表領域のかわりに、一時LOB記憶域に別の一時表領域を使用します

    これによって、データを永続LOBから一時LOBにコピーする場合のデバイスの競合を回避します。

    新しく提供された拡張SQLセマンティクス機能をアプリケーションで使用する場合、以前よりさらに多くの一時LOBがSQLおよびPL/SQLで暗黙的に作成されます。これらの一時LOBを格納するための一時表領域が、使用しているアプリケーションに対して十分大きいことを確認してください。特に、これらの一時LOBが暗黙的に作成されるのは、次の事項を使用または実行する場合です。

    • LOBに対するSQLファンクション

    • LOBに対するPL/SQL組込み文字ファンクション

    • VARCHAR2からCLOBRAWからBLOBへの変数割当て。

    • LONGからLOBへの移行

  • LOBでSQL演算子を使用する場合、PGAメモリーおよび一時表領域は、SQL演算子によって生成される一時LOBを格納するのに十分な大きさがある必要があります。

  • SQL問合せおよびPL/SQLプログラムから戻された一時LOBを解放します

    PL/SQL、C(OCI)、Javaおよび他のプログラム・インタフェースでは、SQL問合せの結果またはPL/SQLプログラムの実行によって、LOBのオペレーション・コールおよびファンクション・コールに対して一時LOBが戻されます。次に例を示します。

    SELECT substr(CLOB_Column, 4001, 32000) FROM ... 
    

    PL/SQLで問合せが実行される場合、戻された一時LOBは、PL/SQLプログラム・ブロックの終了時に自動的に解放されます。任意の時点で、明示的に一時LOBを解放することもできます。OCIおよびJavaでは、戻された一時LOBを明示的に解放する必要があります。

    SQL問合せから戻された一時LOBの割当てを適切に解除しないと、一時表領域が一杯となり、パフォーマンスが低下します。

  • PL/SQLでは、可能なかぎりNOCOPYを使用して、参照によって一時LOBパラメータを渡します。

    関連項目:

    参照によるパラメータの受渡しおよびパラメータのエイリアシングの詳細は、Oracle Database PL/SQL言語リファレンスを参照してください

  • 一時LOBに対してバッファ・キャッシュを利用します。

    CACHEパラメータをTRUEに設定して作成した一時LOBは、バッファ・キャッシュを介して移動できます。そうでない場合、一時LOBの読取りおよび書込みはディスクから直接行われます。

  • 最適なパフォーマンスを実現するために、一時LOBでは、読取り時参照、書込み時コピーのセマンティクスが使用されます。一時LOBロケータが別のロケータに割り当てられる場合、物理LOBデータはコピーされません。どちらかのLOBロケータを使用する以降のREAD操作では、同じ物理LOBデータが参照されます。割当て後の最初のWRITE操作では、LOB値のセマンティクスを保ち、各ロケータが確実に一意のLOB値を指すように、物理LOBデータがコピーされます。このパフォーマンスの考慮事項は、主としてPL/SQL環境とOCI環境に適用されます。

    PL/SQLでは、読取り時参照、書込み時コピーのセマンティクスは次のように示されます。

    LOCATOR1 BLOB; 
    LOCATOR2 BLOB; 
    DBMS_LOB.CREATETEMPORARY (LOCATOR1,TRUE,DBMS_LOB.SESSION); 
    
    -- LOB data is not copied in this assignment operation:  
    LOCATOR2 := LOCATOR;  
    -- These read operations refer to the same physical LOB copy: 
    DBMS_LOB.READ(LOCATOR1, ...); 
    DBMS_LOB.GETLENGTH(LOCATOR2, ...); 
    
    -- A physical copy of the LOB data is made on WRITE:  
    DBMS_LOB.WRITE(LOCATOR2, ...); 
    

    OCIでは、LOBロケータおよびデータの値セマンティクスを保証するために、OCILobLocatorAssign()を使用してLOBデータと一時LOBロケータがコピーされます。OCILobLocatorAssign()の場合、サーバーへのラウンドトリップは発生しません。物理的な一時LOBコピーは、次のように、LOB更新APIと同じラウンドトリップでLOBの更新が発生する場合に実行されます。

    OCILobLocator *LOC1;
    OCILobLocator *LOC2;
    OCILobCreateTemporary(... LOC1, ... TRUE,OCI_DURATION_SESSION);
    
    /* No round-trip is incurred in the following call. */
    OCILobLocatorAssign(... LOC1, LOC2);
    
    /* Read operations refer to the same physical LOB copy. */
    OCILobRead2(... LOC1 ...)
    
    /* One round-trip is incurred to make a new copy of the
     * LOB data and to write to the new LOB copy.
     */
    OCILobWrite2(... LOC1 ...)
    
    /* LOC2 does not see the same LOB data as LOC1. */
    OCILobRead2(... LOC2 ...)
    
    

    LOB値セマンティクスを意図していない場合は、次のように、Cのポインタにより参照セマンティクスを使用できます。

    OCILobLocator *LOC1;
    OCILobLocator *LOC2;
    OCILobCreateTemporary(... LOC1, ... TRUE,OCI_DURATION_SESSION);
    
    /* Pointer is copied. LOC1 and LOC2 refer to the same LOB data. */
    LOC2 = LOC1;
    
    /* Write to LOC2. */
    OCILobWrite2(...LOC2...)
    
    /* LOC1 sees the change made to LOC2. */
    OCILobRead2(...LOC1...)
    
  • 一時LOBに対してOCI_OBJECTモードを使用します。

    LOB割当てでの一時LOBのパフォーマンスを向上させるには、OCILobLocatorAssign()に対してOCI_OBJECTモードを使用します。OCI_OBJECTモードでは、データベースはディープ・コピー回数を最小限にしようとします。そのため、OCI_OBJECTモードでソース一時LOBに対してOCILobLocatorAssign()が実行された後、ソース・ロケータおよび宛先ロケータは、いずれかのLOBロケータにより変更されるまで同一LOBを指すようになります。

スレッド環境におけるLOBへのデータの移動

スレッド環境では、LOBへのデータの移動に使用できる手順が2つありますが、その1つは適切ではありません。

推奨手順

注意:

  • この手順では、空のLOBを作成する必要はありません。

  • INSERT/UPDATE文にRETURNING句を使用することで、ロックされたLOBロケータを戻すことができます。これによって、前述のステップ3のSELECT-FOR-UPDATEを行う必要がなくなります。

推奨手順は次のとおりです。

  1. 空のLOBをINSERTし、LOBロケータをRETURNINGします。

  2. このロケータを使用してデータをLOBに移動します。

  3. COMMITします。これでROWロックが解放され、LOBデータが永続になります。

また、LOB列またはLOB属性の4000バイトを超えるデータを直接挿入できます。

不適切な手順

スレッド環境を使用する場合に新規の接続が必要となる次の手順は、パフォーマンスに悪影響を与えるため、お薦めしません。

  1. 空の(NULLでない)LOBを作成します。

  2. 空のLOBを使用してINSERTを実行します

  3. 入力直後の行をSELECT-FOR-UPDATEします。

  4. データをLOBに移動します。

  5. COMMITします。これでROWロックが解放され、LOBデータが永続になります。

LOBアクセス統計

Oracle Database 10gリリース2から、LOB固有の3つのセッションレベルの統計、LOB読取り、LOB書込みおよび未調整LOB書込みが提供されています。

セッション統計は、V$MYSTATV$SESSTATおよびV$SYSSTATの動的パフォーマンス・ビューからアクセスできます。これらのビューに対して問合せを行うには、SELECT_CATALOG_ROLESELECT ON SYS.V_$MYSTATビューおよびSELECT ON SYS.V_$STATNAMEビューの権限が必要です。

LOB読取りは、セッションまたはシステム内で実行されるLOB APIの読取り操作の合計として定義されています。1回のLOB API読取りが、複数回の物理/論理ディスク・ブロック読取りに相当する場合もあります。

LOB書込みは、セッションまたはシステム内で実行されるLOB APIの書込み操作の合計として定義されています。1回のLOB API書込みが、複数回の物理/論理ディスク・ブロック書込みに相当する場合もあります。

未調整LOB書込みは、開始オフセットまたはバッファ・サイズがLOBのブロック境界に揃えられていないLOB API書込み操作の数として定義付けられています。ブロックの境界に揃えられた書込みが、最も効率のよい書込み操作となります。LOBの使用可能なブロック・サイズは、LOB API(たとえば、PL/SQLを使用する場合はDBMS_LOB.GETCHUNKSIZE())を使用して取得できます。

次の簡単な例は、LOBの読取りまたは書込み操作時に、LOBセッション統計が更新される様子を示しています。

セッション統計は、単一セッション中にアクセスされたすべてのLOBに対する操作を総計した統計です。統計はオブジェクトごと(表、列、セグメント、オブジェクト番号など)に分離または分類されていません。

この例では、各デモごとにデータベースに再接続してV$MYSTATをクリアします。これは、同一セッションで実行された過去のLOB操作の結果を不明瞭にすることなく、テスト対象の操作によりLOB統計がどのように変化するかを確認できるようにするためです。

関連項目:

『Oracle Databaseリファレンス』の付録E「統計の説明」

LOBアクセス統計の取得

この例では、LOBアクセス統計の取得を示します。

rem
rem Set up the user
rem
 
CONNECT / AS SYSDBA;
SET ECHO ON; 
GRANT SELECT_CATALOG_ROLE TO pm;
GRANT SELECT ON sys.v_$mystat TO pm;
GRANT SELECT ON sys.v_$statname TO pm;
 
rem
rem Create a simplified view for statistics queries
rem
 
CONNECT pm;
SET ECHO ON;
 
DROP VIEW mylobstats;
CREATE VIEW mylobstats
AS
SELECT  SUBSTR(n.name,1,20) name,
        m.value             value
FROM    v$mystat    m,
        v$statname  n
WHERE   m.statistic# = n.statistic#
    AND n.name LIKE 'lob%';
 
rem
rem Create a test table
rem
 
DROP TABLE t;
CREATE TABLE t (i NUMBER, c CLOB)
    lob(c) STORE AS (DISABLE STORAGE IN ROW);
 
rem
rem Populate some data
rem
rem This should result in unaligned writes, one for
rem each row/lob populated.
rem

CONNECT pm
SELECT * FROM mylobstats;
INSERT INTO t VALUES (1, 'a');
INSERT INTO t VALUES (2, rpad('a',4000,'a'));
COMMIT;
SELECT * FROM mylobstats;
 
rem
rem Get the lob length
rem
rem Computing lob length does not read lob data, no change
rem in read/write stats.
rem
 
CONNECT pm;
SELECT * FROM mylobstats;
SELECT LENGTH(c) FROM t;
SELECT * FROM mylobstats;
 
rem
rem Read the lobs
rem
rem Lob reads are performed, one for each lob in the table.
rem
 
CONNECT pm;
SELECT * FROM mylobstats;
SELECT * FROM t;
SELECT * FROM mylobstats;
 
rem
rem Read and manipulate the lobs (through temporary lobs)
rem
rem The use of complex operators like "substr()" results in
rem the implicit creation and use of temporary lobs. operations
rem on temporary lobs also update lob statistics.
rem
 
CONNECT pm;
SELECT * FROM mylobstats;
SELECT substr(c, length(c), 1) FROM t;
SELECT substr(c, 1, 1) FROM t;
SELECT * FROM mylobstats;
 
rem
rem Perform some aligned overwrites
rem
rem Only lob write statistics are updated because both the
rem byte offset of the write, and the size of the buffer
rem being written are aligned on the lob block size.
rem
 
CONNECT pm;
SELECT * FROM mylobstats;
DECLARE
    loc     CLOB;
    buf     LONG;
    chunk   NUMBER;
BEGIN
    SELECT c INTO loc FROM t WHERE i = 1
        FOR UPDATE;
 
    chunk := DBMS_LOB.GETCHUNKSIZE(loc);
    chunk = chunk * floor(32767/chunk); /* integer multiple of chunk */
    buf   := rpad('b', chunk, 'b');
 
    -- aligned buffer length and offset
    DBMS_LOB.WRITE(loc, chunk, 1, buf);
    DBMS_LOB.WRITE(loc, chunk, 1+chunk, buf);
    COMMIT;
END;
/
SELECT * FROM mylobstats;
 
rem
rem Perform some unaligned overwrites
rem 
rem Both lob write and lob unaligned write statistics are
rem updated because either one or both of the write byte offset
rem and buffer size are unaligned with the lob's chunksize.
rem 
 
CONNECT pm;
SELECT * FROM mylobstats;
DECLARE
    loc CLOB;
    buf LONG;
BEGIN
    SELECT c INTO loc FROM t WHERE i = 1
        FOR UPDATE;
 
    buf := rpad('b', DBMS_LOB.GETCHUNKSIZE(loc), 'b');
 
    -- unaligned buffer length
    DBMS_LOB.WRITE(loc, DBMS_LOB.GETCHUNKSIZE(loc)-1, 1, buf);
 
    -- unaligned start offset
    DBMS_LOB.WRITE(loc, DBMS_LOB.GETCHUNKSIZE(loc), 2, buf);
 
    -- unaligned buffer length and start offset
    DBMS_LOB.WRITE(loc, DBMS_LOB.GETCHUNKSIZE(loc)-1, 2, buf);
 
    COMMIT;
END;
/
SELECT * FROM mylobstats;
DROP TABLE t;
DROP VIEW mylobstats;
 
CONNECT / AS SYSDBA
REVOKE SELECT_CATALOG_ROLE FROM pm;
REVOKE SELECT ON sys.v_$mystat FROM pm;
REVOKE SELECT ON sys.v_$statname FROM pm;
 
QUIT;