インフィニットループ 技術ブログ

2025年08月18日 (月)

著者 : m-yamagishi

より高速な mysqldump とリストアを求めて

こんにちは。やまゆです。

今までは mysqldump コマンドを実行してデータベーススキーマとデータをファイル化したり、パイプして別のインスタンスにリストアしていたと思います(以下は laravel という DB スキーマの dump/restore を例にします)。

$ mysqldump -uroot -p -h127.0.0.1 -P 3306 --quick --single-transaction laravel | mysql -uroot -p -h127.0.0.1 -P 3306 laravel2
# gzip で圧縮する(ファイルにするので別のインスタンスにファイルで簡単に転送出来る)
$ mysqldump -uroot -p -h127.0.0.1 -P 3306 laravel | gzip > laravel.sql.gz
# 例えば別のインスタンスにファイルを持ってきて
$ zcat laravel.sql.gz | mysql -uroot -p -h127.0.0.1 -P 3306 laravel

しかし、 mysqldump は非常に古いコマンドで、下記のような課題があります。

  • シングルスレッド: 大規模データで時間がかかる
  • 圧縮はパイプ任せ (本体に圧縮制御なし)
  • 進捗がわかりにくい
  • 大量テーブルで巨大な 1 ファイル (部分的リトライがしづらい)
  • バージョン間互換性の自動調整が弱い
  • 権限/イベント/ルーチン等の制御が煩雑

それらを解決するため MySQL 5.7.8 から mysqlpump というコマンドが提供されるようになりましたが、これは MySQL 8.4.0 で削除されました。

mysqlsh という汎用コマンドに吸収されたためです。また、バージョンによって使える挙動が違うので注意してください。さらに、このコマンドは Oracle MySQL にしか搭載されておらず、 MariaDB では利用出来ないので注意が必要です。

ダンプ・リストア先は MySQL 5.7~9.x までと幅広く対応しています(リストアする時にメジャーバージョンが異なる場合はオプション設定しないとエラーになります)。

インストール

デフォルトの圧縮方式に zstd を利用するので、ダンプ・リストアするインスタンスで、事前に各リポジトリ等から apt install zstd などでインストールをしておいてください。

MySQL :: MySQL Shell 8.0 :: 2.2 Linux への MySQL Shell のインストール

最近だと 8.4 LTS や 9.x Innovation が選択される場合もありますので、バージョンには注意してください(今は 8.4 LTS が推奨)。

$ which mysqlsh
/usr/bin/mysqlsh
$ mysqlsh --version
mysqlsh   Ver 8.0.36 for Linux on x86_64 - for MySQL 8.0.36 (Source distribution)

Ubuntu 24.04 では sudo apt install -y mysql-shell で MySQL 8.0 の mysqlsh がインストール可能です。

Debian(例えば Docker イメージなど) では MariaDB しかなく mysql-shell が提供されていないので、代わりに下記に記してある mysql:8.4-oracle(Docker image) を使うのが良いでしょう。

mysqlsh 8.0.x で実行

MySQL :: MySQL Shell 8.0 :: 1 MySQL Shell の機能

スキーマとデータを dump(ファイル化)

$ DB_NAME=laravel
$ TMP_DIR_PATH=$(mktemp -d)
$ DB_DSN=mysql://root:root@127.0.0.1:3306
$ mysqlsh ${DB_DSN} -- util dump-schemas ${DB_NAME} --output-url=${TMP_DIR_PATH} --threads=$(nproc) --defaultCharacterSet="utf8mb4" --tzUtc=false
Acquiring global read lock
Global read lock acquired
Initializing - done 
1 schemas will be dumped and within them 950 tables, 30 views.
Gathering information - done 
All transactions have been started
Locking instance for backup
Global read lock has been released
Writing global DDL files
Running data dump using 8 threads.
NOTE: Progress information uses estimated values and may not be accurate.
Writing schema metadata - done       
Writing DDL - done           
Writing table metadata - done           
Starting data dump
101% (2.99M rows / ~2.95M rows), 346.95K rows/s, 22.83 MB/s uncompressed, 1.16 MB/s compressed                    
Dump duration: 00:00:13s                                                                      
Total duration: 00:00:14s                                                                     
Schemas dumped: 1                                                                             
Tables dumped: 950                                                                            
Uncompressed data size: 198.66 MB                                                             
Compressed data size: 10.05 MB                                                                
Compression ratio: 19.8                                                                       
Rows written: 2987248                                                                         
Bytes written: 10.05 MB                                                                       
Average uncompressed throughput: 14.66 MB/s                                                   
Average compressed throughput: 741.84 KB/s
$ cd ${TMP_DIR_PATH}
$ tar -Izstd -cf laravel.tar.zst .   

これで進捗を表示しながら並列でテーブルごとに dump が取れるので、一つのファイルにして転送出来るようになります。 Shell なので REPL モードももちろんありますが、このようにワンライナーで内蔵されているコマンドを実行することも出来ます。

リストア

リストア先は local_infile システム変数が有効になっている必要があります。

SET GLOBAL local_infile=1;

事前に先ほど dump したファイルを展開しておきます。

$ ZSTD_FILE=/home/myuser/laravel.tar.zst
$ TMP_DIR_PATH=$(mktemp -d)
$ cd ${TMP_DIR_PATH}
$ tar -Izstd -zf ${ZSTD_FILE}
$ DB_NAME=laravel
$ DB_DSN=mysql://root:root@127.0.0.1:3306
$ mysqlsh ${DB_DSN} -- util load-dump ${TMP_DIR_PATH} --threads=$(nproc) --analyzeTables="on" --characterSet="utf8mb4" --schema=${DB_NAME} --ignoreVersion=true
Loading DDL and Data from '/tmp/xxxxxx' using 8 threads.
Opening dump...
Target is MySQL 5.7.42-log. Dump was produced from MySQL 8.0.43
WARNING: Destination MySQL version is older than the one where the dump was created. Loading dumps from different major MySQL versions is not fully supported and may not work. The 'ignoreVersion' option is enabled, so loading anyway.
Scanning metadata - done       
Checking for pre-existing objects...
Executing common preamble SQL
Executing DDL - done           
Executing view DDL - done         
Starting data load
1 thds loading \ 100% (198.65 MB / 198.65 MB), 1.49 MB/s, 961 / 961 tables and partitions done 
Recreating indexes - done       
Executing common postamble SQL                                                                
Analyzing tables - done           
No data loaded.                                                                               
0 warnings were reported during the load.     

analyzeTables を有効にすると、リストア後に自動的に ANALYZE TABLE foo; を実行してくれます。クエリの実行計画が最適化されるのでオススメです。

また、 ignoreVersion を有効にすると、例えば 5.7 から 8.0、 8.0 から 8.4 など dump/restore 間で大きなバージョンの違いがあっても無視してリストアするようになります。上記の出力例では Target is MySQL 5.7.42-log. Dump was produced from MySQL 8.0.43 と出ていますね(8.0.43 の dump を 5.7.42 に展開しようとした)。

mysqlsh 8.4.x(Docker) で実行

MySQL :: MySQL Shell 8.4 :: 1 MySQL Shell Features

MySQL 8.4 バージョンはさらに機能が追加されて、同じ場所で双方の MySQL インスタンスに接続出来る場合は、一発で SRC から DEST に転送することが可能になり、一時ファイルを置くスペースが不要になります。また、 checksum が追加されたので適切にデータを転送出来たかもわかります。

$ docker run --rm -it --net="host" mirror.gcr.io/mysql:8.4 sh
# DB_NAME=laravel
# DB_DSN_FROM=mysql://root:root@127.0.0.1:3308
# DB_DSN_TO=mysql://root:root@127.0.0.1:3306
# THREADS=$(nproc)
# cat <<EOL | mysqlsh ${DB_DSN_FROM} --js
util.copySchemas(["${DB_NAME}"], "${DB_DSN_TO}", {
    threads: ${THREADS},
    defaultCharacterSet: "utf8mb4",
    checksum: true,
    ignoreVersion: true,
    handleGrantErrors: "ignore",
    tzUtc: false,
    analyzeTables: "on",
    ignoreExistingObjects: true,
});
EOL

ホスト側で見える MySQL インスタンスと同じにするため、今回は docker のオプションに --net="host" を追加しています。

本当は前の例と同様ワンライナーがいけるはずなんですが、バグなのか私の環境では入力フォーマットエラーになってしまったので、 JavaScript での命令をパイプで入力しています(実は MySQL Shell では SQL/JavaScript/python が利用可能です)。

各オプションの詳細については公式ページをご覧ください。

  • mysqldump から mysqlsh への移行で並列化・圧縮・進捗表示・整合性確認が一度に解決
  • 8.4 以降はストリーミングコピー + チェックサムで中間ファイル不要
  • バージョン差異がある復元では --ignoreVersion とログ確認が必須
  • 運用面では権限・パフォーマンス調整・検証手順をテンプレ化しておくと再現性が向上

この方式でも、転送には zstd 圧縮が用いられるので、より高速かつ転送量を抑えて dump/restore が可能になります。

また、 dump はオプション指定で宛先を Oracle Cloud Infrastructure のオブジェクト・ストレージか、 S3 API に互換性のある任意のサービスに出力することも出来ます。クラウドストレージへのバックアップが簡単になりますね。

他の選択肢

mysql-shell を使えない場合の選択肢としては、 Percona XtraBackup を用いるという手段もあります。ただ、こちらは dump/restore する MySQL が動作しているそれぞれのインスタンスにインストールする必要があるため、少し条件が厳しくなるかなと思います。

(知ってますか? MySQL 8.0 はもうすぐメンテナンスアップデートも終わって、 8.4 LTS にアップグレードする必要が出てきますよ…?)

運用・保守案件ではまだ全然 dump/restore が現役なインフィニットループではいつでも技術者を募集しています!興味がある方は採用情報ページをご覧ください!

ブログ記事検索

このブログについて

このブログは、札幌市・仙台市の「株式会社インフィニットループ」が運営する技術ブログです。 お仕事で使えるITネタを社員たちが発信します!