2008年04月25日

●基本スタンバイ・データベースを構築する<その2 スタンバイサイト>

<スタンバイサイト>
まずはコールドバックアップした、
・データファイル
・REDOログファイル
・アーカイブログファイル
・パスワードファイル
を確認しましょう。

プライマリサイトで格納されていたディレクトリが、
スタンバイサイトでも「完全に」再現されるように
配置します。

スタンバイ用に作成した制御ファイルは、
プライマリでの制御ファイル配置ディレクトリと
同じ場所にでも置いておきましょう。
(異なっていてもかまいませんが)

プライマリで制御ファイルを多重化していた場合、
スタンバイサイトでもコピーして同数と
なるようにしましょう。

次に転送した初期化パラメータファイルです。
制御ファイルの配置ディレクトリ・名前を
変更していなければ特に変える必要は
ありません。
変更している場合は、パラメータファイルを
スタンバイ環境に合わせて修正しましょう。

control_files = (
E:\ORADATA\HOGEDB\CONTROL01.CTL,
E:\ORADATA\HOGEDB\CONTROL02.CTL,
E:\ORADATA\HOGEDB\CONTROL03.CTL

)

oradimコマンドでスタンバイデータベース用の
サービスを作成します。

D:\> oradim -new -sid HOGEDB -startmode manual -srvcstart system
インスタンスが作成されました。

コンピュータの再起動時に、インスタンスのみ開始されて
データベースが自動オープンしないようにします。

他、ORACLE_HOMEやORACLE_SIDの環境変数も
セットしておきましょう。

ではサービスを開始して、インスタンスを開始した後、
スタンバイ・データベースにnomountで接続しましょう。

SQL> conn sys/パスワード as sysdba
アイドル・インスタンスに接続しました。

SQL> startup nomount pfile='転送したパラメータファイルのフルパス'
ORACLEインスタンスが起動しました。
・・・
・・・

最後にスタンバイ・モードでマウントします。

SQL> ALTER DATABASE MOUNT STANDBY DATABASE;
データベースが変更されました。

これでスタンバイ環境の準備ができました。
早速プライマリサイトから転送したアーカイブ・ログファイルを
適用してみましょう。

SQL> set autorecovery on
SQL> recover standby database;

SQL Plusに対してautorecovery onを指定すると、
必要なアーカイブ・ログファイルを問い合わせ無しに
自動で適用します。

以後、プライマリサイトで作成されるアーカイブログは
全てスタンバイサイトへ転送し、上記のように
適用する必要があります。

一つでもアーカイブログを消失してしまうと、
スタンバイサイトは意味がなくなってしまいます。

(以降のログが適用できなくなり、データは
古いままとなります)

プライマリサイトでSPFILE運用をしていた場合、
スタンバイサイトでも揃えておいた方がよさそうですね。

SQL> CREATE SPFILE='D:\***\spfile***.ora' FROM PFILE;

サーバー再起動の際は、上記サービスの設定により、
インスタンス:開始 データベース:停止
の状態になります。

SPFILE運用であれば、
SQL> conn sys/パスワード as sysdba
SQL> startup nomount
SQL> ALTER DATABASE MOUNT STANDBY DATABASE;
のようにして、スタンバイ・データベースをマウントします。

スタンバイサイトはずっとマウント状態のまま運用します。

プライマリサイトが障害により使用不可となった場合、
スタンバイサイトをプライマリとして昇格させ、
読み書きができるようにします(アクティブ化)。

SQL> ALTER DATABASE ACTIVATE STANDBY DATABASE;
SQL> shutdown immediate
SQL> startup

一度アクティブにすると、スタンバイには戻せなくなります。

後はクライアントアプリケーション側の接続先を
全て昇格されたサーバーになるように設定すれば
完了です。
事前に手順をキチっと定めておかないと、
いざ障害発生という時に手違い/漏れが出ちゃいそうですね。

2008年04月23日

●基本スタンバイ・データベースを構築する<その1 プライマリサイト>

基本スタンバイ〜にはいくつか前提条件があります。

・スタンバイサイトのOracle Databaseは、プライマリサイトと
 同バージョン、同エディションであること。
 (パッチの適用状態も同じであること)
・OSについてもスタンバイ−プライマリ双方で
 同バージョン、同パッチ適用であること
・データベースはアーカイブ・ログモードであること
・スタンバイサイトのディスクは、プライマリサイトの
 データファイル・REDOログファイル等を全て同ディレクトリ階層で
 コピーできる容量があること。

今回試した環境は

プライマリ機:
Windows 2000 Server SP4
Oracle Database Standard Edition 10.2.0.2.0
データベース作成済み(アーカイブ・ログモード)
アーカイブ・ログファイルの出力先はフラッシュ・リカバリ領域

スタンバイ機:
Windows 2000 Server SP4
Oracle Database Standard Edition 10.2.0.2.0

です。

以降全てSYSユーザーにて作業していると
ご理解ください。

<プライマリサイト>
最初にオンラインREDOログに残っているトランザクションを
アーカイブログに吐き出しておきましょう。

SQL> ALTER SYSTEM SWITCH LOGFILE;
システムが変更されました。

ログをスイッチすれば、カレントだったREDOログが
アーカイブされます。

次にスタンバイサイト用の制御ファイルを作成します。

SQL> ALTER DATABASE CREATE STANDBY CONTROLFILE AS 'D:\****.CTL';
データベースが変更されました。

SQL> ALTER SYSTEM SWITCH LOGFILE;
システムが変更されました。

スタンバイ用の制御ファイルは必ず上記コマンドで
作成する必要があります。
プライマリサイトで使用している制御ファイルを
スタンバイへ持っていっても使えません
のでお間違えなく。
ALTER DATABASE CREATE・・・文の「AS」以降で作成する
ファイルをフルパスで指定します。
場所はどこでもかまいません。
名前も何でもかまいません。(一般にはプライマリサイトの
制御ファイルとの混同・誤操作を避けるために、
プライマリとは別の名前を付けることを推奨されています)

スタンバイ用の制御ファイルを作成したら、念のため
もう一度ログスイッチをしておきます。

作成したスタンバイ用制御ファイルは、スタンバイ機へ
転送しておきましょう。後で使います。

次にデータベースをシャットダウンして、コールドバックアップ
をスタンバイサイトへ転送します。

SQL> shutdown immediate
データベースがクローズされました。
・・・
・・・

スタンバイサイトへ転送するのは
・データファイル
・REDOログファイル
・アーカイブログファイル
・パスワードファイル
です。

制御ファイルは先にスタンバイ用に作成したものを
使いますので、プライマリデータベースのものは
転送しません。

転送し終わったら、プライマリデータベースは
起動しておきましょう。

SQL> startup
※(SPFILE運用の場合)

また、プライマリのデータベースがSPFILE運用
である場合、テキストベースの初期化パラメータ
ファイルを作成して転送しておきましょう。

SQL> CREATE PFILE='D:\init***.ORA' from spfile;

テキストベースの初期化パラメータファイルを
使用していた場合はそれを転送します。

長くなったので今回はここまで。

2008年04月22日

●基本スタンバイ・データベースを考える

今回はOracle 基本スタンバイ・データベースについて
書いてみようと思います。
当機能はいわゆる「ディザスタリカバリ」「HA構成」の
範疇に入るもので、高可用性を提供します。

Oracleが提供する高可用性製品(機能)としては
Data Guardが有名ですが、今回これは取り上げません。
また、Data GuardはOracleサポート製品(機能)ですが、
基本スタンバイ〜は・・・?です。
あくまでData Guard導入前の検証用くらいに
捉えたほうが無難です。

基本スタンバイ〜も、Data Guardも基本的な
考え方は同じです。

本番データベースをプライマリサイトと位置付け、
別途データベースサーバーを用意し、スタンバイサイト
として設定します。
プライマリサイトで作成するアーカイブログファイルを
スタンバイサイトに転送し、同ファイルをスタンバイ
のデータベースに適用することにより、両サイトの
データ内容が同期されます。
(ということは、データベースはアーカイブログ・
モードである必要があります)
プライマリサイトに障害が発生した場合は、
スタンバイサイトをプライマリサイトに昇格させて、
以後本番データベースとして扱うわけです。
(フェイルオーバー)

基本スタンバイ〜とData Guardの違いは
大雑把にいうと以下の通り。

・プライマリからスタンバイへアーカイブログ転送
 Data Guardは自動、基本スタンバイ〜は手動
・スタンバイデータベースへのアーカイブログ適用
 Data Guardは自動、基本スタンバイ〜は手動
・障害発生時の自動フェイルオーバー
 Data Guardはあり(10gR2より)、基本スタンバイ〜は手動

と、まぁ、基本スタンバイ〜はなんでも人の手を
介する必要があるってことです。
手間を考えればスクリプトを自作することになるでしょう。

ちなみにData GuardはEnterprise Editionのオプション製品
であり、Databaseとは別にライセンスが必要となります。
プライマリ・スタンバイ双方にライセンスが必要と
なりますから、Data Guardでの最小構成では、

Database Enterprise Edition * 2 + Data Guard * 2

のライセンスが必要ということですね。
年間サポート料金もDatabaseとData Guardは別です。

対して基本スタンバイ〜は、Standard Editionから
利用できます。オプション機能ではないのでStdが
2台あれば構成できます。

ただし繰り返しますが、サポートの面を考えると
Data Guardの方が絶対に安心して使えます。

長くなりそうなので記事を分けます。

2008年04月16日

●Oracle Textで全文検索

以前PostgreSQLにおけるTsearch2を利用した全文検索を
試してみました。
Tsearch2で全文検索

今回はOracleの「Oracle Text」機能を試してみたいと思います。
ちなみにOracle 10gの「Oracle Text」はdbcaを使用した場合、
標準インストールされる機能です。
(Ora8ではConText、8iではinterMedia Textという名称で
オプション機能でした)

OracleVer:10.2.0.2
DBキャラクタセット:JA16SJISTILDE

dbcaでデータベースを作成した場合、「CTXSYS」ユーザーが
作成されているはずです。(DBA_USERS表で確認しましょう)

Oracle Textが有効であるかも確認してみましょう。

SQL> conn SYS/パスワード
SQL> SELECT COMP_NAME,STATUS,substr(VERSION,1,10) AS VER
  2 FROM DBA_REGISTRY
  3 WHERE COMP_ID = 'CONTEXT';

COMP_NAME STATUS VER
------------ ------ -----------
Oracle Text VALID 10.2.0.2.0


SQL>SELECT OBJECT_TYPE,OBJECT_NAME,STATUS
  2 FROM DBA_OBJECTS
  3 WHERE OWNER='CTXSYS' AND STATUS != 'VALID'
  4 ORDER BY OBJECT_TYPE,OBJECT_NAME;

レコードが選択されませんでした。

索引付けを行なう際の言語を、プリファレンスとして
作成します。プリファレンス名とレクサー(トークンを生成するアルゴリズム)を
引数に「ctx_ddl.create_preference」プロシージャを実行します。

SQL> conn ユーザー名/パスワード
SQL> execute ctx_ddl.create_preference('hoge_lexer', 'JAPANESE_LEXER');

「hoge_lexer」がプリファレンス名で、「JAPANESE_LEXER」が
指定する日本語レクサーです。
実行するユーザーには「CTXAPP」ロール権限が必要です。
権限が無い場合は付与してCTXSYSユーザーから付与して
あげましょう。

では検索用のテーブルを作成してみましょう。

SQL> CREATE TABLE TBL (
  2 KEY NUMBER(2) PRIMARY KEY,
  3 ORIGTEXT VARCHAR2(100)
  4 );

Tsearch2と違い、Oracle Textでは分かち書きして
トークンを作成してくれるので、分かち書き用(インデックス用)の
列は不要です。

データをINSERTします。

INSERT INTO TBL VALUES(1,
'ルウム戦役で5隻の戦艦がシャア一人の為に撃破された…に、逃げろーッ!');
INSERT INTO TBL VALUES(2,
'このタイミングで戦闘を仕掛けたと言う事実は、古今例が無い。');
INSERT INTO TBL VALUES(3,
'そのために私のような女を大佐は拾って下さったんでしょう?');
INSERT INTO TBL VALUES(4,
'赤い色のMS!シャアじゃないのか?');
INSERT INTO TBL VALUES(5,
'一年戦争開戦初期、1 月 15 日からサイド 5 (ルウム) にて行われた一大艦隊戦役');
INSERT INTO TBL VALUES(6,
'シャアが、MSに乗って輝いていたのは1stだけ。ms乗りとしては・・・');
COMMIT;

索引を作ります。
INDEXTYPEに「CTXSYS.CONTEXT」を指定してテキスト索引であることを明示します。
PARAMETERSには、先に作成したプリファレンス名を指定します。

CREATE INDEX WAKACHI_INDEX ON TBL(ORIGTEXT)
INDEXTYPE IS CTXSYS.CONTEXT
PARAMETERS('lexer hoge_lexer');

お・・・。ちょい時間かかるな。

作成できたら実際に検索する前に、作成されたトークンを確認
してみましょう。
これは「DR$<インデックス名>$I」という索引表で確認できます。

SQL> SELECT TOKEN_TEXT FROM DR$WAKACHI_INDEX$I ORDER BY TOKEN_TEXT;

TOKEN_TEXT
---------------------------
1
15
1ST
5
MS
(略)
サイド
シャア
シャア
タイミング
(略)
赤い
戦艦
戦争
(略)

79行が選択されました。

よさげですね。

では実際に検索してみましょう。

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'シャア')>0;

KEY ORIGTEXT
--- ------------------------
  1 ルウム戦役で5隻の戦艦がシャア一人の為に撃破された…に、逃げろーッ!
  4 赤い色のMS!シャアじゃないのか?
  6 シャアが、MSに乗って輝いていたのは1stだけ。ms乗りとしては・・・

「シャア」を含むKey「1」「4」「6」が検索されました。
ここで注目したいのは、半角カナ「シャア」で登録したKey「6」も、
全角カナ「シャア」での検索にヒットすることです。
Oracleが「ゆれ」も解決してくれているわけですね。かしこい!

これはデータに全角/半角、大文字/小文字それぞれで登録した
「MS」(「MS」「ms」)についても同じことが言えます。
試してみてください。

また、Tsearch2で問題となった、トークンが分けられている複数の
単語を連結して検索する動作はどうなるのでしょう。
「ルウム戦役」というキーワードは「ルウム」と「戦役」に
分かち書きされるので、Tsearch2では「ルウム戦役」という
検索は工夫しないとできませんでした。

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'ルウム戦役')>0;

KEY ORIGTEXT
--- ------------------------
  1 ルウム戦役で5隻の戦艦がシャア一人の為に撃破された…に、逃げろーッ!

すごい! Oracleかしこい! さすが商用RDBMS!

なお、一度索引を作成した場合、その後テキスト検索対象列に
追加/更新/削除が発生しても、トークンは同期されません。

SQL> INSERT INTO TBL VALUES(7,
  2 '私は父ジオン・ズム・ダイクンの元に召されるであろう!!!');
SQL> COMMIT;

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'ジオン')>0;
レコードが選択されませんでした。

トークンを同期させるには
1.ctx_ddl.sync_indexプロシージャを実行
2.索引をREBUILD
する必要があります。

SQL> execute ctx_ddl.sync_index('WAKACHI_INDEX');
PL/SQLプロシージャが正常に完了しました。

SQL> SELECT * FROM TBL WHERE CONTAINS(ORIGTEXT, 'ジオン')>0;

KEY ORIGTEXT
--- ------------------------
  7 私は父ジオン・ズム・ダイクンの元に召されるであろう!!!

他にCREATE INDEX文のオプションとして、「EVERY
「ON COMMIT」を指定することにより自動同期化も可能なようですが、
トークンの作成はコストが高い処理なので、業務時間中の実行には
注意が要りそうですね。
(夜間バッチがベターかな?)

2006年06月23日

●Oracle10g から 8i へのDBリンクで不具合

今回とある要件で、Oracle10gサーバーから8iサーバーにDBリンクでやり取りをする要件があったのですが、若干不具合がありましたのでご報告します。

リンク元:
Ver:Oracle10.2.0.2.0
DBキャラクタセット:JA16SJISTILDE

リンク先:
Ver:8.1.7.4.1
DBキャラクタセット:JA16SJIS

アプリケーションを作成する際、VBやJAVAを使用する分には問題はなかったのですが、
PL/SQLでのプログラミングで問題が発生しました。
簡単な例を示します。

sqlplusで10gサーバーに接続し、以下の無名PL/SQLブロックを実行するとします。


SQL>  DECLARE
    2    CD  HOGE_TBL.HOGE_CD%TYPE;
    3    BEGIN
    4        SELECT  HOGE_CD  INTO  CD  FROM  HOGE_TBL  WHERE  KEY=123;
    5    END;
    6    /

HOGE_TBLはりモードDB(8i)のテーブルです。リンク先のテーブルを指定する場合、「@リンク名」をテーブル名に付加する必要があるのですが、今回は事前にシノニムを作成していることとします。
実行する内容は、HOGE_TBLの一意な値である「KEY」列で検索して、該当する行の「HOGE_CD」列を変数「CD」に代入するだけのものです。
変数「CD」はDECLARE部にて、「CD  HOGE_TBL.HOGE_CD%TYPE;」と参照宣言されている部分に注意してください。
「HOGE_CD」の実際のデータ型はCHAR(4)です。

そして実行結果はというと・・・


行1でエラーが発生しました。:
ORA-06502:  PL/SQL:  数値または値のエラー:
文字列バッファが小さすぎます。が発生しました
ORA-06512:  行4

となってしまいます。

参照宣言した変数に、参照元の実データを代入しただけでバッファが足りないとはこれいかに。

長年のSEの経験から、上記コードを下記のように修正してトライしてみました。


SQL>  DECLARE
    2    CD  CHAR(8)
    3    BEGIN
    4        SELECT  HOGE_CD  INTO  CD  FROM  HOGE_TBL  WHERE  KEY=123;
    5    END;
    6    /

PL/SQLプロシージャが正常に完了しました。


成功しました。もうちょい試しにCHAR(7)に宣言を変えてみたところ、これはアウトでした。

実データ長の倍になってる?!
ならば変数は参照宣言に戻して、SELECT時にTRIMをかけてみましょう。


SQL>  DECLARE
    2    CD  HOGE_TBL.HOGE_CD%TYPE;
    3    BEGIN
    4        SELECT  RTRIM(HOGE_CD)  INTO  CD  FROM  HOGE_TBL  WHERE  KEY=123;
    5    END;
    6    /

PL/SQLプロシージャが正常に完了しました。


通りますね。本来のデータ長分スペースがついてきているのは間違い無いようです。

ただし、最終確認として以下の分も流してみました。


SQL>  DECLARE
    2    CD  VARCHAR2(8)
    3    BEGIN
    4        SELECT  HOGE_CD  INTO  CD  FROM  HOGE_TBL  WHERE  KEY=123;
    5        DBMS_OUTPUT.PUT_LINE(TO_CHAR(LENGTHB(CD)));
    6    END;
    7    /

変数のサイズを実データ長の倍にして、データ型を可変長文字列であるVARCHAR2にしました。
変数に取得されたデータのバイト長を出力してみます。
可変長であっても、スペースが末尾についているのであれば取得サイズは8の筈なんですが・・・

4

PL/SQLプロシージャが正常に完了しました。


意味わからん。やっぱ・・・これって・・・バグ?