レコードの処理
以下のトピックでは、レコードの処理について説明します。
オペレーションのシーケンス
Btrieve オペレーションの中には、Create(14)、Reset(28)、Version(26)などのように、いつでも発行できるものがあります。しかし、ほとんどの Btrieve オペレーションでは、Open(0)を使用してファイルを開く必要があります。その場合、ファイル内に位置、つまりカレンシーを確立しないと、レコードを処理できません。
キーに基づいて論理カレンシーを確立したり、Step オペレーションを使用して物理カレンシーを確立したりすることができます。
• 物理カレンシーを確立するには、以下のオペレーションのうちの 1 つを使用します。
• Step First (33)
• Step Last (34)
• 論理カレンシーを確立するには、以下のオペレーションのうちの 1 つを使用します。
• Get By Percentage(44)
• Get Direct/Record(23)
• Get Equal(5)
• Get First(12)
• Get Greater Than(8)
• Get Greater Than or Equal(9)
• Get Last(13)
• Get Less Than (10)
• Get Less Than or Equal(11)
カレンシーを確立した後、Insert(2)、Update(3)、Delete(4)などの対応する I/O オペレーションを発行できます。
メモ: Btrieve ファイルで I/O を実行する場合は常に Btrieve オペレーションを使用してください。Btrieve ファイルで標準 I/O を実行しないでください。
カレンシーに基づいて、以下のようにファイル内を移動できます。
• 物理カレンシーに基づいて位置を確立したら、Step Next(24)、Step Next Extended(38)、Step Previous(35)、Step Previous Extended(39)のいずれかのオペレーションを使用します。Step オペレーションは、アプリケーションが特定の順序でレコードを取得する必要がない場合にすばやくファイル内を探索するのに有効です。Extended オペレーションは、多数のレコードを一度で処理するのに有効です。
• 論理カレンシーに基づいて位置を確立したら、オペレーション Get Next(6)、Get Next Extended(36)、Get Previous(7)、Get Previous Extended(37)のいずれかのオペレーションを使用します。Get オペレーションは、特定の順序でファイル内を探索するのに有効です。Extended オペレーションは、多数のレコードを一度で処理するのに有効です。
1 つのオペレーションで物理カレンシーを確立し、次に論理カレンシーを必要とするオペレーションを続けることはできません。たとえば、Step First オペレーションを発行した後に Get Next オペレーションを発行することはできません。
データオンリー ファイルでは、MicroKernel はインデックス ページの保持や作成を行いません。Step オペレーションと Get Direct/Record(23)オペレーションだけを使用してレコードにアクセスできますが、これらはすべて物理位置でレコードを検索します。
キーオンリー ファイルでは、MicroKernel はデータ ページの保持や作成を行いません。Get オペレーションだけを使用してレコードにアクセスできますが、Get オペレーションでは論理カレンシーでレコードを検索します。
ファイルの処理を終了したら、Close(1)を使用してファイルを閉じます。アプリケーションがいつでも終了できる状態にあれば、Stop(25)を発行します。
メモ: Stop オペレーションを実行できないと、MicroKernel はオペレーティング システムにそのリソースを返せません。この障害により、実際に、アプリケーションが動作しているコンピューターをクラッシュする可能性も含めて予測できないシステム動作が発生します。
レコードへのアクセス
Btrieve は、データへの物理アクセスと論理アクセスの両方を行います。物理アクセスでは、Btrieve はファイル内の物理レコード アドレスに基づいてレコードを取得します。論理アクセスでは、Btrieve はレコードに含まれているキー値に基づいてレコードを取得します。また、Btrieve ではレコード内のデータの「チャンク」にアクセスできます。
物理位置によるレコードへのアクセス
物理位置によるレコード アクセスは、以下の理由で高速になります。
• MicroKernel がインデックス ページを使用する必要がない。
• 物理レコードが存在するページはほとんどの場合キャッシュにあるため、通常、その前後のレコードは MicroKernel のメモリ キャッシュ内に既に存在している。
物理カレンシー
物理カレンシーは、物理位置でレコードにアクセスする際のポジショニングに影響を与えます。レコードを挿入する場合、レコードに含まれているキー値とは無関係に、MicroKernel はファイル内の最初の空きスペースにそのレコードを書き込みます。この場所はレコードの物理位置、つまり、アドレスと呼びます。レコードは、ファイルから削除するまでこの位置に残ります。Btrieve Step オペレーションは、物理位置を使用してレコードにアクセスします。
最後にアクセスされたレコードが、現在の物理レコードです。次の物理レコードは、現在の物理レコードに対してすぐ上位のアドレスを持つレコードです。直前の物理レコードは、すぐ下位のアドレスを持つレコードです。最初の物理レコードの直前の物理レコードはありません。同様に、最後の物理レコードの次の物理レコードはありません。
まとめると、現在の物理位置、次の物理位置、直前の物理位置がファイル内の物理カレンシーを構成します。
Step オペレーション
アプリケーションは、Step オペレーションを使用して、ファイル内の物理位置に基づいてレコードにアクセスできます。たとえば、Step First オペレーション(33)は、ファイル内の最初、つまり、最下位の物理位置に格納されているレコードを取得します。
メモ: キーオンリー ファイルでは Step オペレーションを実行できません。
Step Next(24)オペレーションは、次に上位の物理位置に格納されているレコードを取得します。Step Previous(35)オペレーションは、ファイル内の次に下位の物理位置に格納されているレコードを取得します。Step Last(34)オペレーションは、ファイル内の最後、つまり、最上位の物理位置に格納されているレコードを取得します。
Step Next Extended(38)オペレーションと Step Previous Extended(39)オペレーションは、現在のレコードの後または前の物理位置から 1 つまたは複数のレコードを取得します。
メモ: 各 Step オペレーションは物理カレンシーを再確立しますが、論理カレンシーについては、先に存在していたとしても破壊します。
キー値によるレコードへのアクセス
キー値でレコードにアクセスすると、指定されたキーに対するレコードの値に基づいてレコードを取得できます。
論理カレンシー
論理カレンシーは、キー値でレコードにアクセスする際のポジショニングに影響を与えます。ファイルにレコードを挿入すると、MicroKernel はレコード内の対応するキーに非ヌル値が存在する各 B ツリーを更新します。ファイルの各キーは、レコードの論理順序を決定します。この順序は、キーの定義済みのソート順序または ACS で決定されます。
最後にアクセスされたレコードが、現在の論理レコードです。このレコードは、必ずしも、最後に取得されたレコードではありません。最後のレコードは Get Direct/Chunk(23)オペレーションで取得されている可能性があり、このオペレーションは論理カレンシーを変更しません。次の論理レコードは、定められた論理順序ですぐ次にあるレコードです。直前の論理レコードは、定められた論理順序ですぐ直前にあるレコードです。最初の論理レコードの直前の論理レコードはありません。同様に、最後の論理レコードの次の論理レコードはありません。
まとめると、現在の論理レコード、次の論理レコード、直前の論理レコードがファイル内の論理カレンシーを構成します。
no-currency-change(NCC)オプションを使用するオペレーションを実行する場合や、ヌル キー値を持つレコードを処理する場合を除き、現在の論理レコードは現在の物理レコードでもあります。たとえば、NCC Insert(2)オペレーションを実行し、挿入以前にあったものと同じ論理位置をファイル内に持つことができます。物理カレンシーが更新されます。
NCC オペレーションは、別のオペレーションを実行するために論理カレンシーを保存しなければならない場合に有効です。たとえば、レコードの挿入または更新を行った後、元の論理カレンシーに基づいて Get Next(6)オペレーションを使用しなければならない場合があります。
NCC Insert オペレーション
status = BTRV( B_GET_FIRST, posBlock, dataBuf, &dataLen, keyBuf, keyNum); /* キー パスの最初のレコードを取得 */
for (i = 0; i < numRecords; i++)
{ status = BTRV( B_INSERT, posBlock, dataBuf, &dataLen, keyBuf, -1); /* キー番号 -1 はカレンシー変更なしを示す */
} /* 複数レコードを挿入 */
status = BTRV( B_GET_FIRST, posBlock, dataBuf, &dataLen, keyBuf, keyNum); /* キー パスの最初のレコードの次のレコードを取得 */
メモ: NCC オペレーションを使用する場合、MicroKernel はキー バッファー パラメーターに情報を返しません。論理カレンシーを保持したい場合は、NCC オペレーションの後にキー バッファー内の値を変更しないでください。そうしないと、次の Get オペレーションが予期しない結果になる可能性があります。
Get オペレーション
アプリケーションは、Get オペレーションを使用して、指定されたキーの値に基づいてレコードを取得できます。対応する Get オペレーションは、ファイルから特定のレコードを取得したり、ある順序でレコードを取得できます。
たとえば、Get First(12)オペレーションはキー番号パラメーターで指定されたキーで最初のレコードを取得します。同様に、Get Last(13)オペレーションは、指定されたキーに基づいて論理順序に従って最後のレコードを取得します。Get Equal(5)や Get Less Than(10)などの Get オペレーションの中には、アプリケーションがキー バッファー パラメーターで指定するキー値に基づいてレコードを返すものがあります。
Get オペレーションは論理カレンシーを確立します。アプリケーションは、以下の手順を実行することによってあるキーから別のキーへ変更できます。
1. Get オペレーションのうちの 1 つを発行することによってレコードを取得します。
2. レコードの 4 バイト物理アドレスを取得するために、Get Position(22)を発行します。
3. Get Direct/Record(23)を発行し、4 バイトの物理アドレスと変更するキー番号を MicroKernel に渡します。
Get Position(22)以外の Get オペレーションは、論理カレンシーを確立するほか、物理カレンシーも確立します。したがって、Step Next(24)または Step Previous(35)を続けることができます。ただし、Step オペレーションを使用すると論理カレンシーが破壊されます。
Step オペレーションを使用した後に論理カレンシーを再確立するには、以下の手順に従います。
1. Step オペレーションを使用した直後に、Get Position(22)を発行して取得されたレコードの 4 バイトの物理アドレスを取得します。
2. Get Direct/Record(23)を発行し、4 バイトの位置と論理カレンシーを確立するキー番号を MicroKernel に渡します。
可変長レコードの読み取り
可変長の読み取りは、データ バッファー長パラメーターを使用してレコードを返すためのスペース量を MicroKernel に指示するという点で、固定長レコードの読み取りと同じです。このパラメーターをデータ バッファーのサイズに設定すれば、データ バッファーは最大のデータ量を収容できます。
メモ: データ バッファー長を、データ バッファーに割り当てられたバイト数より大きい値に設定しないでください。設定すると、アプリケーションの実行時にメモリが上書きされるおそれがあります。
読み取りオペレーションが成功した後、返されたレコードのサイズ、つまり固定長部分のサイズに可変長部分の実際のデータ量を足したサイズ(レコードの最大サイズではない)を反映して、データ バッファー長パラメータは変更されます。アプリケーションはこの値を使用して、データ バッファー内のデータ量を決定する必要があります。
たとえば、データ ファイル内に以下のレコードがあるとしましょう。
以下に、Get Equal オペレーションの例を示します。
メモ: アプリケーションの開発とデバッグを行う間、このオペレーションは読み取りオペレーションの直前と直後にデータ バッファー長を表示して、それが各時点で正しく設定されているかどうかを確認するのに役立ちます。
C での Get Equal オペレーション
/* B_GET_EQUAL を使用して key 1 = 263512477 のレコードを取得 */
memset(&dataBuf, 0, sizeof(dataBuf));
dataBufLen = sizeof(dataBuf); /* この値は 1047 */
account = 263512477;
*(BTI_LONG BTI_FAR *)&keyBuf[0] = account;
status = BTRV( B_GET_EQUAL, posBlock, &dataBuf, &dataBufLen, keyBuf, 1);
/* dataBufLen は現在 56 */
Visual Basic での Get Equal オペレーション
dataBufLen= length(dataBuf) ' この値は 1047
account% = 263512477
status = BTRV(B_GETEQUAL, PosBlock$, dataBuf, dataBufLen, account%, 1)
' dataBufLen は現在 56
返されたレコードがデータ バッファー長で指定された値より長いと、MicroKernel はデータ バッファー長で設定されたサイズに従ってできるだけ多くのデータを返し、ステータス コード 22 を返します。
チャンクによるレコードへのアクセス
Btrieve のデータ バッファー長パラメーターは 16 ビットの符号なし整数であるため、レコード長は 65535 に制限されます。Chunk オペレーションは、レコードの部分を読み書きできるようにすることで、この制限をはるかに超えてレコード長を拡張します。チャンクは、オフセットと長さとして定義されます。オフセットは 64 GB までの大きさにすることができますが、長さは 65535 バイトに制限されます。オペレーティング システムおよびデータ バッファー長パラメーターの制限は、Chunk オペレーションにも適用されます。ただし、Chunk オペレーションはレコードのどの部分にもアクセスできるので、この制限はレコード長に影響を与えず、1 つのオペレーションでアクセスできるチャンクの最大サイズにのみ影響を与えます。
たとえば、Chunk オペレーションで、アプリケーションは 3 つのチャンク取得呼び出しを行うことによって 150,000 バイト レコードを読み取ることができます。この例の各チャンクの長さは、50,000 バイトです。最初のチャンクはオフセット 0 から始まり、次のチャンクはオフセット 50,000 から始まり、最後のチャンクはオフセット 100,000 から始まります。
チャンクのオフセットと長さは、キー セグメント、レコードの固定長部分、可変長部などの MicroKernel が認識するレコードの内部構造に対応する必要はありません。また、チャンクは、アプリケーションが定義するフィールドなどのレコードの部分に一致する必要はありません。ただし、定義された部分をチャンクとして更新すると便利な場合があります。
メモ: チャンクは、チャンクを定義しているオペレーションの実行中のみ有効です。
場合によっては、クライアント/サーバー環境で Chunk オペレーションを使用すると、クライアントのリクエスターはより小さなデータ バッファー長を設定してリクエスターのメモリ必要量を少なくすることができます。たとえば、アプリケーションがレコード全体のオペレーションを使用し、最高 50 KB 長のレコードにアクセスした場合、リクエスターはデータ バッファー長を最低でも 50 KB に設定しなければならず、それによって 50 KB の RAM を使用することになります。しかし、たとえば、アプリケーションが Chunk オペレーションを使用し、各チャンクのサイズを 10 KB に制限した場合、リクエスターはデータ バッファー長を 10 KB に設定することができ、それによって 40 KB の RAM が節約されることになります。
レコード内のカレンシー
レコード内のカレンシーが Chunk オペレーションに関連するのは、現在のレコード内のオフセットを追跡するからです。現在位置は、読み取りまたは書き込みが行われたチャンクの最後のバイトを 1 バイト超えたオフセットです。これに関しては、最後のオペレーションでレコード全体を読み取ろうとし、MicroKernel がそのレコードの一部のみを返した場合でも同じです。レコードの一部のみを返す現象は、データ バッファー長が不十分であるときに発生します。
例外は、Truncate サブファンクションを使用する Update Chunk(53)オペレーションの場合です。この場合、MicroKernel は切り捨てられたレコードの終わりから 1 バイト先のオフセットを現在位置と定義します。
MicroKernel はレコード内カレンシーを追跡することにより、以下のことが行えます。
• Chunk オペレーションに対してネクストインレコード サブファンクションのバイアスを与える。
チャンクの元のオフセット、長さ、および個数を指定すると、MicroKernel はそれ以降のオフセットを計算します。
• チャンクにアクセスする際のパフォーマンスを向上する。
レコード内カレンシーは、ポジション ブロックで最後にアクセスされた同じレコードを処理する限り、また、次のチャンク オフセットがレコード内の現在位置より大きい限り、Chunk オペレーションを高速化することができます。つまり、レコード内カレンシーの利点を得るためにレコード内の直後のバイトにアクセスする必要はありません。
メモ: MicroKernel は、現在のレコードのレコード内カレンシーのみを保持します。物理カレンシーまたは論理カレンシーを変更すると、MicroKernel はレコード内カレンシーもリセットします。
Chunk オペレーション
Get Direct/Chunk(23)オペレーションと Update Chunk(53)オペレーションを使用して、チャンクにアクセスします。これらのオペレーションを使用するには、チャンクのオフセットと長さを定義するチャンク記述子構造を定義する必要があります。Get Direct/Chunk オペレーションの場合、チャンク記述子構造は MicroKernel がチャンクを返す先のアドレスも指定する必要があります。
Get Direct/Chunk(23)を使用する前に、Get Position(22)を発行することによって現在のレコードの物理アドレスを取得する必要があります。1 つの Chunk オペレーションで、ネクストインレコード サブファンクション バイアスを使用してレコード内の複数のチャンクを取得または更新することができます。
レコードの挿入と更新
ほとんどの場合、レコードの挿入と更新は簡単なプロセスです。つまり、Insert(2)または Update(3)を使用し、データ バッファーによってレコードを渡します。このトピックでは、挿入と更新に関連するいくつかの特殊なケースについて説明します。
ミッションクリティカルな挿入と更新における信頼性の確保
MicroKernel は非常に信頼性のあるデータ管理エンジンですが、システム障害を防ぐことができません。クライアント/サーバー アプリケーションでシステム障害がよく起こるのは、ネットワーク障害が発生する可能性があるからです。以下の MicroKernel 機能を利用して、信頼性を高めることができます。
• トランザクション一貫性保持。一貫性保持は、アプリケーションが End Transaction オペレーションから正常終了を示すステータス コードを受け取る前に、変更がディスクへコミットされていることを保証します。トランザクションは通常、複数の変更操作が 1 つのグループとして成功または失敗する必要がある場合、それらをグループ化するのに使用されます。また一方、変更がディスクへコミットされるタイミングはアプリケーションが管理するため、トランザクション一貫性保持は単一の操作にとっても役に立ちます。
個々のミッション クリティカルな挿入操作および更新操作を Begin Transaction オペレーションと End Transaction オペレーションの内側に「ラップ」し、MicroKernel の[トランザクション一貫性保持]設定オプションを使用することを検討してください。Begin Transaction オペレーションと End Transaction オペレーションの詳細については、『
Btrieve API Guide』を参照してください。トランザクション一貫性保持の詳細については、
トランザクション一貫性保持を参照してください。
メモ: アクセラレイティド モードでファイルを開くと、そのファイルに対するトランザクション ログは実行されなくなります。つまり、アクセラレイティド モードで開かれたファイル上で行われたオペレーションはトランザクション一貫性がありません。
• システム トランザクション回数。トランザクション一貫性保持を使用する別の方法として、MicroKernel のオペレーション バンドル制限と起動時間制限を使用してシステム トランザクションの回数を制御する方法があります。開いているファイルごとに、MicroKernel は一連のオペレーションを 1 つのシステム トランザクションにバンドルします。システム障害が発生すると、最新のトランザクション以前に行われた変更の内容がすべて失われますが、ファイルは矛盾のない状態に復元されるので、システム障害の原因を解決した後にオペレーションを再試行することができます。
[オペレーション バンドル制限]設定と[起動時間制限]設定をともに 1 に設定すると、MicroKernel は各オペレーションを個別のシステム トランザクションとしてコミットします。そうするとパフォーマンスが下がるので、この方法はパフォーマンスの低下を認めることのできるアプリケーションにのみ有効です。これを決定する 1 つの方法は、アプリケーションの実行中に CPU の利用度を測定する方法です。CPU の 50 ~ 100% を利用するアプリケーションは、このアプローチの良い候補ではありません。
重複不可キーの挿入
レコードを挿入する場合で、同じキー値が既に存在する可能性があり、それを重複させたくない場合は、次のいずれかの方法を実行します。
• Insert(2)を実行する。MicroKernel からステータス コード 5 が返された場合、キー値は存在するので、挿入を行えません。
• Get Equal オペレーションを Get Key バイアス付き(55)で実行する。MicroKernel からステータス コード 4 が返された場合、キー値はまだ存在していないので、挿入を実行できます。
Insert オペレーションが単独で存在し、ファイル内の論理カレンシーに依存しない場合、各 Insert より前に Get Equal を実行すると、オーバーヘッドが増えます。しかし、挿入がグループになっている場合は、Get Equal オペレーションがキー位置を指すインデックス ページをメモリに取り込むことで、これ以降に行われる Insert オペレーションが促進されます。
可変長レコードの挿入および更新
可変長データ ファイルを設計する場合は、アプリケーションがサポートするレコードの可変長部分の最大サイズを決定する必要があります。固定長部分と可変長部分の最大サイズを合計したサイズを収容するレコード構造を設定する必要があります。可変長レコードの読み取り、挿入および更新を行うときは、この構造体をデータ バッファーとして使用します。
可変長レコードを挿入または更新する場合は、データ バッファー長パラメーターによって書き込むデータ量を MicroKernel に指示します。固定長部分のサイズに可変長部分の実際のデータ量を加えたサイズに、このパラメーターを設定します。データ バッファー長を、アプリケーションが可変長フィールドに対して許可する最大サイズに固定長を加えたサイズに設定しないでください。設定すると、MicroKernel は常に最大サイズを書き込みます。
たとえば、以下のレコードを挿入すると仮定します。
以下に、Insert オペレーションの例を示します。ここで、データ バッファー長は固定長部分に Comments フィールド内のデータ量(8 バイト)を加えた長さとして計算されることに注意してください。加えるのは、Comments フィールドの最大サイズ(1000 バイト)ではありません。
Insert オペレーション
#define MAX_COMMENT 1000 /* コメントの最大サイズ */
typedef struct
{ char owner[30];
Int number;
int balance;
} FixedData;
typedef struct
{ FixedData fix;
char variable[MAX_COMMENT];
} DataBuffer;
DataBuffer account;
BTI_ULONG dataBufLen;
BTI_SINT status;
strcpy(account.fix.owner, "John Q. Smith");
account.fix.number = 263512477;
account.fix.balance = 102438;
strcpy (account.variable, "Comments");
dataBufLen = sizeof(FixedData) + strlen(account.variable) +1;
/* +1 はデータの後にヌル文字用 */
status = BTRV(B_INSERT, PosBlock, &account, &dataBufLen, keyBuffer, 0);
固定長部分の読み取りおよび更新
データ バッファー サイズを固定長に設定することによって、レコードの固定長部分のみを読み取ることができます。MicroKernel は、固定長部分のみと、ステータス コード 22 を返します。ただし、その後 Update(3)を使用して固定長部分だけを渡すと、可変長部分は失われます。代わりに Update Chunk(53)を使用すれば、バイト オフセットと長さに基づいてレコードの一部が更新されます。バイト オフセットを 0 に設定し、その長さを固定長部分の長さに設定します。このオペレーションは固定長部分を更新し、可変長部分を残します。
変更不可キーの更新
変更不可と定義されているキー値を更新しようとすると、MicroKernel はステータス コード 10 を返します。どうしてもキー値を更新したい場合は、まずレコードを Delete(4)し、次にレコードを Insert(2)する必要があります。
No-Currency-Change(NCC)オペレーション
キー番号パラメーターに -1(0xFF)を渡すことによって、No-Currency-Change(NCC)オペレーションと呼ぶ、標準の Insert または Update のバリエーションを実行することができます。NCC オペレーションは、アプリケーションが Get Next(6)などの別のオペレーションを実行するために元の論理位置をファイルに保存しておく必要がある場合に有効です。
NCC Insert オペレーションを使用せずに同じ効果を実現するには、以下の手順を実行する必要があります。
1. Get Position(22) - 現在の論理レコードの 4 バイトの物理アドレスを取得します。手順 3 で使用するためにこの値を保存します。
2. Insert(2) – 新しいレコードを挿入します。このオペレーションにより、新しい論理カレンシーおよび物理カレンシーが確立されます。
3. Get Direct/Record(23) - 論理カレンシーと物理カレンシーを手順 1 での状態に戻します。
NCC Insert オペレーションは、論理カレンシーについては標準の Insert と同様の結果を得られますが、物理カレンシーについては異なります。たとえば、これら 2 つの手順のいずれかに続けて Get Next(6)オペレーションを実行した場合は、どちらの手順でも結果は変わりませんが、Step Next(24)を実行した場合は、異なるレコードが返される可能性があります。
NCC Update オペレーションを使用せずに元の位置を保つには、以下の手順を実行する必要があります。
1. Get Next(6) - 次の論理レコードを確立します。
2. Get Position(22) - 次の論理レコードの 4 バイトの物理アドレスを取得します。手順 8 で使用するためにこの値を保存します。
3. Get Previous(7) - 現在の論理レコードを再確立します。
4. Get Previous(7) - 直前の論理レコードを確立します。
5. Get Position(22) - 直前の論理レコードの 4 バイトの物理アドレスを取得します。手順 8 で使用するためにこの値を保存します。
6. Get Next(6) - 現在の論理レコードを再確立します。
7. Update(3) - 影響を受けたレコードを更新します。この標準の Update オペレーションは指定されたキーの値を変更すると、新しい論理カレンシーも確立します。
8. Get Direct/Record(23) - 手順 7 で更新されたレコードの前または後のレコードにカレンシーを設定します。アプリケーションが前方検索を続ける場合は、手順 2 で保存されたアドレスを Get Direct/Record オペレーションに渡します。アプリケーションが後方検索を続ける場合は、手順 5 で保存されたアドレスを渡します。
複数レコード オペレーション
Zen は、複数のレコードまたは複数のレコードの一部をフィルタリングして返すための高パフォーマンスのメカニズムを提供します。このメカニズムは「Extended オペレーション」と呼ばれ、4 つの特定のオペレーション コードでサポートされています。
• Get Next Extended (36)
• Get Previous Extended(37)
• Step Next Extended (38)
• Step Previous Extended(39)
これらのオペレーションのコードの記述方法については、『Btrieve API Guide』を参照してください。このセクションでは、これらのオペレーションを最高のパフォーマンスで使用するために、最適化する方法を説明します。
用語
次の語はほかの状況では別の意味を持つことがあります。このセクションでの用途では、次のような定義になります。
ディスクリプター
拡張式とも呼ばれます。Btrieve Extended オペレーションをどのように実行するかを示す、データ バッファー全体の内容です。
フィルター
拡張式の一部で、選択するレコードに適用される選択条件を表します。
条件
フィルターの一部で、単一の論理演算子を使用します。
コネクタ
ある条件を後続の条件と接続するための論理演算子です。AND、OR、または NONE のいずれかになります。
エクストラクタ
拡張式の一部で、どのデータを返すかを定義します。
キー
インデックス定義全体を表し、複数のセグメントを含むことができます。Get オペレーションは、MicroKernel が単一のキー パスに沿ってデータ ファイル内を移動することを必要とします。
キー セグメント
複合インデックス、またはマルチ セグメント キーとも呼ばれ、複数のセグメントを定義できます。各セグメントには、オフセット、長さ、データ型などを定義します。
背景
Extended オペレーションに対するフィルターの評価メカニズムは、非常に高速に設計されています。余計な処理を行わず直接的な方法で式を評価します。このアプローチにより、インターフェイスが独特な方法をとっていることに気付かれることでしょう。
• Extended オペレーションは初期位置を確立しません。現在の位置から前または後に移動するだけです。したがって、条件(lastname = 'Jones' AND firstname = 'Tom' AND city = 'LasVegas')に該当するすべてのレコードを検索するためには、アプリケーションは Get Next Extended オペレーションを実行する前に Get Equal オペレーションを実行する必要があります。
• フィルターの評価は厳密に左から右へと行われます。たとえば、アプリケーションは、単一の Extended オペレーションを実行して、条件((Age = 32 AND Gender = "M") OR (Age = 30 AND Gender = "F"))を満たすすべてのレコードを取得することはできません。
この種の検索では、最も効率を上げるためにはファイルの中であちこち移動する必要がありますが、Extended オペレーションではこのような移動は行いません。Extended オペレーションでは、論理キーまたはレコード パスに沿って一度に 1 レコード移動します。上に挙げたような複合論理式は、Zen の一部であるリレーショナル エンジンにあるような最適化プログラムを必要とします。その代わり、MicroKernel は式を左から右へと評価し、フィルターの評価を可能な限り高速に行います。
上記の検索を行うには、呼び出しプログラムは 2 つの Get Extended 呼び出しを行います。それぞれの呼び出しの前に Get Equal オペレーションを実行してカーソルを先頭レコードに位置付けてから実行します。1 つの Extended オペレーションで、上の例の 4 つの条件を使用する場合、(Age = 32 AND (Sex = "M" OR (Age = 30 AND Sex = "F")))のように評価されます。言い換えると、MicroKernel は各レコードで最初の条件を評価し、次に論理演算子に注目して、次の条件を評価する必要があるかどうかを判断します。最初の条件が False の場合、AND 演算子は式全体が False であることを意味します。
検証
Extended オペレーションではステータス コード 62(無効なディスクリプター)を返す場合がたくさんありますが、最も一般的なものを以下に挙げます。
• ディスクリプターの長さが不十分です。これは、フィルター条件の数、各条件値の長さ、照合順序が使用されているかどうか、および抽出するフィールドの数によって異なります。
• データ バッファー。長さは、ディスクリプターを完全に含むことができる長さが最低限必要です。
• 各条件は有効なデータ型である必要があります。Btrieve のキー タイプの 1 つである必要があります。
• 比較コードに使用するフラグは有効なもの(FILTER_NON_CASE_SENSITIVE、FILTER_BY_ACS、FILTER_BY_NAMED_ACS)である必要があり、文字列型フィールド(STRING_TYPE、LSTRING_TYPE、ZSTRING_TYPE、WSTRING_TYPE、WZSTRING_TYPE)にしか使用できません。
• 有効な比較コード(1 -6)である必要があります。
• 有効なコネクタ(0 -2)である必要があります。
• 参照する ACS や ISR はあらかじめ定義されている必要があります。
• 最後のフィルター条件には終端文字(コネクタが 0)が必要です。
• ほかのすべてのフィルター条件は終端文字を含めてはいけません(コネクタが 1 または 2)。
• エクストラクタ レコード カウントが 0 ではありません。
最適化
Extended オペレーションの最適化というのは、現在のキー パスにフィルター条件を満たすレコードが残っている可能性がない場合に、MicroKernel がそのキー パスを使用してレコードを探すのをやめることができる、ということです。PSQL 2000i SP3 以降、MicroKernel は複数の条件で最適化を行うことができます。ただし、それらの条件が順次発生し、現在のキーのセグメントに合致している場合に限ります。
各条件の評価時に、MicroKernel は指定されたセグメントが最適化可能かどうかを決定します。最適化を実行するには、以下のすべてが真である必要があります。
• Step Extended オペレーション(38 または 39)ではなく、Get Extended オペレーション(36 または 37)である。
• 前のセグメントの評価時に一致するレコードが見つかっているため、残りのセグメントの最適化を無効にできない。
• OR コネクタによって、現在の条件と後続のすべての条件が最適化できなくなる。これは式が左から右へ評価されるためです。
• 条件は、現在のキー セグメントと同じレコード内のオフセットを参照している。
• 条件は、次のいずれかに該当している必要があります。
• レコード内で、現在のキー セグメントと同じフィールド長を参照している。
• データ型が文字列型の 1 つである場合は、キーの部分文字列である。
• 条件はキーと同じフィールド型を参照している。
• 条件で、フィールドをレコード内の別のフィールドと比較していない(FILTER_BY_FIELD)。
• 条件とキーの大文字小文字の区別の設定が同じである。
• データ型が文字列型の 1 つである場合、条件はキーと同じ ACS または ISR を持っている。
• 条件が最初のキー セグメントで最適化されない場合を除き、論理演算子は EQ(=)である。
• 最初のキー セグメントの場合、論理演算子には、方向が前方の場合は LT または LE(< または <=)、逆方向の場合は GT または GE(> または >=)も使用できます。これらの論理演算子では、1 つのフィルター条件のみが最適化されます。
次の表に示すように、実際の方向はオペレーションによって示される方向だけでなく、現在のキーが降順か昇順かにも左右されます。
以降のすべてのセグメントに対する最適化は、以下のいずれかによって無効にされます。
• これ以上最適化するセグメントがキーにない場合。
• 現在の条件に OR コネクタがある場合。
• 現在の条件は最適化されたが、条件が関連するキー セグメントの部分文字列である場合。
• 現在の条件は最適化されたが、論理演算子が EQ(=)でない場合。
• 現在の条件は最適化されず、前の条件が最適化された場合。つまり、複数の条件が AND で結合されている場合、最初の最適化条件となる最初のキー セグメントに一致するものは、フィルター内の最初の条件である必要はないということです。ただし、最初のキー セグメントに最適化できる条件がいったん見つかると、そのほかの最適化条件は最初の最適化条件の直後に続いて発生する必要があります。
メモ: PSQL 2000i SP3 では、最適化条件はフィルター内で最初に発生する必要があるという不具合がありました。したがって、最適化されない条件を最適化条件の前に置くという機能は SP3 より後でのみ有効です。この制限された機能は SP3 より前でも有効でしたが、最適化することができるのは 1 つの条件だけでした。
例
後述の複数レコード オペレーションの例は、次のサンプル データのテーブルに基づいています。
上のテーブルは、Field 1、Field 2、および Field 3 がこの順で複合キーであるとします。アプリケーションでは、このファイルに対し、前述の 3 つのフィールドから成るセグメント キーを使用して Get First(12)オペレーションを実行し、引き続き Get Next Extended(36)オペレーションを実行します。以下の例には想定されるかっこが含まれていることに注意してください。フィルターが左から右へ評価されるとき、かっこはこの位置にのみ出現します。
キー セグメントに対して最適化を行うためには、フィルター条件に指定したオフセット、長さ、データ型、大文字小文字の区別、および ACS はキー定義と同一である必要があることを忘れないでください。
(Field1 = AAA AND (Field2 = AAA AND (Field3 = AAA)))
MicroKernel エンジンはレコード 1 を取得し、ステータス コード 64(フィルター制限に達しました)で検索を停止します。最適化条件を満たす最後に調べたレコードはレコード 1 で、これが現在のレコードになります。Pervasive.SQL 2000 SP3 より前のエンジンでは、1 つの条件しか最適化することができなかったため、現在のレコードはレコード 3 のままでした。
(Field1 = AAA OR (Field2 = AAA OR (Field3 = AAA)))
MicroKernel エンジンはレコード 1、2、3、4、5、6、7、10、13、および 14 を取得し、ステータス コード 9(ファイルの終わりに達した)を返します。最初の条件に OR コネクタが含まれているため、どの条件も最適化されません。現在のレコードは、レコード 14 になります。
(Field1 = BBB AND (Field2 = BBB OR (Field3 = BBB)))
MicroKernel エンジンはレコード 5、7、8、9、および 11 を取得し、ステータス コード 64 を返します。最初の条件は最適化されましたが、2 番目の条件は OR コネクタが含まれているため、最適化されませんでした。最適化条件を満たす最後に調べたレコードはレコード 12 で、これが現在のレコードになります。
(Field4 = OOO AND (Field2 = BBB AND (Field3 = BBB)))
MicroKernel エンジンはレコード 2 と 8 を取得し、ステータス コード 9 を返します。最初のキー セグメントに対して最適化できる条件がないため、以降のセグメントも最適化されません。現在のレコードは、レコード 14 になります。
(Field1 = BBB AND (first byte of Field2 = B AND (Field3 = BBB)))
MicroKernel エンジンはレコード 8 を取得し、ステータス コード 64 を返します。最初の 2 つの条件は最適化されますが、2 番目の条件が部分文字列であるため、3 番目の条件は最適化されません。最適化条件を満たす最後に調べたレコードは、レコード 9 です。Pervasive.SQL 2000 SP3 より前のエンジンは 1 つの条件しか最適化できなかったため、現在のレコードとしてレコード 12 を返しました。
(Field1 = BBB AND (Field2 = Field3))
これは、比較コードに +64 バイアスを使用して行われます。このバイアスは、2 番目のオペランドが定数ではなくレコード内の別のフィールドであることを示します。MicroKernel エンジンはレコード 4、8、および 12 を取得し、ステータス コード 64 を返します。最初の条件は最適化されますが、2 番目の条件は定数との比較でないため最適化されません。最適化条件を満たす最後に調べたレコードは、レコード 12 です。
(Field1<= BBB AND (Field2 <= BBB AND (Field3 <= BBB)))
MicroKernel エンジンはレコード 1、2、4、5、7、および 8 を取得し、ステータス コード 64 を返します。最初の条件は最適化されますが、論理演算子 EQ を含まないため、以降の条件は最適化されません。最適化条件を満たす最後に調べたレコードは、レコード 12 です。
(Field1= BBB AND (Field2 < BBB AND (Field3 < BBB)))
MicroKernel エンジンはレコード 4 を取得し、ステータス コード 64 を返します。最初の条件は最適化されますが、2 番目の条件は論理演算子 EQ を含まないため最適化されません。最適化条件を満たす最後に調べたレコードは、レコード 12 です。
(Field1= BBB AND (Field2 = BBB AND (Field3 < BBB)))
MicroKernel エンジンはレコード 7 を取得し、ステータス コード 64 を返します。最初の 2 つの条件は EQ を使用しているため最適化されますが、3 番目の条件は最適化されません。最適化条件を満たす最後に調べたレコードは、レコード 9 です。Pervasive.SQL 2000 SP3 より前のエンジンは 1 つの条件しか最適化できなかったため、レコード 12 が現在のレコードになりました。
(Field2>= AAA AND (Field2 <= BBB AND (Field1 >= AAA) AND (Field1 <= BBB))))
MicroKernel エンジンはレコード 1、2、4、5、6、7、8、および 9 を取得し、ステータス コード 64 を返します。最初の 3 つの条件は最初のキー セグメントに対して最適化されませんが、これらはすべて AND で結合されているため、4 番目の条件は検索を最適化するのに使用されます。2 番目の条件は、4 番目の条件の直後に発生すれば最適化することができます。しかし、キー セグメントに関連する位置がずれているため、最適化されません。1 つのキー セグメントのみが最適化されるため、最適化条件を満たす最後に調べたレコードは、レコード 12 になります。Pervasive.SQL 2000 SP3 には不具合があり、最適化可能な条件が最初に発生しない場合最適化が妨げられます。したがって、SP3 エンジンは同様のレコードを取得しますが、ステータス コード 9 を返します。
パフォーマンスのヒント
このセクションでは、オペレーションの速度を上げる方法に関して説明します。
コネクタ
Extended オペレーションは論理式を左から右へ評価するため、この機能は決して完全な式エバリュエーターではありません。つまり、必要なデータを最も効率的な方法で抽出できるものではありません。Extended オペレーションは、最初にカーソルをファイル内の適切な位置に設定して Get または Step オペレーションと共に使用するように設計されています。したがって、最初に提案できることは次のようになります。
• 1 つのフィルター内に AND と OR コネクタを混在させないようにします。混在させる場合は、キーのセグメントと一致するように AND 条件を先行して置き、少なくともエンジンがキー パスの短い部分で検索を最適化できるようにします。
言い換えると、インデックスに含まれないフィールドに対する OR で結合する条件は、インデックス付きのフィールドに対する最適化可能な条件の後に追加するのが適切です。たとえば、全国の電話帳から、テキサス州に住む "William"、"Bill"、"Billy" または "Billybob" という名前の人をすべて検索するとします。State フィールドのキーを使用し、Get Equal を使用してテキサス州の最初の人に現在のレコードを設定します。次に (State = "Texas" AND (FirstName = "William" OR (FirstName = "Bill" OR (FirstName = "Billy" OR (FirstName = "Billybob"))))) のようなフィルターを使って Get Next Extended を呼び出します。エクストラクタのリジェクト カウントが 10,000 で検索する最大レコード件数が 100 の場合、Get Next Extended はおよそ 10,000 レコードを見た後制御を戻します。しかし、テキサス州の 14,000,000 人を処理するには、ユタ州に到達してステータス コード 64(フィルター制限に達しました)が返されるまで、同じ Get Next Extended オペレーションを何度も何度も繰り返す必要があります。この処理は各レコードを 1 度に 1 つずつアプリケーションに転送するよりはずっと速く行われます。
しかし、State と FirstName に複数のインデックスが存在した場合はどうでしょうか。上の Get Next Extended は動作はしますが、4 つの州と FirstName の組み合わせのそれぞれで Get Equal および Get Next Extended を行って両方のフィールドで最適化を行えば、ずっと速く処理できます。
したがって、インデックスが使用できない場合は、OR コネクタを使ったフィルターを使用することが唯一有効な方法であることがわかります。キーに一致するフィールドに対する AND コネクタが優先されます。
リジェクト カウント
もう 1 つ理解する必要のある問題は、リジェクト カウントの設定方法です。使用するアプリケーションが MicroKernel エンジンのサービスを受ける唯一のアプリケーションである場合は、ネットワーク トラフィックまたはプロセス間通信を最低限に保つことができるため、最大のリジェクト カウントを使用することが最も効果的です。ただし、並行処理が高い頻度で行われる環境でたくさんのアプリケーションが実行される場合は、リジェクト カウントの設定が大きすぎると重大な結果を招きます。
MicroKernel は、1 つのファイルに複数のアトミックな Btrieve オペレーションを実行中であっても、同時にたくさんの Btrieve 要求を処理することができます。したがって、読み取りスレッドがいくつでも許容されるだけでなく、1 つの書き込みスレッドが同じファイルにアクセスできます。ほとんどの Btrieve オペレーションは読み取り操作で、それらは実行に多くの時間を要しません。そのため、書き込み操作を行う場合は、すべての読み取り処理が終わるのを待ってから、レコードの挿入、更新、または削除を行う瞬間にファイルをロックします。この調整は、完了するのに長い時間がかかる読み取り操作を行わない限り、非常によく機能します。リジェクト カウントの値を大きく設定した Extended オペレーションで、レコードが見つからない場合にそうなります。これは読み取り処理を何度も何度も繰り返します。ほかの読み取り操作は問題なく完了しますが、書き込み操作は停滞し始めます。書き込みオペレーションがファイルに対して 100 回書き込みアクセスを試行すると、フラストレーション カウントと呼ばれる回数に達します。この時点で、書き込み操作はすべての新しい読み取りスレッドにブロックをかけます。その結果、このファイルに対するすべての Btrieve オペレーションは Extended オペレーションが完了するまで停止します。
• このため、並行処理が高い頻度で行われる環境で使用されるアプリケーションの場合は、リジェクト カウントは 100 から 1000 の間で使用してください。また、Extended オペレーションを最適化するようにして、MicroKernel がレコードの読み取りと拒否を頻繁に行わなくてもよいようにしてください。
100 から 1000 のリジェクト カウントを使用したとしても、レコードをアプリケーションに戻してから拒否するよりも、MicroKernel に読み取りと拒否を実行させる方がよいです。
キーの追加と削除
Btrieve は、ファイルのキーを追加および削除するための操作を 2 つ用意しています。Create Index(31)と Drop Index(32)です。Create Index オペレーションは、ファイルが作成された後にファイルにキーを追加する場合に有効です。Drop Index オペレーションは、インデックス ページが損傷しているキーを削除する場合に有効です。キーを削除した後に、そのキーを再度追加することができますが、そうすると、MicroKernel によってインデックスが再構築されます。
キーを削除する場合、特に指定しなければ、そのキーよりもキー番号の大きなキーはすべて、MicroKernel によって番号が付け替えられます。MicroKernel は、より大きな番号を持つすべてのキーから 1 を引くことによって、キー番号の付け替えを行います。たとえば、キー番号 1、4、および 7 を含むファイルがあるとします。キー 4 を削除すると、MicroKernel は残ったキーの番号を 1 と 6 に付け替えます。
MicroKernel によってキー番号を自動的に付け替えられたくない場合は、バイアス値 0x80 をキー番号パラメーターに設定する値に加算します。これにより、キー番号に空きを残しておくことができ、その結果、ファイル内のほかのキー番号に影響を及ぼすことなく、損傷したキーを削除し、そのキーを再構築することができます。インデックスを再構築するには、Create Index(31)を使います。このオペレーションではキー番号を指定できます。
メモ: 番号の付け替えを指示しないでキーを削除した場合、その後でユーザーが具体的なキー番号を割り当てずに影響を受けたファイルを複製すると、複製したファイルには元のファイルとは異なるキー番号が割り当てられます。