13.1 読取り一貫性のあるロケータ

Oracle DatabaseのLOBに対する読取り一貫性メカニズムは、その他すべてのスカラー量に対するデータベースの読取りおよび更新のための読取り一貫性メカニズムと同じです。

LOBロケータの場合、読取り一貫性にはいくつかの特別な用途があるため、正しく理解しておく必要があります。次の各項では、読取り一貫性について説明し、互いに関連付けながら参照する必要がある例を示します。

関連項目:

13.1.1 読取り一貫性のあるロケータになる、SELECTされたロケータ

読取り一貫性のあるロケータには、SELECT操作の実行時点のスナップショット環境が含まれます。

FOR UPDATE句の有無にかかわらず、SELECTされたロケータは読取り一貫性のあるロケータとなり、LOB値がそのロケータによって更新されるまでは読取り一貫性のあるロケータとして存在します。

これには複雑な意味があります。たとえば、SELECT操作によって読取り一貫性のあるロケータ(L1)を作成したとします。L1によって永続LOB値を読み取るときは、次のことに注意してください。

  • SELECT文にFOR UPDATEが含まれていても、SELECT文実行時点のLOBが読み取られます。

  • 同じトランザクションの別のロケータ(L2)によってLOB値が更新されても、L1ではL2の更新が認識されません。

  • L1では、別のトランザクションによってLOBにコミットされた更新も認識されません。

  • 読取り一貫性のあるロケータL1が別のロケータL2にコピーされた場合(たとえば、2つのロケータ変数のPL/SQL割当てL2:= L1によって)、L2L1と同様に読取り一貫性のあるロケータとなり、読み取られるデータはL1に対するSELECT実行時点のデータとなります。

複数のロケータが存在していることを利用して、変化するLOB値のそれぞれの値にアクセスできます。ただし、その場合は、どのロケータでどの値にアクセスしているかを常に理解しておく必要があります。

13.1.2 LOBの更新および読取り一貫性の例

読取り一貫性のあるロケータでは、SELECT実行時期に関係なく同じLOB値が提供されます。次の例は、読取り一貫性とUPDATE操作の関係を示しています。

print_media表とPL/SQLを使用して、clob_selectedclob_updateおよびclob_copiedの3つのCLOBインスタンスをロケータとして作成します。

次のコード例では、t1からt6が次のように処理されます。

  • 最初のSELECT INTOの実行時(t1)に、ad_sourcetext内の値がロケータclob_selectedに対応付けられます。

  • 次の操作(t2)で、ad_sourcetext内の値がロケータclob_updatedに対応付けられます。t1t2の間ではad_sourcetextの値が変わらないため、clob_selectedおよびclob_updatedの両方のロケータは、異なる時点でのスナップショットを反映していても、同じ値を持つ読取り一貫性のあるロケータとなります。

  • 3つ目の操作(t3)では、clob_selectedの値がclob_copiedにコピーされます。この時点で、3つのロケータが同じ値になります。例では、一連のDBMS_LOB.READ()コールがこれを示しています。

  • t4で、プログラムはDBMS_LOB.WRITE()を使用してclob_updated内の値を変更します。DBMS_LOB.READ()が新しい値を示します。

  • ただし、clob_selectedを介した値のDBMS_LOB.READ()は(t5)、これが読取り一貫性のあるロケータで、SELECTの実行時と同じ値が引き続き参照されることを示します。

  • 同様に、clob_copiedを介した値のDBMS_LOB.READ()は(t6)、これが読取り一貫性のあるロケータで、clob_selectedと同じ値が引き続き参照されることを示します。

例13-1

INSERT INTO print_media VALUES (2056, 20020, EMPTY_BLOB(), 
    'abcd', EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL);

COMMIT;

DECLARE
  num_var           INTEGER;
  clob_selected     CLOB;
  clob_updated      CLOB;
  clob_copied       CLOB;
  read_amount       INTEGER;
  read_offset       INTEGER;
  write_amount      INTEGER;
  write_offset      INTEGER;
  buffer            VARCHAR2(20);

BEGIN
  -- At time t1:
  SELECT ad_sourcetext INTO clob_selected
     FROM Print_media
     WHERE ad_id = 20020;

  -- At time t2:
  SELECT ad_sourcetext INTO clob_updated
     FROM Print_media
     WHERE ad_id = 20020
     FOR UPDATE;

  -- At time t3:
  clob_copied := clob_selected;
  -- After the assignment, both the clob_copied and the
  -- clob_selected have the same snapshot as of the point in time
  -- of the SELECT into clob_selected

  -- Reading from the clob_selected and the clob_copied does
  -- return the same LOB value. clob_updated also sees the same
  -- LOB value as of its select:
  read_amount := 10;
  read_offset := 1;
  DBMS_LOB.READ(clob_selected, read_amount, read_offset, buffer);
  DBMS_OUTPUT.PUT_LINE('clob_selected value: ' || buffer);
  -- Produces the output 'abcd'

  read_amount := 10;
  DBMS_LOB.READ(clob_copied, read_amount, read_offset, buffer);
  DBMS_OUTPUT.PUT_LINE('clob_copied value: ' || buffer);
  -- Produces the output 'abcd'

  read_amount := 10;
  DBMS_LOB.READ(clob_updated, read_amount, read_offset, buffer);
  DBMS_OUTPUT.PUT_LINE('clob_updated value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t4:
  write_amount := 3;
  write_offset := 5;
  buffer := 'efg';
  DBMS_LOB.WRITE(clob_updated, write_amount, write_offset, buffer);

  read_amount := 10;
  DBMS_LOB.READ(clob_updated, read_amount, read_offset, buffer);
  DBMS_OUTPUT.PUT_LINE('clob_updated value: ' || buffer);
  -- Produces the output 'abcdefg'

  -- At time t5:
  read_amount := 10;
  DBMS_LOB.READ(clob_selected, read_amount, read_offset, buffer);
  DBMS_OUTPUT.PUT_LINE('clob_selected value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t6:
  read_amount := 10;
  DBMS_LOB.READ(clob_copied, read_amount, read_offset, buffer);
  DBMS_OUTPUT.PUT_LINE('clob_copied value: ' || buffer);
  -- Produces the output 'abcd'
END;
/

13.1.3 更新済ロケータを介したLOB更新の例

この項では、ロケータを介したLOBの更新について学習します。

LOBロケータ(L1)を介して永続LOBの値を更新する場合、現在のスナップショット環境が含まれるようL1が更新されます。

このスナップショットは、ロケータL1を介してLOB値に対する操作が完了した時点のものです。このため、L1は更新済ロケータと呼ばれます。この操作によって、LOB値に対して行った変更を、同じロケータL1による次の読取り時に参照できます。

ノート:

LOB値を読み取ることのみのためにロケータが使用される場合、ロケータのスナップショット環境は更新されません。更新されるのは、PL/SQL DBMS_LOBパッケージまたはOCI LOB APIを使用して、ロケータを介してLOB値を変更した場合のみです。

別のトランザクションによってコミットされた更新は、そのトランザクションがコミット読取りトランザクションであり、他のトランザクションのコミット後にL1を使用してLOB値を更新する場合のみに、L1によって認識されます。

ノート:

永続LOBの値を更新すると、最新のLOB値が常に変更されます。

OCI LOB APIやPL/SQL DBMS_LOBパッケージなどの使用可能なメソッドによって永続LOBの値を更新することは、LOB値を更新して、新しいLOB値を参照するロケータを再選択することになります。

ノート:

どのような手段でもLOBロケータを選択すればロケータから読み取れますが、書き込めません。

SQLを介したLOB値の更新は、単なるUPDATE文です。UPDATE文によって行われた変更をロケータが認識できるように、LOBロケータを再選択するか、UPDATE文でRETURNING句を使用するかは、ユーザーが決定します。LOBロケータを再選択するかRETURNING句を使用しないかぎり、最新の値を読み取っていない場合でも最新の値を読み取っていると判断する場合があります。このような理由により、SQL DMLをOCIおよびDBMS_LOBピース単位操作と混同しないようにする必要があります。

13.1.4 SQL DMLおよびDBMS_LOBを使用したLOBの更新例

次の例では、print_media表を使用し、CLOBロケータがclob_selectedとして作成されます。

この例では、t1からt3が次のように処理されます。

  • 最初のSELECT INTOの実行時(t1)に、ad_sourcetext内の値がロケータclob_selectedに対応付けられます。

  • 次の操作(t2)では、ロケータclob_selectedに影響を与えずに、SQL UPDATE文を介してad_sourcetext内の値が変更されます。ロケータは元のSELECT時点でのLOB値を参照します。つまり、ロケータはSQL UPDATE文によって実行された更新を認識しません。例では、後続のDBMS_LOB.READ()コールがこれを示しています。

  • 3つ目の操作(t3)では、LOB値が再選択されロケータclob_selectedに挿入されます。これによって、ロケータは最新のスナップショット環境に更新され、先のSQL UPDATE文によって行われた変更を認識できるようになります。このため、次のDBMS_LOB.READ()では、LOB値が空である(データがない)ためエラーが戻されます。

INSERT INTO Print_media VALUES (3247, 20010, EMPTY_BLOB(), 
    'abcd', EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL);

COMMIT;

DECLARE
  num_var           INTEGER;
  clob_selected     CLOB;
  read_amount       INTEGER;
  read_offset       INTEGER;
  buffer            VARCHAR2(20);

BEGIN

  -- At time t1:
  SELECT ad_sourcetext INTO clob_selected
  FROM Print_media
  WHERE ad_id = 20010;

  read_amount := 10;
  read_offset := 1;
  dbms_lob.read(clob_selected, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_selected value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t2:
  UPDATE Print_media SET ad_sourcetext = empty_clob()
      WHERE ad_id = 20010;
  -- although the most current LOB value is now empty,
  -- clob_selected still sees the LOB value as of the point
  -- in time of the SELECT

  read_amount := 10;
  dbms_lob.read(clob_selected, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_selected value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t3:
  SELECT ad_sourcetext INTO clob_selected FROM Print_media WHERE
       ad_id = 20010;
  -- the SELECT allows clob_selected to see the most current
  -- LOB value

  read_amount := 10;
  dbms_lob.read(clob_selected, read_amount, read_offset, buffer);
  -- ERROR: ORA-01403: no data found
END;
/

13.1.5 同じLOB値を更新するために1つのロケータを使用する例

特定のLOB値の更新にはロケータを1つのみ使用することで、多くの問題を回避できます。この項では、その詳細について学習します。

ノート:

異なるロケータを使用して同じLOBを更新しないでください。

次の例では、表print_mediaを使用し、clob_updatedおよびclob_copiedの2つのCLOBをロケータとして作成します。

この例では、t1からt5が次のように処理されます。

  • 最初のSELECT INTOの実行時(t1)に、ad_sourcetext内の値がロケータclob_updatedに対応付けられます。

  • 次の操作(t2)では、clob_updatedの値がclob_copiedにコピーされます。この時点では、両方のロケータが同じ値を参照します。例では、一連のDBMS_LOB.READ()コールがこれを示しています。

  • t3で、プログラムはDBMS_LOB.WRITE()を使用してclob_updated内の値を変更します。DBMS_LOB.READ()が新しい値を示します。

  • ただし、clob_copiedを介した値のDBMS_LOB.READ()は(t4)、clob_updatedから割り当てられた時点(t2)のLOBの値を参照しています。

  • clob_updatedclob_copiedに割り当てられた時点(t5)で初めて、clob_copiedclob_updatedによる更新があったことを認識します。

INSERT INTO PRINT_MEDIA VALUES (2049, 20030, EMPTY_BLOB(), 
    'abcd', EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL);

COMMIT;

DECLARE
  num_var          INTEGER;
  clob_updated     CLOB;
  clob_copied      CLOB;
  read_amount      INTEGER;
  read_offset      INTEGER;
  write_amount     INTEGER;
  write_offset     INTEGER;
  buffer           VARCHAR2(20);
BEGIN

-- At time t1:
  SELECT ad_sourcetext INTO clob_updated FROM PRINT_MEDIA
      WHERE ad_id = 20030
      FOR UPDATE;

  -- At time t2:
  clob_copied := clob_updated;
  -- after the assign, clob_copied and clob_updated see the same
  -- LOB value

  read_amount := 10;
  read_offset := 1;
  dbms_lob.read(clob_updated, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_updated value: ' || buffer);
  -- Produces the output 'abcd'

  read_amount := 10;
  dbms_lob.read(clob_copied, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_copied value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t3:
  write_amount := 3;
  write_offset := 5;
  buffer := 'efg';
  dbms_lob.write(clob_updated, write_amount, write_offset,
        buffer);

  read_amount := 10;
  dbms_lob.read(clob_updated, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_updated value: ' || buffer);
  -- Produces the output 'abcdefg'


  -- At time t4:
  read_amount := 10;
  dbms_lob.read(clob_copied, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_copied value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t5:
  clob_copied := clob_updated;

  read_amount := 10;
  dbms_lob.read(clob_copied, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_copied value: ' || buffer);
  -- Produces the output 'abcdefg'
END;
/

13.1.6 PL/SQL(DBMS_LOB)バインド変数を使用したLOBの更新の例

この項では、PL/SQLバインド変数を使用したLOBの更新について学習します。

別の永続LOBを更新するためのソースとしてLOBロケータを使用する場合(SQLのINSERT文またはUPDATE文、DBMS_LOB.COPYルーチンなど)、ソースLOBロケータ内のスナップショット環境によって、ソースとして使用されるLOB値が決まります。

ソース・ロケータ(たとえばL1)が読取り一貫性のあるロケータの場合、L1SELECT実行時点のLOB値が使用されます。ソース・ロケータ(たとえばL2)が更新済ロケータの場合、更新時におけるL2のスナップショット環境に対応付けられたLOB値が使用されます。

次の例では、clob_selected、clob_updatedおよびclob_copiedの3つのCLOBをロケータとして作成します。

この例では、t1からt5が次のように処理されます。

  • 最初のSELECT INTOの実行時(t1)に、ad_sourcetext内の値がロケータclob_updatedに対応付けられます。

  • 次の操作(t2)では、clob_updatedの値がclob_copiedにコピーされます。この時点では、両方のロケータが同じ値を参照します。

  • この時点で(t3)、プログラムはDBMS_LOB.WRITE()を使用してclob_updated内の値を変更します。DBMS_LOB.READ()が新しい値を示します。

  • ただし、clob_copiedを介した値のDBMS_LOB.READ()は(t4)、clob_copiedclob_updatedによる変更を参照しないことを示します。

  • このため(t5の時点で)、clob_copiedINSERT文の値のソースとして使用する場合、clob_copiedと対応付けられた値が挿入されます(clob_updatedによる新規の変更は反映されません)。これは、その後の、挿入された直後の値に対するDBMS_LOB.READ()によってわかります。

INSERT INTO PRINT_MEDIA VALUES (2056, 20020, EMPTY_BLOB(), 
    'abcd', EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL);

COMMIT;

DECLARE
  num_var           INTEGER;
  clob_selected     CLOB;
  clob_updated      CLOB;
  clob_copied       CLOB;
  read_amount       INTEGER;
  read_offset       INTEGER;
  write_amount      INTEGER;
  write_offset      INTEGER;
  buffer            VARCHAR2(20);
BEGIN

  -- At time t1:
  SELECT ad_sourcetext INTO clob_updated FROM PRINT_MEDIA
      WHERE ad_id = 20020
      FOR UPDATE;

  read_amount := 10;
  read_offset := 1;
  dbms_lob.read(clob_updated, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_updated value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t2:
  clob_copied := clob_updated;


  -- At time t3:
  write_amount := 3;
  write_offset := 5;
  buffer := 'efg';
  dbms_lob.write(clob_updated, write_amount, write_offset, buffer);

  read_amount := 10;
  dbms_lob.read(clob_updated, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_updated value: ' || buffer);
  -- Produces the output 'abcdefg'
  -- note that clob_copied does not see the write made before
  -- clob_updated


  -- At time t4:
  read_amount := 10;
  dbms_lob.read(clob_copied, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_copied value: ' || buffer);
  -- Produces the output 'abcd'

  -- At time t5:
  -- the insert uses clob_copied view of the LOB value which does
  -- not include clob_updated changes
  INSERT INTO PRINT_MEDIA VALUES (2056, 20022, EMPTY_BLOB(), 
    clob_copied, EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL)
    RETURNING ad_sourcetext INTO clob_selected;

  read_amount := 10;
  dbms_lob.read(clob_selected, read_amount, read_offset, buffer);
  dbms_output.put_line('clob_selected value: ' || buffer);
  -- Produces the output 'abcd'
END;
/

13.1.7 ロケータを使用したLOBの削除の例

この項では、PL/SQLバインド変数を使用したLOBの削除について学習します。

次の例では、特定の時点で選択されたロケータを介したLOBコンテンツを、同じトランザクション内でそのLOBが削除されている場合でも使用可能であることを示します。

次の例では、print_media表を使用し、clob_selectedおよびclob_copiedという2つのCLOBをロケータとして作成します。

この例では、t1からt3が次のように処理されます。

  • 最初のSELECT INTOの実行時(t1)に、ad_idの値が20020であるad_sourcetextの値が、ロケータclob_selectedに対応付けられます。ad_idの値が20021であるad_sourcetextの値は、ロケータclob_copiedに関連付けられます。
  • 2つ目の操作(t2)では、ad_idの値が20020である行が削除されます。ただし、clob_selectedを介した値のDBMS_LOB.READ()は(t1)、これが読取り一貫性のあるロケータであり、SELECTの実行時と同じ値が引き続き参照されることを示します。
  • 3つ目の操作(t3)では、clob_selectedを介して読み取られたLOBデータがLOB clob_copiedにコピーされます。clob_selectedおよびclob_copiedを介した値のDBMS_LOB.READ()はこれと同じになり、clob_selectedSELECTの実行時と同じ値を参照します。
INSERT INTO PRINT_MEDIA VALUES (2056, 20020, EMPTY_BLOB(),
    'abcd', EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL);

INSERT INTO PRINT_MEDIA VALUES (2057, 20021, EMPTY_BLOB(),
    'cdef', EMPTY_CLOB(), EMPTY_CLOB(), NULL, NULL, NULL, NULL);

DECLARE
  clob_selected CLOB;
  clob_copied CLOB;
  buffer VARCHAR2(20);
  read_amount INTEGER := 20;
  read_offset INTEGER := 1;

BEGIN
  -- At time t1:
  SELECT ad_sourcetext INTO clob_selected 
      FROM PRINT_MEDIA
      WHERE ad_id = 20020
      FOR UPDATE;

  SELECT ad_sourcetext INTO clob_copied 
      FROM PRINT_MEDIA
      WHERE ad_id = 20021
      FOR UPDATE;

  dbms_lob.read(clob_selected, read_amount, read_offset,buffer);
  dbms_output.put_line(buffer);
  -- Produces the output 'abcd'

  dbms_lob.read(clob_copied, read_amount, read_offset,buffer);
  dbms_output.put_line(buffer);
  -- Produces the output 'cdef'

  -- At time t2: Delete the CLOB associated with clob_selected
  DELETE FROM PRINT_MEDIA WHERE ad_id = 20020;

  dbms_lob.read(clob_selected, read_amount, read_offset,buffer);
  dbms_output.put_line(buffer);
  -- Produces the output 'abcd'

  -- At time t3:
  -- Copy using clob_selected
  dbms_lob.copy(clob_copied, clob_selected, 4000, 1, 1);
  dbms_lob.read(clob_copied, read_amount, read_offset,buffer);
  dbms_output.put_line(buffer);
  -- Produces the output 'abcd'

END;
/

13.1.8 読取り一貫性の保証

この項のスクリプトを使用すると、NOLOGGINGまたはFILESYSTEM_LIKE_LOGGING LOBを持つ表のホット・バックアップを取得でき、読取り非一貫性なしでリカバリ・ポイントが既知であることを確認できます。

ALTER DATABASE FORCE LOGGING;
SELECT CHECKPOINT_CHANGE# FROM V$DATABASE;  --Start SCN

SCN(システム変更番号)は、トランザクションがコミットされた時点のデータベースのバージョンを定義するスタンプです。

バックアップを実行します。

次のスクリプトを実行します。

ALTER SYSTEM CHECKPOINT GLOBAL;
SELECT CHECKPOINT_CHANGE# FROM V$DATABASE;  --End SCN
ALTER DATABASE NO FORCE LOGGING;

データベースで生成されたアーカイブ・ログをバックアップします。少なくとも、開始SCNと終了SCN(両方のSCN時点を含む)の間のアーカイブ・ログはバックアップする必要があります。

読取り非一貫性のない時点までリストアするため、不完全なリカバリの時点として終了SCNまでリストアします。終了SCNより後のSCNにリカバリを実行すると、NOLOGGING LOBで読取り非一貫性が発生する可能性があります。

SecureFilesでは、メディア・リカバリ中に読取り非一貫性が検出されると、データベースにより一貫性のないブロックがホールとして処理され、BLOB0が、CLOBに充填文字が格納されます。