「バックアップファイルがどんどん増えてディスクを圧迫している」
このような状況は、エラーハンドリングと世代管理を組み込んでいないスクリプトでよく起きます。
mysqldumpは単体では失敗を通知してくれません。接続エラーや権限不足のとき、終了コード0を返す設定になっていると、cronで夜間に回していると翌朝まで気づけないことがあります。この記事では、シェルスクリプトで
mysqldumpの自動バックアップを設計するパターンを解説します。接続情報の安全な管理・trapによるエラー処理・世代管理ローテーション・バックアップ検証まで、実際の運用で動くスクリプトを例示しながら説明します。RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みです。この記事のポイント
・mysqldump失敗を検出するにはset -euo pipefailとtrap ERRの組み合わせが基本
・接続情報は.my.cnfに外部化するとスクリプト内にパスワードを書かずに済む
・findコマンドで指定日数より古いダンプファイルを自動削除して世代管理できる
・gzip -tで圧縮ファイルの整合性チェックをバックアップ直後に自動実行する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
cronにmysqldumpを直書きするだけでは足りない理由
次のようなcrontab設定でバックアップを回しているケースをよく見かけます。# NG例:エラー検知も世代管理もない単純なcron設定 0 2 * * * mysqldump -u root -pPASSWORD mydb | gzip > /backup/mydb_$(date +%Y%m%d).sql.gz
・エラーを検知できない: mysqldumpが失敗してもcronは正常終了として記録し、空ファイルや不完全なダンプが残る
・パスワードの平文埋め込み: crontabにパスワードを直書きすると
ps auxで他のユーザーに見える環境もある・ディスク枯渇: 世代管理がなく古いバックアップが削除されないためディスクが徐々に埋まっていく
これらを一気に解決するのが、シェルスクリプトを挟んだバックアップ設計です。
基本スクリプトの設計 — 接続情報管理・日付ファイル名・trap EXIT
1. 接続情報を .my.cnf で管理する
MySQL 5.6以降は~/.my.cnf(または任意パスの設定ファイル)に接続情報を書いておくと、コマンドライン引数にパスワードを渡さずに済みます。# /etc/mysql/backup.cnf(rootが読める600権限で保存する) [client] user=backup_user password=s3cret_pass host=127.0.0.1
# 権限設定(他ユーザーに読まれないよう必ず実施) $ chmod 600 /etc/mysql/backup.cnf $ ls -la /etc/mysql/backup.cnf -rw------- 1 root root 61 Sep 21 09:00 /etc/mysql/backup.cnf
--defaults-extra-fileオプションで読み込みます。--defaults-fileと違い、他のデフォルト設定ファイルも継続して読み込まれます。2. ダンプファイル名に日付を付ける設計
#!/bin/bash set -euo pipefail MYSQL_CNF="/etc/mysql/backup.cnf" BACKUP_DIR="/backup/mysql" DB_NAME="mydb" DATE=$(date +%Y%m%d_%H%M%S) DUMP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz" TMPFILE="" # バックアップディレクトリがなければ作成 mkdir -p "$BACKUP_DIR"
date +%Y%m%d_%H%M%Sで秒まで含むファイル名にすると、同日に複数回実行しても上書きされません。set -euo pipefailは最初に必ず書き、意図しないエラーのスルー防止を全体に適用します。3. trap EXIT で確実なクリーンアップ設計
#!/bin/bash set -euo pipefail MYSQL_CNF="/etc/mysql/backup.cnf" BACKUP_DIR="/backup/mysql" DB_NAME="mydb" DATE=$(date +%Y%m%d_%H%M%S) DUMP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz" TMPFILE="" # 後片付け関数(EXITで必ず呼ばれる) cleanup() { local exit_code=$? if [[ -n "$TMPFILE" && -f "$TMPFILE" ]]; then rm -f "$TMPFILE" fi if [[ $exit_code -ne 0 ]]; then echo "[ERROR] バックアップが失敗しました(exit code: ${exit_code})" >&2 # 不完全なダンプファイルを削除 [[ -f "$DUMP_FILE" ]] && rm -f "$DUMP_FILE" fi } trap cleanup EXIT mkdir -p "$BACKUP_DIR" # 一時ファイルに書き出してから最終的にリネーム(原子的置換) TMPFILE=$(mktemp "${BACKUP_DIR}/.${DB_NAME}_XXXX.sql.gz.tmp") mysqldump --defaults-extra-file="$MYSQL_CNF" \ --single-transaction \ --routines \ --events \ "$DB_NAME" | gzip -c > "$TMPFILE" # 問題がなければ最終ファイル名にリネーム mv "$TMPFILE" "$DUMP_FILE" TMPFILE="" # cleanup関数がTMPFILEを削除しないようクリア echo "[OK] バックアップ完了: ${DUMP_FILE}"
trap cleanup EXITを先に書くことで、スクリプトがどんな理由で終了しても(エラー・シグナル・正常終了)cleanup関数が呼ばれます。TMPFILE=""でクリアする行は、正常完了時に不要なファイル削除をしないための分岐です。--single-transactionはInnoDB向けのオプションで、テーブルロックをかけずに整合性のあるスナップショットを取得します。本番環境への影響を最小化するために必ず付けます。世代管理とローテーションの設計
実運用でバックアップを取り続けるとディスクが枯渇します。findコマンドで古いファイルを削除する設計を組み込みましょう。シェルスクリプトの世代管理やログローテーション設計についてはシェルスクリプト実践講座(Linux Master Pro)でも体系的に解説しています。
4. find コマンドで古いバックアップを自動削除する
# バックアップ保持日数の設定 RETENTION_DAYS=14 # 削除前に残存ファイル数を確認 BEFORE_COUNT=$(find "$BACKUP_DIR" -name "${DB_NAME}_*.sql.gz" | wc -l) # 14日より古いバックアップを削除 find "$BACKUP_DIR" \ -name "${DB_NAME}_*.sql.gz" \ -mtime +"$RETENTION_DAYS" \ -delete AFTER_COUNT=$(find "$BACKUP_DIR" -name "${DB_NAME}_*.sql.gz" | wc -l) DELETED=$(( BEFORE_COUNT - AFTER_COUNT )) echo "[INFO] ローテーション完了: ${DELETED}本削除(残存: ${AFTER_COUNT}本)"
-mtime +14は「最終更新から15日以上」のファイルが対象になります(+NはN日超)。意図した保持期間と1日ずれないよう注意してください。5. 週次フル・日次差分の組み合わせ設計
MySQLには差分バックアップ(バイナリログベース)と論理バックアップ(mysqldump)があります。小中規模のDBであれば毎日フルダンプするのが最もシンプルですが、DB容量が大きい場合は次の設計が有効です。・週次(日曜):
mysqldumpでフルダンプ(全テーブル)・日次(月~土): バイナリログを退避して差分として保管
# 曜日で処理を切り分ける(0=日曜, 1=月曜 ... 6=土曜) DOW=$(date +%w) if [[ "$DOW" -eq 0 ]]; then # 日曜: フルバックアップ TYPE="full" mysqldump --defaults-extra-file="$MYSQL_CNF" \ --single-transaction --all-databases | gzip -c > \ "${BACKUP_DIR}/all_${TYPE}_${DATE}.sql.gz" else # 月~土: バイナリログのフラッシュ&コピー TYPE="binlog" mysql --defaults-extra-file="$MYSQL_CNF" -e "FLUSH LOGS;" # バイナリログの保管は /var/lib/mysql/mysql-bin.* を対象にする rsync -a /var/lib/mysql/mysql-bin.* \ "${BACKUP_DIR}/binlog_${DATE}/" fi echo "[OK] ${TYPE} バックアップ完了"
バックアップ検証とエラー通知の設計
バックアップが「取れた」だけでは不十分です。「復元できるか」を担保するために、最低限の整合性チェックを組み込みます。6. gzip -t でバックアップ直後に破損検証する
# バックアップ後すぐに整合性チェック if gzip -t "$DUMP_FILE" 2>/dev/null; then echo "[OK] 整合性チェック: ${DUMP_FILE}" else echo "[ERROR] ダンプファイルが破損しています: ${DUMP_FILE}" >&2 rm -f "$DUMP_FILE" exit 1 fi # ファイルサイズが極端に小さい場合もエラーとして扱う(100KB未満) FILE_SIZE=$(stat -c%s "$DUMP_FILE") if [[ "$FILE_SIZE" -lt 102400 ]]; then echo "[WARN] ダンプファイルが異常に小さいです: ${FILE_SIZE}バイト" >&2 fi
gzip -tはgzipファイルの整合性を検証するオプションで、展開せずに圧縮データの構造を確認します。破損したダンプファイルを「あり」と記録してしまう前に即座に検出できます。7. エラー発生時のメール通知設計
ALERT_MAIL="admin@example.com" HOSTNAME_STR=$(hostname -s) notify_error() { local message="$1" local subject="[BACKUP ERROR] ${HOSTNAME_STR}: ${DB_NAME}" # mailxがある環境向け(sendmailの場合はsendmailコマンドに変える) echo "$message" | mailx -s "$subject" "$ALERT_MAIL" 2>/dev/null || \ echo "[WARN] メール送信に失敗しました" >&2 } cleanup() { local exit_code=$? if [[ -n "$TMPFILE" && -f "$TMPFILE" ]]; then rm -f "$TMPFILE" fi if [[ $exit_code -ne 0 ]]; then local msg="バックアップ失敗(exit: ${exit_code})\nDB: ${DB_NAME}\nサーバー: ${HOSTNAME_STR}\n時刻: $(date '+%Y-%m-%d %H:%M:%S')" echo -e "$msg" >&2 notify_error "$msg" [[ -f "$DUMP_FILE" ]] && rm -f "$DUMP_FILE" fi } trap cleanup EXIT
trap cleanup EXIT内でメール通知を呼ぶことで、スクリプトのどこでエラーが起きても確実に通知が飛びます。mailxコマンドはRHEL/Rocky LinuxではMailxパッケージ、UbuntuではbsdmailxかHeirloom mailxをインストールする必要があります。よくある失敗パターンとトラブルシュート
8. mysqldump が exit 0 を返すのにダンプが不完全な場合
mysqldumpの終了コードはMySQLサーバーへの接続失敗時は1を返しますが、テーブルのロック失敗などは警告メッセージを出力したまま0を返すことがあります。set -o pipefailだけに頼らず、ダンプ後のファイルサイズチェックとgzip -tを必ず追加してください。# NG: mysqldump の exit codeだけを信じる mysqldump -u root mydb > /backup/mydb.sql echo $? # 0が返っても不完全な可能性がある # OK: gzip -t でダンプ後に整合性検証を必ず追加する mysqldump ... | gzip -c > "$TMPFILE" gzip -t "$TMPFILE" || { echo "破損検出" >&2; exit 1; }
9. 「Access denied」で取得できないテーブルがある
backup_userに必要な権限が付与されていないと、一部のテーブルだけ欠落します。バックアップ専用ユーザーには最低限次の権限を付与します。# バックアップ専用ユーザーに必要な権限 mysql> GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION CLIENT, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'127.0.0.1'; mysql> FLUSH PRIVILEGES;
RELOADはFLUSH TABLES WITH READ LOCKに必要です。REPLICATION CLIENTはバイナリログ位置の記録(--master-dataオプション)を使う場合に必要です。10. gzip パイプで「Broken pipe」が出る
set -o pipefailが有効の場合、パイプラインの途中でエラーが起きると最後のコマンドでなく最初のコマンドの終了コードが採用されます。mysqldump ... | gzipでgzipが途中で失敗するとset -eでスクリプトが止まります。これは意図どおりの動作です。止まらない場合はset -o pipefailが有効になっているか確認してください。本記事のまとめ
シェルスクリプトでmysqldumpのバックアップを設計するには、エラーハンドリング・接続情報の外部化・世代管理・整合性検証の4つを組み合わせることが重要です。| やりたいこと | 実装パターン |
|---|---|
| 接続情報を安全に管理する | mysqldump --defaults-extra-file=/etc/mysql/backup.cnf(600権限のファイルを指定) |
| ダンプ失敗を確実に検出する | set -euo pipefail + trap cleanup EXIT |
| 一時ファイルの確実な後片付け | mktempで一時ファイル生成後、trap cleanup EXITで削除 |
| InnoDB をロックせずにダンプする | mysqldump --single-transactionを付ける |
| 古いバックアップを自動削除する | find "$BACKUP_DIR" -mtime +14 -delete |
| ダンプファイルの破損を検出する | gzip -t "$DUMP_FILE"をバックアップ直後に実行する |
| エラー時に即座に通知する | trap cleanup EXIT内でmailxを呼ぶ |
| DB容量が大きい場合のバックアップ | 週次フルダンプ + 日次バイナリログ退避の組み合わせ |
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトで設定ファイルを安全に更新する設計|バックアップ・原子的置換・trapロールバックの実装パターン
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます