Paradoxテーブルの注意事項

ParadoxのDBの注意事項のまとめです。 いろいろな情報の集合となっています。
下記をよく読んで各自の責任において実行してください。

使用コンポーネント


Paradox をより安全に使用するためには、以下の点に留意する必要があります。
1.すべての BDE の LOCAL SHARE を TRUE にする。
2.すべてのセッションの NET DIR が、同じ位置を指すようにする。
3.各セッションの PrivateDir は、それぞれのマシンのローカルディレクトリを
  指すようにする。
4.すべてのマシンの BDE を最新かつ同じバージョンでそろえる。


以下、それぞれの詳細です。

1.すべての BDE の LOCAL SHARE を TRUE にする。

[ LOCAL SHARE の意味 ]
BDE の LOCAL SHARE は、BDE Administrator の Help では、
---------------
BDE アプリケーションと非 BDE アプリケーションの間で,ローカルドライブのデータを共有する場合,
TRUE に設定します。
(2つのアプリケーションを同時に使わない場合は LOCAL SHARE を TRUEに設定する必要はありません)
デフォルトは FALSE です。
---------------
となっていますが、これをもっと平たく言うと、BDE のローカルドライバ( Paradox または dBase ドライバ)に
更新内容をメモリにキャッシュさせるか、ファイル(ディスク)に書き込ませるか、を指示するモード、
ということです。
FALSE は、データへのアクセスは BDE を通してしか行なわないから、BDE がその管理を好きなように最適化して
しまって構わないよ。ということです。この最適化には、問題になっている BDE によるキャッシュ
(書き込みの遅延)も含まれることになるわけです。
TRUE にすると、BDE はキャッシュしません。Post の都度、確実にファイルに書き込まれます。
FALSE にすると、キャッシュされて、更新速度は上がりますが、ファイルへの書き込みは、データセットの
Close まで遅延されます。
Post = ファイルへの反映にはなりません。今回、 FALSEにして、40 バイト程度のレコードを 1000行
Insert してみましたが、更新内容はファイルに反映されず、キャッシュされたままでした。
RDBMS では、一旦コミットしたデータは、その直後にシステムがクラッシュしようと、
保障しなければならないことになっています。
LOCAL SHARE を TRUE にすることで、それに準じた動作を行なわせることができます。
(Peer to Peer 接続の場合は、無条件に TRUE にしてください。)

[ BDE のキャッシュによる弊害 ]
システムが何らかの理由でクラッシュした場合、キャッシュにある更新内容は、なくなってしまいます。
インデックスが壊れるとかデータが壊れるとかインデックスの日付が古くなるといったエラー、
これらはすべて、データの整合がとれなくなった状態です。
で、キャッシュされていると、Close 前、どこまでのデータが書き込まれているかは、BDE のみが預かり知る
ところであって、それが常に論理的な単位(参照整合性も含んだ)でディスクに反映されているとは
必ずしも期待できません。この時点でシステムが落ちたら、先のデータの整合が取れない状態を招く
可能性は高いと思います。また、システムが落ちる寸前の不安定な状態で、BDE が大量の
キャッシュデータをディスクに一気に書き込むといったことも同様です。
実際、LOCAL SHARE を TRUE にすることで、データ破損の頻度が大幅に減ったという報告が
borland の newsgroup でありました。

[ DbiSaveChanges と FlushBuffers ]
LOCAL SHAREを TRUE にすると、DbiSaveChanges や FlushBuffersを呼ぶ必要はありません。
LOCAL SHARE を TRUE にしない場合は、該当データセットのAfterPost と AfterDelete で、
あるいは Application.OnIdle で BDE の DbiSaveChanges や TBDEDataSet.FlushBuffersを呼ぶことで、
該当データセットの更新内容をキャッシュからファイルへ吐き出させることができます。
ただしこれらの手続きは Open されたカーソルを持つデータセットでしか有効ではないということに
注意してください。実験した結果、ExecSQL で、Insert、Update、Delete を発行した場合も、
TTable などと同様に更新内容はキャッシュされるということが判明しました。
でも、 ExecSQLされた Query は、カーソルを持ちません。この場合、強制書き込みをさせるには、
再度、そのテーブルを TTable や TQuery で Open して、FlushBuffers を呼ぶか、
そのテーブルに関連付けられたカーソルを持つ TTable やTQueryがあれば、そのデータセットで
FlushBuffers を呼ぶといったことを行なう必要があります。これは、キャッシュアップデートで
TUpdateSQL を使って ApplyUpdatesしたデータセットでも同様です。
(ApplyUpdates は、究極 ExecSQLで更新を行ないますから。)
ということで、もっとも簡単・確実なのは、LOCAL SHARE を TRUE にすることだと思います。

[ 設定方法 ]
BDE Administrator で、
<環境設定> - <System> - <INIT> - <LOCAL SHARE>
を TRUE にします。
より確実にプログラム上で行なう方法については、後述します。


2.すべてのセッションの NET DIR が、同じ位置を指すようにする。

NET DIR は、Paradox.net ファイルの所在を示します。
Paradox.net は、BDE が Paradox DB にアクセスする複数のユーザを管理するためのもので、特定の
Pradox DB に対してネットワーク上に唯ひとつ存在すべきものです。
そのため、同じ Paradox ファイルを利用するセッションはすべてが同じ NET DIR を参照しなければなりません。
ルートディレクトリの指定は避けて下さい。
ドライブレターは、すべてのクライアントで一致する必要があります。
\\DBServer\Paradox\Data のような UNC 共有名を使ってください。
NET DIR が正しく設定されていないと、ロックが動作しません。
さらにそれにキャッシュが併用されると、 AutoIncrementが重複値を返したり、重複値が登録されてしまう
といったことが起きる可能性があります。
NetFileDir を正しく設定しなかったために発生するエラーには、
「他の.NETファイルがこのディレクトリをコントロールしています。」
「複数の.NETファイルが使われています。」があります。
一旦、これらのエラーになった場合は、BDE を使うアプリをすべて終了させた上で、残っている
Pdoxusrs.net, Pdoxusrs.lck, Paradox.lck をすべて削除してしまいます。

[ 設定方法 ]
BDE Administrator で、
<環境設定> - <Drivers> - <Native> - <PARADOX> - <NET DIR>に、パスを設定します。
より確実にプログラム上で行なう方法については、後述します。


3.各セッションの PrivateDir は、それぞれのマシンのローカルディレクトリを指すようにする。

PrivateDir は、Pdoxusrs.lck と Paradox.lck を置くパスです。
Pdoxusrs.lck は、該当セッションがロックしたレコードの情報を持ちます。
PrivateDir を設定しなかったために発生するエラーには、
「LCKファイルが大きくなりすぎています。」
「他の.NETファイルがこのディレクトリをコントロールしています。」
「ディレクトリは使用中です。」があります。
これらのエラーが発生した場合は、上記2のときと同様に3つのファイルを削除します。
PrivateDir の設定が行なわれているにもかかわらず「LCKファイルが大きくなりすぎています。」
エラーが発生する場合は、実際にロックしたレコードが多すぎる可能性があります。
Paradox のレコードロックは、1つのテーブルに対して上限 255 となっています。

[ 設定方法 ]
プログラム上で行なう方法については、後述します。


4.すべてのマシンの BDE を最新かつ同じバージョンでそろえる。


――――――――――――――――――――――――――――――――
以下、上記1、2、3の設定を MainForm の FromCreate で行なう例:

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
 TForm1 = class(TForm)
 .......
 end;

procedure SetConfigParameter2(Param: string; Value: string);
function IsLocalShare : Boolean;
procedure EnsureLocalShare;

var
 Form1: TForm1;

implementation

Uses BDE, DBTables;

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
 tempPath : array [0..MAX_PATH] of Char;
begin
 // Local Share を 強制的に True にする。
 EnsureLocalShare;
 // Session.NetFileDir の設定例。
 Session.NetFileDir := '\\Dbserver\server\ParadoxData';
 // Session.PrivateDir をローカルの Temporary Path に設定。
 GetTempPath(MAX_PATH, tempPath);
 Session.PrivateDir := tempPath;
end;


// BDE の Configuration 設定汎用エントリ。
procedure SetConfigParameter2(Param: string; Value: string);
var
 hCfg: hDBICfg;
 Config: SYSConfig;
 Path, Option: string;
 ParamCount, I: word;
 pFields, pFld: pFLDDesc;
 pRecBuf, pRec: pBYTE;
 Found, SelfInitialized: boolean;
 rslt: DBIResult;
begin
 {$Ifdef WIN32}
 hCfg := nil; pFld := nil; pRec := nil; Found := False; SelfInitialized := False;
 try
  if Pos(';', Param) = 0 then
   raise EDatabaseError.Create('Invalid parameter passed to function. There must ' +
    'be a semi-colon delimited sting passed');
  Path := Copy(Param, 0, Pos(';', Param) - 1);
  Option := Copy(Param, Pos(';', Param) + 1, Length(Param) - Pos(';', Param));

  rslt := DbiGetSysConfig(Config);
  if rslt <> DBIERR_NONE then
  begin
   if rslt = DBIERR_NOTINITIALIZED then // Engine not initialized error...
   begin
    SelfInitialized := True;
    DbiInit(nil);
    Check(DbiGetSysConfig(Config));
   end
   else

    Check(rslt);
   end;
   (* DbiOpenConfigFile is defined as such:
    function DbiOpenConfigFile ( { Open/Create configuration }
      pszDirPath : PChar; { Directory }
        bCreate : Bool; { TRUE to create/overwrite }
       var hCfg : hDBICfg { Handle to config }
           ): DBIResult stdcall; *)
   Check(DbiOpenConfigFile(Config.szIniFile, FALSE, hCfg));

   (* DbiCfgGetRecord is defined as such:
    function DbiCfgGetRecord ( { Get a record }
        hCfg : hDBICfg; { Config Handle/NULL }
     pszCfgPath : PChar; { Path }
     var iFields : Word; { Returned nbr of fields }
      pfldDesc : pFLDDesc; { Field descriptors }
        pRec : Pointer { Field values }
          ): DBIResult stdcall; *)
   { Call it without the field and record buffer to get the count... }
   Check(DbiCfgGetRecord(hCfg, PChar(Path), ParamCount, nil, nil));

   pFields := AllocMem(ParamCount * sizeof(FLDDesc));
   pFld := pFields;
   pRecBuf := AllocMem(10000);
   pRec := pRecBuf;

   { Get the node values... }
   Check(DbiCfgGetRecord(hCfg, PChar(Path), ParamCount, pFields, pRecBuf));

   for I := 0 to ParamCount - 1 do
   begin
    if pFields^.szName = Option then
    begin
     StrPCopy(PChar(pRecBuf), Value);

     (* DbiCfgModifyRecord is defines as such:
       function DbiCfgModifyRecord ( { Modify a record }
            hCfg : hDBICfg; { Config Handle/NULL }
         pszCfgPath : PChar; { Path }
           iFields : Word; { Nbr of fields }
          pfldDesc : pFLDDesc; { Field descriptors }
            pRec : Pointer { Data values }
              ): DBIResult stdcall; *)
     Check(DbiCfgModifyRecord(hCfg, PChar(Path), ParamCount, pFld, pRec));

     Found := True;
    end;
    Inc(pFields);
    Inc(pRecBuf, 128);
   end;
   if Found = False then
    raise EDatabaseError.Create(Param + ' entry was not found in configuration file');

  finally
   if pFld <> nil then
   FreeMem(pFld);
   if pRec <> nil then
    FreeMem(pRec);
   if hCfg <> nil then

   (* DbiCloseConfigFile is defined as such:
      function DbiCloseConfigFile ( { Close the config file }
      var hCfg : hDBICfg; { Handle }
        bSave : Bool; { To save the changes }
      bDefault : Bool; { To make this file the default }
      bSaveAs16 : Bool { To save as a 16-bit config file }
          ): DBIResult stdcall; *)
   { Close and save the config file... }
   Check(DbiCloseConfigFile(hCfg, TRUE, TRUE, FALSE));
   if SelfInitialized = True then
    DbiExit;
  end;
  {$Else}
   raise EDatabaseError.Create('Non supported function in 16 bit');
  {$EndIf}
end;


// LOCAL SHARE は、TRUE か?
function IsLocalShare : Boolean;
var sys : SYSConfig;
begin
 if (not Session.Active) then Session.Open;
 Result := (DbiGetSysConfig(sys) = DBIERR_NONE) and sys.bLocalShare;
end;


Const
cLOCALSHARE = '\SYSTEM\INIT\;LOCAL SHARE';


// LOCAL SHARE を強制的に TRUE に設定する。
// 他の BDE アプリが動作中、または、IDE 上では、LOCAL SHARE を
// TRUE に設定しても、BDE には、反映されない。
// その場合は、メッセージ表示後、アプリを強制終了する。
procedure EnsureLocalShare;
begin
 if (not IsLocalShare) then begin
  if (not Session.Active) then Session.Open;
  SetConfigParameter2(cLOCALSHARE, 'TRUE');
  Session.Close;
  if (not IsLocalShare) then begin
   ShowMessage('このプログラムを問題なく実行させるために' +
   'システムの設定を変更しました。(LOCAL SHARE = TRUE)' + #13#10 +
   'すべてのデータベースアプリケーションを終了して、' +
   'このアプリケーションを再起動してください。 ');
   Application.ShowMainForm := False;
   Application.Terminate;
  end;
 end
end;




Paradox のファイル破損に関する詳細な記事です。
・LOCAL SHARE を TRUE に設定すること。
(LOCAL SHARE が FALSE なら、アプリケーションの起動を行なえなくすること。)
・さらに DbiSaveChanges を併用すること。
・開いたデータセットは、必ず明示的にクローズすること。
・フォームの終了前にレコードの Post を行なうこと。
・NET DIR を正しく設定すること。
・Private DIR を設定すること。
・一時ファイル (_qb*.*) の削除。
・周期的に Paradox テーブルを Pack すること。
・システムのキャッシュを Disable にすること。
Win95 の場合:
<コントロールパネル> - <システム> - <パフォーマンス>
- <ファイルシステム> - <トラブルシューティング> の
「すべてのドライブに遅延書き込みを行なわない」をチェックする。
・システムのレジストリ設定を確認すること。
・Win95 の VREDIR.VXD(4.00.1113と4.00.1114) をバグなしバージョン
に変更すること。
・キャッシュデータを失う可能性のある NT の OpLock を OFF にする
こと。