Understanding MySQL Internalsを読む(9)

連載記事一覧は*こちら*にあります。

http://mirz.jpでまとめて読むこともできます(オススメ)。

今回はストレージエンジンインタフェースについて。かなりボリュームがあります。

handlerクラス

ストレージエンジンのインタフェースはhandler抽象クラスによって定義されています。このhandlerクラスはテーブルのopenやclose、テーブルの順次走査、インデックスを利用したレコードの検索、レコードのソート処理、レコードの削除など、基本的な操作を行うためのメソッドを定義しています。

handlerクラスはsql/handler.hに定義されており、メソッドの実装はsql/handler.ccで行われています。またhandlerクラスはSql_allocクラス(sql/sql_list.h)を継承しています。MySQL内部では、各テーブル記述子ごとにhandlerインスタンスが生成されます。テーブル記述子は各テーブル、各スレッドごとに作成されますので、相等数のhandlerインスタンスMySQL内部で作成されることになります。またMySQL 5.0から導入されたindex_mergeによるテーブル結合が行われる時は、オプティマイザによりさらに追加でhandlerインスタンスが生成されることになります。

handlerクラスは多数のメンバとメソッドを持っていますので、それぞれ分けて表にします。

handlerクラスのメンバ

メンバ定義 メンバの説明
struct st_table *table このhandlerインスタンスに関連づけられているテーブル記述子へのポインタ。
byte *ref 現在のレコード参照の値。レコード参照とは、内部的に使用されているレコードのテーブル単位でのユニークな識別子。MyISAMストレージエンジンではこの変数にデータファイル上でのオフセットを格納している。InnoDBストレージエンジンでは主キーの値を独自フォーマットで格納している。MEMORYストレージエンジンでは、レコードの開始位置へのポインタを格納している。この変数のサイズはref_lengthに設定されている。
byte *dupp_ref 新しいレコードをINSERTする際にユニーク制約違反が発生するレコードの参照を格納しておくための追加"レジスタ"。
ulonglong data_file_length 各エンジンで使用するデータファイルの長さ。データファイルを用いないエンジンの場合には、データファイルの長さの代わりに全てのレコード長、delete等で空いた穴の長さ(以降のINSERTで利用できる)の合計値を格納している。この変数の値はSHOW TABLE STATUSコマンドで見ることができる。
ulonglong max_data_file_length data_file_lengthの取りうる最大値。データファイルのサイズ上限。SHOW TABLE STATUSコマンドで見ることができる。
ulonglong index_file_length 各エンジンで使用するインデックスファイルの長さ。インデックスファイルを用いないエンジンの場合は、このテーブルのインデックスをメモリ上あるいはディスク上に格納するのに必要な合計サイズの概算が格納されている。SHOW TABLE STATUSコマンドで値を確認できる。
ulonglong max_index_file_length インデックスファイルのサイズ上限。現在は、MyISAMストレージエンジンとISAMストレージエンジンのみがこの変数に値を設定している。
ulonglong delete_length 割当て済みだが未使用のバイト数。MyISAMでは"削除された"とマークされているレコードが占めている領域の合計値が格納されている。SHOW TABLE STATUSコマンドで値を確認できる。
ulonglong auto_increment_value 次のINSERT時に使用されるAUTO_INCREMENTの値。この値はテーブル生成時あるいはALTER TABLE実行時に設定が可能。INSERT_IDが設定されている場合にはそちらが優先される。
ha_rows records テーブルに含まれるレコードの数。InnoDBではマルチバージョニング機能の影響で、この値は推定値となっている。SHOW TABLE STATUSコマンドで値を確認できる。
ha_rows deleted テーブル内で"削除された"とマークされているレコードの数。
ulong raid_chunksize MyISAMエンジンでRAIDを利用するために使われている変数。ver5.1で削除された。
ulong mean_rec_length レコードの平均サイズ。SHOW TABLE STATUSコマンドで確認できる。
time_t create_time テーブルが生成された日時。SHOW TABLE STATUSコマンドで確認できる。
time_t check_time 最後にCHECK TABLEコマンドが実行された日時。SHOW TABLE STATUSコマンドで確認できる。
time_t update_time 最後にテーブルが更新された日時。SHOW TABLE STATUSコマンドで確認できる。
key_range save_end_range handler::read_range_firstメソッドで使用される変数。
key_range *end_range 多くのhandlerクラスのメソッドで、キーの値の範囲指定に基づいてレコードを読む際に使用される変数。
KEY_PART_INFO *range_key_part handler::read_range_firstメソッドで使用される変数。
int key_compare_result_on_equal handler::read_range_firstメソッドおよびhandler::compare_kehyメソッドで使用される変数。
bool eq_range handler::read_range_firstメソッドおよびhandler::read_range_nextメソッドで使用される変数。開始と終了の範囲が同じ場合にtrueが設定される。
uint errkey 最後に発生したエラー番号が設定される。一意性制約違反となるようなキーの生成が行われる際に頻繁にエラーが発生する。
uint sortkey レコードが物理的にソートされているキーの番号。そのようなキーが存在しない場合には255が設定される。現在使用されていない変数。
uint key_used_on_scan レコードの走査で最後に使用されたキーの番号。そのようなキーが存在しない場合にはMAX_KEY(=64)が設定される。
uint active_index 現在選択されているキーの数。1つも選択されていない場合にはMAX_KEY(=64)が設定される。
uint ref_length メンバrefの長さ。
uint block_size このテーブルで使用されているキーのブロックサイズ。
uint raid_type MyISAMRAIDを使用する際に使われる変数。ver5.1で削除された。
uint raid_chunks MyISAMRAIDを使用する際に使われる変数。ver5.1で削除された。
FT_INFO *ft_handler FULLTEXTインデックス用の記述子。現在はMyISAMでのみ使用されている。
enum {NONE=0, INDEX, RND} inted このhandlerインスタンスが初期化されてキーの読み込みができるようになっているか(INDEX)、テーブルの走査ができるようになっているか(RND)、あるいはどちらもできないか(NONE)を示す変数。handler::ha_init_indexメソッドが呼ばれるとこの変数はINDEXが設定され、handler::ha_init_rndメソッドが呼ばれるとRNDが設定される。またhandler::ha_end_indexメソッドおよびhandler::ha_end_rndメソッドが呼ばれると後処理が行われてNONEが設定される。
bool auto_increment_column_changed 最後に行われた操作によってauto_increment属性のカラムの値が変更されたかどうかが、handler::update_auto_incrementメソッドにより設定される。
bool implicit_emptied サーバの再起動時にテーブルデータが空になったかどうかについて、MEMORYストレージエンジンにより設定される。この情報はレプリケーションのためのログ出力を適切に行うのに必要。

handlerクラスのメンバはこんなところ。

handlerクラスのメソッド

続いてhandlerクラスのメソッド。

メソッド定義 メソッドの説明
int ha_open(const char *name, int mode, int test_if_locked) 引数nameで指定されたテーブルをopenする。このnameはテーブル定義情報を含んでいる".frm"ファイルへのパス(データディレクトリからの相対パス)。ただし拡張子".frm"は取除かれている。例えばtestデータベースのテーブルt1の場合、frmファイルへの実際のパスは"./test/t1.frm"だが、拡張子が取り除かれるのでnameの値は"./test/t1"となる。他の引数はストレージエンジンにそのまま渡され、各エンジンにより独自に処理される。成功した場合には0、失敗した場合にはエンジンからのエラーコードを返す。
void update_auto_increment() INSERTで使用されるべきautoincrement値を決定し、autoincrementフィールドの記述子に格納する。
virtual void print_error(int error, myf errflag) エラーログにエラーメッセージを出力する。このメソッドは共通のエラーを扱うことができる実装が行われている。未知のエラーコードが与えられるとhandler::get_error_messageメソッドを呼び出して対応するメッセージを検索する。引数errorはエラーコードが与えられる。引数errflagはmy_error関数(mysys/my_error.c)へ渡されるが、通常は0であることが多い。
virtual bool get_error_message(int error, String *buf) handler::print_errorメソッドでエラーメッセージを解決できなかった場合に、ストレージエンジン独自のエラーメッセージを取得するために呼ばれる。引数errorにはエラーコードが渡される。引数bufはStringオブジェクトのバッファで、解決したエラーメッセージがここに格納される。ストレージエンジン内のエラーが一時的なものであった場合にはtrueを返し、そうでない場合にはfalseを返す。
uint get_dup_key(int error) 最後に発生した重複キーエラーに関連したキーの数を返す。引数で渡されたエラーコードが重複キーエラーに関連するものでない場合には、(uint) -1を返す。
void change_table_ptr(TABLE *table_arg) 引数で渡されたテーブル記述子へのポインタをメンバに設定する。
virtual double scan_time() テーブルをフルスキャンする際、ブロック読み込み操作を何度行う必要があるのかについての推定値を返す。
virtual double read_time(uint index, uint ranges, ha_rows rows) 引数rowsで指定された行数を引数rangesで指定された範囲から、引数indexで指定された番号のキーを使用して読み込む際、ブロック読み込み操作を何度行う必要があるのかについての推定値を返す。
virtual const key_map *keys_to_use_for_scanning() 通常、テーブルのスキャンを行う際、B-treeインデックスを辿るよりもプレーンなデータファイルをフルスキャンするほうが速いため、MySQLオプティマイザはインデックスを使用しない。しかしいくつかのストレージエンジンではテーブルのフルスキャンを行うよりもB-treeインデックスを辿る方がメリットが大きいようなデータ構造を採用している。このメソッドはテーブルスキャンを行う際に利用可能なキーを判定するためのビットのマップを返す。
virtual bool has_transactions() ストレージエンジンがトランザクションをサポートしている場合にtrueを返す。デフォルト実装ではfalseを返す。ver5.1からはvirtual修飾子は取除かれている。
virtual uint extra_rec_buf_length() openfrm関数(sql/table.cc)は現在のレコードをテーブル記述子に格納するために一時的なレコードバッファを割り当てる。このバッファのサイズはレコードの論理的な長さに加え、ストレージエンジンごとの追加予約分を加算したものになる。このメソッドはその追加予約分のサイズを返す。
virtual ha_rows estimate_rows_upper_bound() テーブルスキャンを行う際に最大で何行になるかを返す。
virtual const char *index_type(uint key_number) 引数で指定された番号のインデックスのタイプを文字列で返す。
int ha_index_init(uint idx) 引数idxで指定された番号のインデックスに対する操作を行うためにストレージエンジンを初期化する。成功した場合に0、失敗した場合には0以外の値を返す。
int ha_index_end() キーに対する終了後、ストレージエンジン内で終了処理を行わせるためのメソッド。handler::ha_index_initメソッドが事前に呼ばれている必要がある。成功した場合に0、失敗した場合には0以外の値を返す。
int ha_rnd_init(bool scan) 位置に基づいたレコードの読み込みを行うためにストレージエンジンを初期化する。引数scanはテーブルのフルスキャンを行う予定であるかどうかの指定。成功した場合に0、失敗した場合には0以外の値を返す。
int ha_rnd_end() 位置に基づいたレコード読み込みに対する終了処理を行う。事前にhandler::ha_rnd_initメソッドが事前に呼ばれている必要がある。成功した場合に0、失敗した場合には0以外の値を返す。
int ha_index_or_rnd_end() handler::ha_index_end()かhandler::ha_rnd_end()のどちらかを呼ぶ(事前に対応するどちらの初期化メソッドを呼んでいたかに依存する)。成功した場合に0、失敗した場合には0以外の値を返す。
uint get_index(void) const 現在選択されているインデックスの番号を返す。
virtual int open(const char *name, int mode, uint test_if_locked)=0 テーブルを実際にopenするメソッド(ha_open()はただのwrapperメソッド)。引数nameは拡張子が取り除かれたfrmファイルへのパス。残りの引数はどのように初期化すべきか、およびテーブルがロックされていた場合にどのようにすべきかを指定するためのフラグ。このフラグは主にMyISAMストレージエンジンで有意義なものとなっている。成功した場合に0、失敗した場合には0以外の値を返す。このメソッドは純粋仮想関数なので、各ストレージエンジンはサブクラスで必ず実装しなければならない。
virtual int close(void)=0 テーブルに対して終了処理を行った上でcloseする。事前にhandler::openメソッドが呼ばれている必要がある。成功した場合に0、失敗した場合には0以外の値を返す。このメソッドは純粋仮想関数なので、各ストレージエンジンはサブクラスで必ずオーバーライドしなければならない。
virtual int write_row(byte * buf) ポインタ引数で指定されたレコードをテーブルに追加する。このメソッドはINSERT文を処理する際に全てのストレージエンジン共通の呼出スタックの底で呼ばれる。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。このメソッドをサブクラスでオーバーライドしない限り、全てのINSERTはエラーが返ることになる。
virtual int update_row(const byte * old_data, byte * new_data) ポインタ引数old_dataで指定されたレコードをポインタ引数new_dataで指定された内容に更新する。このメソッドはUPDATE文を処理する際に全てのストレージエンジン共通の呼出スタックの底で呼ばれる。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。このメソッドをサブクラスでオーバーライドしない限り、全てのUPDATEはエラーが返ることになる。
virtual int delete_row(const bute * buf) ポインタ引数で指定されたレコードをテーブルから削除する。このメソッドはDELETE文を処理する際に全てのストレージエンジン共通の呼出スタックの底で呼ばれる。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。このメソッドをサブクラスでオーバーライドしない限り、全てのDELETEはエラーが返ることになる。
virtual int index_read(byte * buf, const byte * key, uint key_len, enum ha_rkey_function find_flag) 引数keyおよびkey_lenの値に従って最初のキーのカーソルを位置付け、マッチする場合には引数bufにレコードを読み込む。レコードの一致検索は引数find_flagで指定された方法を利用して行われる。操作は現在アクティブになっているインデックスに対して行われる。enum ha_rkey_functionはinclude/my_base.hに定義されている。成功した場合に0、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int index_read_idx(byte * buf, uint index, const byte * key, uint key_len, enum ha_rkey_function find_flag) handler::index_readメソッドと同じだが、引数indexで指定されたインデックスが最初にアクティブになる点が異なる。
virtual int index_next(byte * buf) アクティブなインデックスから次のレコードを読み取り、引数bufに格納して、アクティブなインデックスのカーソルを1つ進める。成功した場合に0、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int index_prev(byte * buf) アクティブなインデックスから前のレコードを読み取り、引数bufに格納して、アクティブなインデックスのカーソルを1つ戻す。成功した場合に0、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int index_first(byte * buf) アクティブなインデックスから先頭のレコードを読み取り、引数bufに格納して、アクティブなインデックスのカーソルをその次の位置に設定する。成功した場合に0、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int index_last(byte * buf) アクティブなインデックスから末尾のレコードを読み取り、引数bufに格納して、アクティブなインデックスのカーソルをその1つ前の位置に設定する。成功した場合に0、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int index_next_same(byte *buf, const byte *key, uint keylen) 現在アクティブなレコードを開始位置として、前に読んだレコードのキーと同じキーの値を持つレコードを、引数bufにより渡されたバッファに読み込む。いくつかのストレージエンジンは1つ前に読んだレコードのキーを保持しないため、引数keyと引数keylenを使って前の値を教えている。処理に成功した場合、アクティブなキーのカーソルが1つ進み、0が返される。失敗した場合には0以外のエラーコードが返される。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int index_read_last(byte * buf, const byte * key, uint key_len) 引数keyとkey_lenの値に一致する最後のキー値を利用して見つけたレコードを引数bufのバッファに読み込み、カーソル位置をそのレコードの1つ前に移動する。成功した場合は0を返し、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int read_range_first(const key_range *start_key, const key_range *end_key, bool eq_range, bool sorted) 引数start_keyと引数end_keyで指定された範囲に含まれる最初のレコードをtable->record[0]バッファに読み込む。ここで指定された範囲条件はこの後read_range_nextメソッドを呼び出す際にも利用される。引数eq_rangeはstart_keyとend_keyによる範囲が同じ値を持つかどうかを示す。この情報はstart_keytとend_keyを評価する際に使用されるが、呼出元はしばしば既にこの評価を行っている場合があり、そうした場合にこの情報が渡されることでCPUサイクルの消費を節約できる。引数sortedは、検索結果がソートされていることを呼出元が期待しているかどうかを教えるためのもの。key_range型はinclude/my_base.hに定義されている。このメソッドは成功すると0を返し、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装では、start_keyが0である場合に最初の値を読み込むためにindex_first()を呼出し、そうでなければindex_read()を呼び出して最初にマッチしたキーのレコードを読み込む。読んだキーが条件範囲にまだ合致しているかどうかをcompare_keyメソッドで判定する。現在、NDBクラスタエンジンだけがこのメソッドをオーバーライドしている(sql/ha_ndbcluster.cc)。
virtual int read_range_next() 現在の範囲検索における次のレコードをtable->record[0]バッファに読み込む。成功した場合に0を返し、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装では、eq_range変数(read_range_first()により設定されている)の値がtrueに設定されている場合には、index_next_same()が呼ばれ、ひとつ前に返されたレコードのキーの値と同じ値を持つ次のレコードを指すようにカーソルが移動する。eq_range変数がfalseの場合には、index_next()が呼ばれて次のキーの値に移動する。そしてcompare_key()が呼ばれてその値が条件範囲に入っているかどうかの確認が行われる。現在、NDBクラスタエンジンだけがこのメソッドをオーバーライドしている(sql/ha_ndbcluster.cc)。
int compare_key(key_range *range) 現在のレコード(table->record[0])のキーを引数rangeで渡された値と比較する。値が同じ場合あるいは引数rangeの値が0である場合には0を返し、レコードのキーの値の方が小さい場合には-1、レコードのキーの値の方が大きい場合には1を返す。
virtual int ft_init() FULLTEXTインデックスによる操作を行うためにストレージエンジンを再初期化する。JOINなどでFULLTEXT検索を繰り返し実行する場合に呼ばれる。現在はMyISAMでのみ意味のあるメソッド。成功した場合には0、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual FT_INFO *ft_init_ext(uint flags, uint inx, const byte *key, uint keylen) 検索のためにFULLTEXTエンジンを初期化する。現在はMyISAMにとってのみ意味がある。引数flagsは検索のモードを指定するのに利用される。引数inxはインデックスの番号、引数keyと引数keylenは検索に使用するキーの値を指定するのに使用する。MySQL 5.0以上では後ろふたつの引数はString系オブジェクトに変更されている。処理に成功するとFULLTEXT検索記述子へのポインタを返し、失敗した場合にはNULLを返す。デフォルトの実装は単純にNULLを返すようになっている。
virtual int ft_read(byte *buf) 現在アクティブになっているFULLTEXTインデックスから次のレコードを読み取って引数bufのバッファに格納する。現在はMyISAMでのみ意味のあるメソッド。処理に成功した場合に0を返し、失敗した場合には0以外のエラーコードを返す。このメソッドのデフォルト実装は、HA_ERR_WRONG_COMMANDを返すことに注意。
virtual int rnd_next(byte *buf)=0 テーブルへの順次スキャンを行い、次のレコードを読み取って引数bufのバッファに格納し、順次スキャンのカーソルを1つ進める。現在はMyISAMでのみ意味のあるメソッド。処理に成功した場合に0を返し、失敗した場合には0以外のエラーコードを返す。このメソッドは純粋仮想関数なのでサブクラスで実装する必要がある。
virtual int rnd_pos(byte * buf, byte *pos)=0 引数posで指定されたレコードを引数bufのバッファに読み込む。引数posをどのように解釈するかはストレージエンジン依存。MyISAMではデータファイルのオフセットとして使用している。InnoDBでは主キーの値として使用している。MEMORYではレコードのメモリアドレスとして使用している。処理に成功した場合に0を返し、失敗した場合には0以外のエラーコードを返す。このメソッドは純粋仮想関数なのでサブクラスで実装する必要がある。
virtual int read_first_row(byte *buf, uint primary_key) テーブルから任意に選択したレコードを取得し、引数bufで指定されたバッファへ読み込む。引数primary_keyはどのレコードを選択すべきかに影響を与える。現在、デフォル実装ではレコードの選択について2つの方法から1つを選んで行っている。1つめは、テーブルをスキャンして最初に見付かったレコード(削除マークがついていないもの)を返すという方法。2つめは、引数primary_keyで指定された番号のキーの中の最初のレコードを返す方法。1つめの方法は、削除マークが付いたレコードの数が10未満であるような場合、あるいは引数primary_keyの値がMAX_KEY(=64)であるような場合に使用される。もしこれに該当しない場合には2つめの方法が採用される。現在、このメソッドをオーバーライドしているストレージエンジンは無い。処理に成功した場合に0、失敗した場合に0以外のエラーコードを返す。
virtual int restart_rnd_next(byte *buf, byte *pos) 現在、MyISAMに対してのみ意味のあるメソッドで、rnd_post()のエイリアス。デフォルトの実装ではHA_ERR_WRONT_COMMANDを返す。現在このメソッドは、SELECT DISTINCTコマンドを一時テーブルに対して処理しているときに重複したレコードを削除する際に一度だけ呼ばれる。このメソッドは将来的に名称変更や削除が行われる可能性がある。


(まだまだ続く)