この記事は 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;
dumper.cc#L1508-L1509
以降、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);
}
}
dumper.cc#L1449-L1456
キーが数値型以外の場合 (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 さんです!
qiita.com