天天看點

分享一個 MySQL Shell 備份資料庫的腳本

作者:賀浦力特

編寫目的

使用 mysql shell 對 MySQL 資料庫進行線上備份,并指定輪轉次數

說明

因為個人原因,有點強迫症, 喜歡編寫能夠複用且使用簡單的腳本,提供簡單的功能, 先是寫的oracle備份腳本, 後來陸續寫了其他資料庫和作業系統的腳本

mysql備份先是用的mysqldump,後來又寫了xtrabackup, mysqlbackup, mysql shell的一共4個, 後面有空都貼出來, 基本上除了使用的備份工具不同, 基本流程大同小異

能看懂腳本的花幾分鐘基本上也就明白了, 也都在測試環境和生産環境用過,但還是建議根據你的環境測試一下 也許有bug我沒測出來,哈哈

為了節省大家時間,也能放心使用,把大概功能略述一下

一 使用時隻需要提供備份目的地和輪轉次數

1. 備份目的地就不說了

2. 輪轉次數. 有點像logrotate,每次備份會在備份目的地生成一個序号命名的子檔案夾, 到指定的輪轉次數再從頭開始

二 臨時檔案

記錄上次備份狀态的檔案, 主要用于防止上一次備份未結束,定時任務又啟動了下一次任務。

記錄輪轉序号的隐藏檔案, 決定下次備份到哪兒,每次備份結束+1, 到最大了再從0開始

日志檔案,記錄了備份的開始時間,每個庫的開始備份時間,結束時間

資料庫清單檔案,先查詢資料庫名稱并記錄于這個檔案, 然後挨個備份。 不喜歡整個執行個體一起備份, 還是分資料庫一個一個的好, 個人習慣。

三 執行順序

先查詢資料庫, 再挨個備份資料庫, 備份完成了,再删除廢棄的備份(寫死的15天,可以自己改,因為我一般輪轉次數是7或3, 15天足夠了)。

四 備份時間

背景備份的話可以看日志檔案減一下, 前台執行的話腳本運作結束也會計算運作了多久(開始,結束時間減了一下)

速度比較

測試資料庫 73G :

mysqldump: 花了 34 分鐘 備份輸出10G

myshell dump 預設壓縮 線程4(預設): 花了 30 分鐘 備份輸出 10G

myshell dump 預設壓縮 線程8: 花了 5 分鐘 輸出 備份輸出10G

myshell dump 預設壓縮 線程12: 花了 5 分鐘 輸出 備份輸出10G

xtrabackup 預設并行度1: 花了 34 分鐘輸出73G

#!/bin/bash

# =========================================================================
# (C) Copyright 2003-2033 hoplite
# =========================================================================
# Script Purpose : backup mysql database by mysql shell
# -------------------------------------------------------------------------
# instructions
# 1.setup url
#   save credentials: run mysqlsh to connect database and save password. the password will save in .mylogin.cnf with the server URL
#   list login-path: mysql_config_editor print --all
# 2.setup cron job
#   example: dump databases one by one every night
#   0 3 * * * /u01/backup/mysql_shelldump.sh [email protected]:3308/mysql /u01/backup/shelldump 7

# -------------------------------------------------------------------------
# Version Date      Description
# -------------------------------------------------------------------------
# V1.0              Initial version
# =========================================================================
# NOTE
# MySQL Shell's dump utility util.dumpInstance() and util.dumpSchemas() introduced in MySQL Shell 8.0.21

[ -f ~/.bash_profile ] && . ~/.bash_profile
set -e -u -o pipefail

show_usage ()
{
  echo "usage: `basename $0` uri target-basedir rotate"
  echo "eg.    `basename $0` [email protected]:3308/mysql /u01/backup/shelldump 7"
}

if [ $# -lt 3 ] || [ $# -gt 3 ]; then
   show_usage
   exit 0
else
  start_time=`date +%F" "%T`

  p_uri=$1
  p_targetbasedir=$2
  p_rotate_factor=$3

  v_id=`date +%Y%m%d%H%M%S`
  v_hash=`echo -n ${p_uri}|md5sum|cut -f1 -d" "`
  [ -d ${p_targetbasedir} ] || mkdir -p ${p_targetbasedir}

  stusfile=${p_targetbasedir}/.status.${v_hash} #file to record last backup status
  rotafile=${p_targetbasedir}/.rotate.${v_hash} #file to record rotate number
  log_file=${p_targetbasedir}/backup.log
  vdbsfile=${p_targetbasedir}/.vdbs.${v_hash}.${v_id} #file to record databases

  #check last backup result
  if [ -f ${stusfile} ]; then
    if [ `cat ${stusfile}` = "running" ]; then
      loginfo="`date +%F" "%T` ...ERROR:last backup fail, check it,then drop status file and rerun." ; echo ${loginfo} ; echo ${loginfo} >> ${log_file}
      exit 1
    fi
  else
    echo "running" > ${stusfile}
  fi

  #rotate number,+1 every time
  if [ -f ${rotafile} ]; then
    v_last_id=`cat ${rotafile}`
    v_incr_id=`expr ${v_last_id} + 1`
    v_curr_id=$(( ${v_incr_id} % ${p_rotate_factor} ))
  else
    v_curr_id=0
  fi
  echo ${v_curr_id} > ${rotafile}
  
  v_targetdir=${p_targetbasedir}/${v_curr_id}
  [ -d ${v_targetdir} ] && mv ${v_targetdir} ${p_targetbasedir}/obsolete.${v_id}
  [ -d ${v_targetdir} ] || mkdir -p ${v_targetdir}
  cd ${v_targetdir}
  loginfo="`date +%F" "%T` ...start backup to ${v_targetdir}" ; echo ${loginfo} ; echo ${loginfo} >> ${log_file}

mysqlsh --uri ${p_uri} --sql > ${vdbsfile}<<EOF
SELECT schema_name FROM information_schema.SCHEMATA;
EOF

  filtered_dbs=("SCHEMA_NAME" "TABLE_SCHEMA" "mysql" "sys" "information_schema" "performance_schema" "the_database_you_do_not_want_to_dump")
  while read v_db
  do
    is_in_filtered_db="N"
    for fd in ${filtered_dbs[*]} ; do
      if [ "${fd}" == "${v_db}" ]; then
        is_in_filtered_db="Y"
        break
      fi
    done

    if [[ "${is_in_filtered_db}" == "N" ]]; then
      loginfo="`date +%F" "%T` ...backup database ${v_db}" ; echo ${loginfo} ; echo ${loginfo} >> ${log_file}
      mysqlsh --uri ${p_uri} -e "util.dumpSchemas(['${v_db}'],'${v_targetdir}/${v_db}',{threads:8})"
    fi
  done < ${vdbsfile}
  loginfo="`date +%F" "%T` ...end backup to ${v_targetdir}" ; echo ${loginfo} ; echo ${loginfo} >> ${log_file}
  rm "${vdbsfile:-unset}"
  echo "completed" > ${stusfile}
  
  tmpfile=/tmp/.tmpfile.`basename $0`.${v_hash}.${v_id}
  find ${p_targetbasedir} -mtime +15 -name "obsolete.20????????????" -type d > ${tmpfile}
  #eg. /u01/backup/shelldump/obsolete.20230317100025
  while read foldername
  do
    echo "${foldername:-unset}"
    rm -rf "${foldername:-unset}"
    echo "deleted backup folder : ${foldername}"
  done < ${tmpfile}

  end_time=`date +%F" "%T`
  t_start_time=`date -d "${start_time}" +%s`
  t_end_time=`date -d "${end_time}" +%s`
  t_diff_secs=$((${t_end_time} - ${t_start_time}))
  t_hours=$((${t_diff_secs}/3600))
  t_mins=$(($((${t_diff_secs}-${t_hours}*3600))/60))
  t_secs=$((${t_diff_secs}%60))

  echo
  echo ==================================================
  echo "File Name:`basename $0`    Start Time:${start_time}    End Time:${end_time}    Total Cost:${t_hours}:${t_mins}:${t_secs}"
  echo ==================================================
  echo
fi