MySQL パフォーマンスとtransactd その2の2
前回はselect * from tablename where fieldname = xxxのfieldnameをキーセグメントの先頭に持つインデックスがない場合を書きました。今回は、インデックスがある場合です。
MySQLでfieldnameフィールドのインデックスがある場合
今回の例は条件式が一つですので簡単です。MySQLはまず、レコードバッファ内のfieldnameフィールドの位置にキー値(xxx)をセットしてhandler::ha_index_read_map(HA_READ_KEY_EXACT)を呼び出します。
レコードがなければ(エラーならば)検索終了です。レコードが存在する場合、uniqueキーでなければhandler::ha_index_next_same()をエラーになるまで繰り返し呼び出し、順次レコードを取得します。
handlerでアクセスしたレコード数は、fieldname=xxxの重複レコードの数だけです。インデックスがあれば、実際に存在するレコード数しかアクセスしないので、パフォーマンスは最大です。(正確には、innodbの場合このキーがprimaryかそうでないかで若干パフォーマンスは異なりますが、重複レコードが少なければ大差ありません。)
アクセスするレコード数が少ないので、仮にinnodbのキャッシュに乗っていなくても、それなりに高速です。また、勝手にキャッシュされるレコード数も最少で、キャッシュメモリを温存できます。インデックスがあると色々お得ですね。
transactdでfieldnameフィールドのインデックスがある場合
クライアントフィルターを使う場合
最初にtable::setKeyNum()でインデックス番号を指定し、table::setFV(fieldname, xxxx)でキー値をセットします。その後、uniqueキーならtable::seekEqual()で、そうでなければtable::seekGreater(orEqual=true)でレコードにアクセスします。
レコードが見つかればtable::stat()にゼロが返ります、uniqueキーでない時は、table::seekNext()で次のレコードに移動してtable::stat()とフィールドの値を調べます。値が異なるかtable::stat()がゼロ以外になるまでtable::seekNext()を繰り返し呼び出します。
最初にインデックスを指定しseekEqualやseekGreaterといった名前の関数を呼び出すので、コードからパフォーマンスを容易に想像できます。
サーバーフィルターを使う場合
通常はクライアントフィルターで十分ですが、重複値がたくさんあるようなら、サーバーフィルターを使って一気に取得し、通信のオーバヘッドを削減しましょう。
table::setKeyNum()でインデックス番号を指定し、table::setFV(fieldname, xxxx)でキー値をセットするまでは同じです。その後、table::setFiletr(fieldname=xxx, rejectCount=1, maxRecords=0)でサーバーフィルターをセットしてからtable::find()を呼び出します。
サーバー側では、handler::ha_index_read_map(HA_READ_KEY_OR_NEXT)を使ってスキャンの先頭に移動します。そして、handler::ha_index_next()で次のレコードに移動しながら、条件にマッチするレコードかを調べていきます。
今回は対象フィールドのインデックスを指定しているので、レコードはそのインデックスでソートされていることが保証されています。そのため、マッチしないレコードが見つかればすぐにループを抜けるように、rejectCount=1と指定することがポイントです。ここでもやはり、transactdは、スキャンの開始位置と終了条件を明確にコントロールできます。
まとめ
検索対象のフィールドがインデックスの先頭セグメントにあれば、検索パフォーマンスは最大です。ただし、重複レコードを許可するキーの場合は、重複レコードがどれくらいあるか想定し、パフォーマンスを読みましょう。これはMySQLでもtransactdでも同じです。
transactdでは、重複レコードの量に応じて、クライントフィルターかサーバーフィルターを選択しましょう。
このテーマの最後に、transactdを使った検索のひな形コード(C++)を書いておきます。(他の言語、Ruby ActiveX C# PHP などでも同じ名前のメソッドがあるので、ほとんど同じです。)
次は select * from tablename where fieldname in(a,b,c...)を取り上げたいと思います。
Transactd インデックスを使った検索(クライアントフィルター)
static const char keynum = 0; tb->clearBuffer(); tb->setKeyNum(keynum); tb->setFV("fieldname", "xxxx"); tb->seekGreater(true/*orEqual*/); while (tb->stat() == 0) { if (strcmp(tb->getFVstr("fieldname"),"xxxx")!=0) break; //ここに見つかった場合の処理を記述します。 tb->seekNext(); }
Transactd インデックスを使った検索(サーバーフィルター)
static const char keynum = 0; tb->clearBuffer(); tb->setKeyNum(keynum); tb->setFilter("fieldname = xxxx", 1/*rejectCount*/, 0/*maxRecords*/); tb->setFV("fieldname", "xxxx"); tb->find(tbale::findForword); while (tb->stat() == 0) { //ここに見つかった場合の処理を記述します。 tb->findNext(); }