この記事は MySQL Advent Calendar 2023 の21日目の記事です。
dumpInstance
dumpInstance は論理バックアップを取得する mysqlsh のコマンドです。 テーブルを分割し、並列でSELECTし、高速にバックアップしてくれます。
このように、1つのテーブルが複数のファイル(chunk)に分割されて出力されます。
$ ls -al ~/dump | grep pkchar | head -rw-r----- 1 s-mitani s-mitani 2155324 Sep 11 13:12 t@pkchar_tbl@0.tsv -rw-r----- 1 s-mitani s-mitani 24 Sep 11 13:12 t@pkchar_tbl@0.tsv.idx -rw-r----- 1 s-mitani s-mitani 2155324 Sep 11 13:12 t@pkchar_tbl@10.tsv -rw-r----- 1 s-mitani s-mitani 24 Sep 11 13:12 t@pkchar_tbl@10.tsv.idx -rw-r----- 1 s-mitani s-mitani 2155324 Sep 11 13:12 t@pkchar_tbl@11.tsv -rw-r----- 1 s-mitani s-mitani 24 Sep 11 13:12 t@pkchar_tbl@11.tsv.idx
バックアップ中は以下ような各chunkに対応するレンジ(WHERE句) が指定されたクエリが流れています。 このレンジはどのように決定されているのでしょうか?
SELECT SQL_NO_CACHE `pk_char`,`col1` FROM `t`.`pkchar_tbl` WHERE (( `pk_char`>='000000-00001') AND (`pk_char`<='000000-10000') ) ORDER BY `pk_char`;
AUTO_INCREMENTのように主キーが連続している数値の場合は、簡単に分割できそうです。 しかし、文字型の場合はどうしているのでしょうか。dumpInstance のロジックを追いかけてみました。
chunk あたりの行数を求める
chunk のサイズは --bytesPerChunk
オプションで与えられます。
$ mysqlsh -- util dumpInstance dump --threads 4 --bytesPerChunk 3M
ここから、一行あたりのサイズ (average_row_length
) を用いて、chunk あたりの行数を求めます。
average_row_length
は統計情報に由来します。
info.rows_per_chunk = m_dumper->m_options.bytes_per_chunk() / average_row_length;
以降、SELECT結果が rows_per_chunk
行に近くなるように、SELECT文のWHERE句(レンジ)を決めていきます。
キーの型によってロジックが分かれる
レンジを求めるロジックはPKの型によって異なっていました。
数値型の場合は chunk_integer_column
、数値型でなければ chunk_non_integer_column
が適用されます。
if (mysqlshdk::db::Type::Integer == type || mysqlshdk::db::Type::UInteger == type || mysqlshdk::db::Type::Decimal == type) { return chunk_integer_column(info, begin, end); } else { return chunk_non_integer_column(info, begin, end); } }
キーが数値型以外の場合 (chunk_non_integer_column)
キーが数値型でない場合のロジックは非常にシンプルです。LIMIT を用いて、線形にレンジの区切りをSELECTしていきます。
説明を簡単にするために、以下のように、連続した数値が含まれる、文字型のPKを仮定します。
mysql> desc pkchar_tbl; +---------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+--------------+------+-----+---------+-------+ | pk_char | varchar(200) | NO | PRI | NULL | | | col1 | varchar(255) | YES | | NULL | | +---------+--------------+------+-----+---------+-------+ 2 rows in set (0.01 sec)
mysql> SELECT pk_char FROM pkchar_tbl; 000000-00001 000000-00002 000000-00003 ...
最小値と最大値を求めます。
SELECT SQL_NO_CACHE `pk_char` FROM `t`.`pkchar_tbl` ORDER BY `pk_char` LIMIT 1 SELECT SQL_NO_CACHE `pk_char` FROM `t`.`pkchar_tbl` ORDER BY `pk_char` DESC LIMIT 1
rows_per_chunk
を LIMIT 句に指定し、線形にチャンクの区切りをSELECTしていく。
rows_per_chunk
= 10000 とすると、最初の区切りは以下のSELECTで求められます。
SELECT SQL_NO_CACHE `pk_char` FROM `t`.`pkchar_tbl` WHERE (`pk_char`>='000000-00001') -- PKの最小値からスタート ORDER BY `pk_char` LIMIT 10000,2 > 000000-10000 > 000000-10001
これで、 000000-00001 〜 000000-10000 が 最初の chunk の範囲だとわかります。 また、 000000-10001 がその次の chunk の開始だとわかります。以下、同様に chunk の区切りを求めていく。
SELECT SQL_NO_CACHE `pk_char` FROM `t`.`pkchar_tbl` WHERE (`pk_char`>='000000-10001') ORDER BY `pk_char` LIMIT 10000,2 > 000000-20000 > 000000-20001
上記の結果からこのようなSELECT文が生成され、バックアップが作成されます。
SELECT SQL_NO_CACHE `pk_char`,`col1` FROM `t`.`pkchar_tbl` WHERE (( `pk_char`>='000000-00001') AND (`pk_char`<='000000-10000') ) ORDER BY `pk_char`; SELECT SQL_NO_CACHE `pk_char`,`col1` FROM `t`.`pkchar_tbl` WHERE (( `pk_char`>='000000-10001') AND (`pk_char`<='000000-20000') ) ORDER BY `pk_char`;
キーが数値型の場合 (chunk_integer_column)
ということで、キーが数値型以外のケースについて見てみました。 長くなってしまったので、キーが数値型の場合については、別のエントリーにします!
つづく。。。
MySQL Advent Calendar 2023
明日は、@ikuosaito1989 さんです!