読者です 読者をやめる 読者になる 読者になる

BizStationブログ

ビズステーション株式会社の公式ブログです。

MySQL パフォーマンスとtransactd その2の2

MySQL Transactd

前回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()を繰り返し呼び出します。

最初にインデックスを指定しseekEqualseekGreaterといった名前の関数を呼び出すので、コードからパフォーマンスを容易に想像できます。

サーバーフィルターを使う場合

通常はクライアントフィルターで十分ですが、重複値がたくさんあるようなら、サーバーフィルターを使って一気に取得し、通信のオーバヘッドを削減しましょう。

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();
}