xtrabackup是percona公司專門針對mysql 數據庫開發的一款開源免費的物理備份(熱備)工具,能夠對innodb和xtradb等事務引擎數據庫實現非阻塞(即不鎖表)方式的備份,也能夠針對myisam等非事務引擎鎖表方式備份,是商業備份工具InnoDB Hotbackup的一個很好的替代品。mysql
innodb
數據不影響業務mysql
和maridb
文件擴展名sql
文件擴展名 | 文件做用說明 |
---|---|
.idb文件 | 以獨立表空間存儲的InnoDB引擎類型的數據文件擴展名 |
.ibdata文件 | 以共享表空間存儲的InnoDB引擎類型的數據文件擴展名 |
.frm文件 | 存放於表相關的元數據(meta)信息及表結構的定義信息 |
.MYD文件 | 存放MyISAM引擎表的數據文件擴展名 |
.MYI文件 | 存放MyISAM引擎表的索引信息文件擴展名 |
名詞數據庫
redo日誌
redo
日誌,也稱事務日誌,是innodb
引擎的重要組成部分,做用是記錄innodb
引擎中每個數據發生的變化信息。主要用於保證innodb
數據的完整性,以及丟數據後的恢復,同時能夠有效提高數據庫的io
等性能。redo
日誌對應的配置參數爲innodb_log_file_size
和innodb_log_files_in_group
centos
Undo日誌
Undo
是記錄事務的逆向邏輯操做或者向物理操做對應的數據變化的內容,undo
日誌默認存放在共享表空間裏面的ibdata*
文件,和redo
日誌功能不一樣undo
日誌主要用於回滾數據庫崩潰前未完整提交的事務數據,確保數據恢復先後一致。數組
LSN
LSN
,全拼log sequence number
,中文是日誌序列號,是一個64
位的整型數字,LSN
的做用是記錄redo
日誌時,使用LSN
惟一標識一條變化的數據。安全
checkpoint
用來標識數據庫崩潰後,應恢復的redo log
的起始點bash
checkpoint
,記錄LSN
號碼information schema.xxx
備份innoDB
文件,過程當中發生的新變化redo
也會被保存,保存至備份路徑Binlog
只讀,FTWRL
(global read lock)Non InnoDB
,拷貝完成解鎖binlog
、LSN
Last LSN
備份時經歷的階段:服務器
InnoDB表:app
checkpoint
:將已提交的數據頁刷新到磁盤,記錄一個LSN
號碼InnoDB
表相關的文件(ibdata1
、frm
、ibd
...)redo
也會備份走非InnoDB表:socket
FTWRL
全局鎖表InnoDB
表的數據再次統計LSN
號碼,寫入到專用文件xtrabackup checkpoint
記錄二進制日誌位置
全部備份文件統一存放在一個目錄下,備份完成
redo lo
文件內容和全備數據合併,而且read only
不進行回滾redo log
變化加載到第一次增量數據再與全量數據作合併redo log
變化加載到第二次增量數據備份,在與全量和第一次增量的合併再進行合併, 最後把髒數據進行提交或回滾binlog
的文件內容# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo # yum -y install perl perl-devel libaio libaio-devel perl-Time-HiRes perl-DBD-MySQL libev
這裏使用的是清華源,官方地址下載較慢。
官方最新的是8.0
版本,此版本只適用於mysql8.0
版本的數據庫,因此這裏下載支持mysql5.6
的版本
# wget -c https://mirrors.tuna.tsinghua.edu.cn/percona/centos/7/os/x86_64/percona-xtrabackup-24-2.4.18-1.el7.x86_64.rpm # yum localinstall -y percona-xtrabackup-24-2.4.18-1.el7.x86_64.rpm
xtrabackup
能鏈接上數據庫:在mysql
配置文件client
下指定socket
位置標籤或者在使用時指定[client] socket=/tmp/mysql.sock
mysqld
下的datadir
參數[mysqld] datadir=/usr/local/mysql/data
binlog
log-bin = /data/mysql/mysql-bin binlog_format="ROW" expire_logs_days=3
xtrabackup
是服務器端工具,不能遠程備份# innobackupex --user=root --password=123456 /backup/xbk/
在作全備時爲了控制生成的目錄名稱,能夠添加參數--no-timestamp
並保留日期
# innobackupex --user=root --password=123456 --no-timestamp /backup/xbk/full_`date +%F`
在備份目錄下查看備份的文件,除了mysql
自身的數據文件外,還有這樣幾個文件
# pwd /backup/xbk/2020-03-25_10-26-16 # ll ... -rw-r-----. 1 root root 27 Mar 25 10:53 xtrabackup_binlog_info -rw-r-----. 1 root root 147 Mar 25 10:53 xtrabackup_checkpoints -rw-r-----. 1 root root 480 Mar 25 10:53 xtrabackup_info -rw-r-----. 1 root root 31987200 Mar 25 10:53 xtrabackup_logfile
binlog
位置binlog
的文件名字和當時的結束的position
,能夠用來做爲截取binlog
時的起點# cat xtrabackup_binlog_info mysql-bin.000001 192790323
commit
過的,內存中的數據頁刷新到磁盤CKPT
開始備份數據,數據文件的LSN
會停留在to_lsn
位置redo
的undo
,若是一旦有變化會將日誌也一併備走,並記錄LSN
到last_lsn
,從to_lsn
——>last_lsn
就是,備份過程當中產生的數據變化# cat xtrabackup_checkpoints backup_type = full-backuped from_lsn = 0 # 上次所到達的LSN號(對於全備就是從0開始,對於增量有別的顯示方法) to_lsn = 14194921406 # 備份開始時間(ckpt)點數據頁的LSN last_lsn = 14200504300 # 備份結束後,redo日誌最終的LSN compact = 0 recover_binlog_info = 0 flushed_lsn = 14177446392
# cat xtrabackup_info uuid = c04f3d33-6e43-11ea-9224-005056ac7d7c name = tool_name = innobackupex tool_command = --user=root --password=... /backup/xbk/ tool_version = 2.4.18 ibbackup_version = 2.4.18 server_version = 5.6.46-log start_time = 2020-03-25 10:26:16 end_time = 2020-03-25 10:53:05 lock_time = 0 binlog_pos = filename 'mysql-bin.000001', position '192790323' innodb_from_lsn = 0 innodb_to_lsn = 14194921406 partial = N incremental = N format = file compact = N compressed = N encrypted = N
redo
,關聯在備份期間對InnoDB
表產生的新變化恢復流程:
ckpt
,已提交的數據髒頁,從內存刷寫到磁盤,並記錄此時的LSN
號redo
和undo
一塊兒拷貝走,也就是checkpoint LSN
以後的日誌Innodb
「自動故障恢復」的過程,將redo
(前滾)與undo
(回滾)進行應用cp
備份到原來數據目錄下模擬數據庫宕機,刪除數據
# pkill mysqld # rm -rf datadir=/usr/local/mysql/data/*
prepare
預處理備份文件,將redo
進行重作,已提交的寫到數據文件,未提交的使用undo
回滾掉。模擬了CSR
的過程
# innobackupex --apply-log /backup/xbk/2020-03-25_10-26-16
數據恢復並啓動數據庫
# cp -a /backup/xbk/2020-03-25_10-26-16/* /usr/local/mysql/data/ # chown -R mysql.mysql /usr/local/mysql/data/ # /etc/init.d/mysqld start
增量必須依賴於全備
每次增量都是參照上次備份的LSN
號碼(xtrabackup checkpoints),在此基礎上變化的數據頁進行備份
會將備份過程當中產生新的變化的redo
一併備份走
恢復時增量備份沒法單獨恢復,必須基於全備進行恢復。必須將全部的增量備份,按順序所有合併到全備中
# innobackupex --user=root --password --no-timestamp /backup/full >&/tmp/xbk_full.log
db01 [(none)]>create database cs charset utf8; db01 [(none)]>use cs db01 [cs]>create table t1 (id int); db01 [cs]>insert into t1 values(1),(2),(3); db01 [cs]>commit;
# innobackupex --user=root --password=123 --no-timestamp --incremental --incremental-basedir=/backup/full /backup/inc1 &>/tmp/inc1.log
參數:
--incremental 增量備份,後面跟要增量備份的路徑
--incremental-basedir=DIRECTORY 基目錄,增量備份使用,上一次(全備)增量備份所在目錄
db01 [cs]>create table t2 (id int); db01 [cs]>insert into t2 values(1),(2),(3); db01 [cs]>commit;
# innobackupex --user=root --password=123 --no-timestamp --incremental --incremental-basedir=/backup/inc1 /backup/inc2 &>/tmp/inc2.log
db01 [cs]>create table t3 (id int); db01 [cs]>insert into t3 values(1),(2),(3); db01 [cs]>commit; db01 [cs]>drop database cs;
恢復流程:
模擬數據庫宕機,刪除數據
# pkill mysqld # rm -rf datadir=/usr/local/mysql/data/*
確認備份完整性,對比每一個備份集中的checkpoints
文件
全備份的checkpoints
文件內容以下,能夠發現to_lsn
和last_lsn
中間相差9
。這個數字差在5.7
版本前爲0
,二者相等,在5.7
版本後開啓GTID
後有了這個差值,做爲內部使用。因此若是是知足這個條件,那麼能夠認爲備份期間並無新的數據修改。一樣的,在增量備份的備份集下的文件也是如此,且增量備份from_lsn
號與相鄰的上一個備份的last_lsn
減去9
是一致的。
# cat full/xtrabackup_checkpoints backup_type = full-backuped from_lsn = 0 to_lsn = 337979814 last_lsn = 337979823 compact = 0 recover_binlog_info = 0 # cat inc1/xtrabackup_checkpoints backup_type = incremental from_lsn = 337979814 to_lsn = 337985758 last_lsn = 337985767 compact = 0 recover_binlog_info = 0 # cat inc2/xtrabackup_checkpoints backup_type = incremental from_lsn = 337985758 to_lsn = 337991702 last_lsn = 337991711 compact = 0 recover_binlog_info = 0
合併整理全部(apply-log)備份(full+inc1+inc2)到全備:
--redo-only
參數表示只應用redo
,不進行undo
,防止LSN
號發生變化,除最後一次的備份合併外都須要加此參數
# innobackupex --apply-log --redo-only /data/backup/full
check_points
文件的last_lsn
相同,說明合併成功# 合併inc1到full中 # innobackupex --apply-log --redo-only --incremental-dir=/data/backup/inc1 /data/backup/full # 合併inc2到full中(最後一次增量) # innobackupex --apply-log --incremental-dir=/data/backup/inc2 /data/backup/full
# innobackupex --apply-log /data/backup/full
# cp -a /backup/full/* /usr/local/mysql/data/ # chown -R mysql.mysql /usr/local/mysql/data/ # /etc/init.d/mysqld start
drop
以前的 binlog
查看最後一次增量備份中的文件內容
# cat /data/backup/inc2/xtrabackup_binlog_info mysql-bin.000020 1629 9b8e7056-4d4c-11ea-a231-000c298e182d:1-19. df04d325-5946-11ea-000c298e182d:1-7 # mysqlbinlog --skip-gtids --start-position=1629 /data/binlog/mysql-bin.000020 >/data/backup/binlog.sql 或 # mysqlbinlog --skip-gtids --include-gtids='9b8e7056-4d4c-11ea-a231-000c298e182d:1-19' /data/binlog/mysql-bin.000020 >/data/backup/binlog.sql
登陸mysql
,恢復最後的sql
Master [(none)]>set sql_log_bin=0; Master [(none)]>source /data/backup/binlog.sql Master [(none)]>set sql_log_bin=1;
恢復完成。
現有一個生產數據庫,總數據量3TB
,共10
個業務,10
個庫500
張表。週三上午10
點,誤DROP
了taobao.1
業務核心表20GB
,致使taobao
庫業務沒法正常運行。
採用的備份策略是:週日full
全備,週一到週五inc
增量備份,binlog
完整
針對此種場景,怎麼快速恢復業務,還不影響其餘業務?
遷移表空間
create table t1; alter table taobao.t1 discard tablespace; alter table taobao.t1 import tablespace;
一、要想恢復單表,須要表結構和數據
首先合併備份到最新的備份
如何獲取表結構?藉助工具mysqlfrm
yum install -y mysql-utilities
二、獲取建表語句
# mysqlfrm —diagnostic t2.frm create table `t2` ( `id` int(11) default null ) engine=InnoDB;
三、進入數據庫中建立表
create table `t2` ( `id` int(11) default null ) engine=InnoDB;
四、丟棄新建的表空間
alter table t2 discard tablespace;
五、將表中的數據cp
回數據庫數據目錄
cp t2.ibd /data/23306/xbk/ chown mysql:mysql /data/3306/xbk/t2.ibd
六、導入表空間
alter table t2 import tablespace;
七、切割二進制日誌到刪庫前生成sql
並導入
建立一個專用於備份的受權用戶
create user 'back'@'localhost' identified by '123456'; grant reload,lock tables,replication client,create tablespace,process,super on *.* to 'back'@'localhost' ; grant create,insert,select on percona_schema.* to 'back'@'localhost';
mybak-all.sh
#!/bin/bash #全量備份,只備份一次 #指定備份目錄 backup_dir="/bak/mysql-xback" #檢查 [[ -d ${backup_dir} ]] || mkdir -p ${backup_dir} if [[ -d ${backup_dir}/all-backup ]];then echo "全備份已存在" exit 1 fi #命令,須要設置 innobackupex --defaults-file=/etc/my.cnf --user=back --password='123456' --no-timestamp ${backup_dir}/all-backup &> /tmp/mysql-backup.log tail -n 1 /tmp/mysql-backup.log | grep 'completed OK!' if [[ $? -eq 0 ]];then echo "all-backup" > /tmp/mysql-backup.txt else echo "備份失敗" exit 1 fi
mybak-section.sh
#!/bin/bash #增量備份 #備份目錄 backup_dir="/bak/mysql-xback" #新舊備份 old_dir=`cat /tmp/mysql-backup.txt` new_dir=`date +%F-%H-%M-%S` #檢查 if [[ ! -d ${backup_dir}/all-backup ]];then echo "還未全量備份" exit 1 fi #命令 /usr/bin/innobackupex --user=back --password='123456' --no-timestamp --incremental --incremental-basedir=${backup_dir}/${old_dir} ${backup_dir}/${new_dir} &> /tmp/mysql-backup.log tail -n 1 /tmp/mysql-backup.log | grep 'completed OK!' if [[ $? -eq 0 ]];then echo "${new_dir}" > /tmp/mysql-backup.txt else echo "備份失敗" exit 1 fi
單點,備份binlog
,要指定備份目錄位置和其它變量
#!/bin/bash # # 注意:執行腳本前修改腳本中的變量 # 功能:cp方式增量備份 # # 適用:centos6+ # 語言:中文 # #使用:./xx.sh -uroot -p'123456',將第一次增量備份後的binlog文件名寫到/tmp/binlog-section中,若都沒有,自動填寫mysql-bin.000001 #過程:增量先刷新binlog日誌,再查詢/tmp/binlog-section中記錄的上一次備份中最新的binlog日誌的值 # cp中間的binlog日誌,並進行壓縮。再將備份中最新的binlog日誌寫入。 #恢復:先進行全量恢復,再根據全量備份附帶的time-binlog.txt中的記錄逐個恢復。當前最新的Binlog日誌要去掉有問題的語句,例如drop等。 #[變量] #mysql這個命令所在絕對路徑 my_sql="/usr/local/mysql/bin/mysql" #mysqldump命令所在絕對路徑 bak_sql="/usr/local/mysql/bin/mysqldump" #binlog日誌所在目錄 binlog_dir=/usr/local/mysql/data #mysql-bin.index文件所在位置 binlog_index=${binlog_dir}/mysql-bin.index #備份到哪一個目錄 bak_dir=/bak/mysql-binback #這個腳本的日誌輸出到哪一個文件 log_dir=/tmp/mybak-binlog.log #保存的天數,4周就是28天 save_day=10 #[自動變量] #當前年 date_nian=`date +%Y-` begin_time=`date +%F-%H-%M-%S` #全部天數的數組 save_day_zu=($(for i in `seq 1 ${save_day}`;do date -d -${i}days "+%F";done)) #開始 /usr/bin/echo >> ${log_dir} /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:開始增量備份" >> ${log_dir} #檢查 ${my_sql} $* -e "show databases;" &> /tmp/info_error.txt if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:登錄命令錯誤" >> ${log_dir} /usr/bin/cat /tmp/info_error.txt #若是錯誤則顯示錯誤信息 exit 1 fi #移動到目錄 cd ${bak_dir} bak_time=`date +%F-%H-%M` bak_timetwo=`date +%F` #刷新 ${my_sql} $* -e "flush logs" if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) error:刷新binlog失敗" >> ${log_dir} exit 1 fi #獲取開頭和結尾binlog名字 last_bin=`cat /tmp/binlog-section` next_bin=`tail -n 1 ${binlog_dir}/mysql-bin.index` echo ${last_bin} |grep 'mysql-bin' &> /dev/null if [[ $? -ne 0 ]];then echo "mysql-bin.000001" > /tmp/binlog-section #不存在則默認第一個 last_bin=`cat /tmp/binlog-section` fi #截取須要備份的binlog行數 a=`/usr/bin/sort ${binlog_dir}/mysql-bin.index | uniq | grep -n ${last_bin} | awk -F':' '{print $1}'` b=`/usr/bin/sort ${binlog_dir}/mysql-bin.index | uniq | grep -n ${next_bin} | awk -F':' '{print $1}'` let b-- #輸出最新節點 /usr/bin/echo "${next_bin}" > /tmp/binlog-section #建立文件 rm -rf mybak-section-${bak_time} /usr/bin/mkdir mybak-section-${bak_time} for i in `sed -n "${a},${b}p" ${binlog_dir}/mysql-bin.index | awk -F'./' '{print $2}'` do if [[ ! -f ${binlog_dir}/${i} ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) error:binlog文件${i} 不存在" >> ${log_dir} exit 1 fi cp -rf ${binlog_dir}/${i} mybak-section-${bak_time}/ if [[ ! -f mybak-section-${bak_time}/${i} ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) error:binlog文件${i} 備份失敗" >> ${log_dir} exit 1 fi done #壓縮 if [[ -f mybak-section-${bak_time}.tar.gz ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:壓縮包mybak-section-${bak_time}.tar.gz 已存在" >> ${log_dir} /usr/bin/rm -irf mybak-section-${bak_time}.tar.gz fi /usr/bin/tar -cf mybak-section-${bak_time}.tar.gz mybak-section-${bak_time} if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) error:壓縮失敗" >> ${log_dir} exit 1 fi #刪除binlog文件夾 /usr/bin/rm -irf mybak-section-${bak_time} if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:刪除sql文件失敗" >> ${log_dir} exit 1 fi #整理壓縮的日誌文件 for i in `ls | grep "^mybak-section.*tar.gz$"` do echo $i | grep ${date_nian} &> /dev/null if [[ $? -eq 0 ]];then a=`echo ${i%%.tar.gz}` b=`echo ${a:(-16)}` #當前日誌年月日 c=`echo ${b%-*}` d=`echo ${c%-*}` #看是否在數組中,不在其中,而且不是當前時間,則刪除。 echo ${save_day_zu[*]} |grep -w $d &> /dev/null if [[ $? -ne 0 ]];then [[ "$d" != "$bak_timetwo" ]] && rm -rf $i fi else #不是當月的,其餘類型壓縮包,跳過 continue fi done #結束 last_time=`date +%F-%H-%M-%S` /usr/bin/echo "begin_time:${begin_time} last_time:${last_time}" >> ${log_dir} /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:增量備份完成" >> ${log_dir} /usr/bin/echo >> ${log_dir}
主從,備份relay-bin
,要指定備份目錄位置和其它變量
#!/bin/bash # # 注意:執行腳本前修改腳本中的變量 # 功能:cp方式增量備份 # # 適用:centos6+ # 語言:中文 # #使用:./xx.sh -uroot -p'123456' #[變量] #mysql這個命令所在絕對路徑 my_sql="/usr/local/mysql/bin/mysql" #mysqldump命令所在絕對路徑 bak_sql="/usr/local/mysql/bin/mysqldump" #binlog日誌所在目錄 binlog_dir=/usr/local/mysql/data #mysql-bin.index文件所在位置 binlog_index=${binlog_dir}/mysql-bin.index #備份到哪一個目錄 bak_dir=/bak/mysql-binback #這個腳本的日誌輸出到哪一個文件 log_dir=/tmp/mybak-binlog.log #保存的天數,4周就是28天 save_day=10 #[自動變量] #當前年 date_nian=`date +%Y-` begin_time=`date +%F-%H-%M-%S` #全部天數的數組 save_day_zu=($(for i in `seq 1 ${save_day}`;do date -d -${i}days "+%F";done)) #開始 /usr/bin/echo >> ${log_dir} /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:開始增量備份" >> ${log_dir} #檢查 ${my_sql} $* -e "show databases;" &> /tmp/info_error.txt if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:登錄命令錯誤" >> ${log_dir} /usr/bin/cat /tmp/info_error.txt #若是錯誤則顯示錯誤信息 exit 1 fi #移動到目錄 cd ${bak_dir} bak_time=`date +%F-%H-%M` bak_timetwo=`date +%F` #建立文件 rm -rf mybak-section-${bak_time} /usr/bin/mkdir mybak-section-${bak_time} for i in `ls ${binlog_dir}| grep relay-bin` do cp -rf ${binlog_dir}/${i} mybak-section-${bak_time}/ if [[ ! -f mybak-section-${bak_time}/${i} ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) error:binlog文件${i} 備份失敗" >> ${log_dir} exit 1 fi done #壓縮 if [[ -f mybak-section-${bak_time}.tar.gz ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:壓縮包mybak-section-${bak_time}.tar.gz 已存在" >> ${log_dir} /usr/bin/rm -irf mybak-section-${bak_time}.tar.gz fi /usr/bin/tar -cf mybak-section-${bak_time}.tar.gz mybak-section-${bak_time} if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) error:壓縮失敗" >> ${log_dir} exit 1 fi #刪除binlog文件夾 /usr/bin/rm -irf mybak-section-${bak_time} if [[ $? -ne 0 ]];then /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:刪除sql文件失敗" >> ${log_dir} exit 1 fi #整理壓縮的日誌文件 for i in `ls | grep "^mybak-section.*tar.gz$"` do echo $i | grep ${date_nian} &> /dev/null if [[ $? -eq 0 ]];then a=`echo ${i%%.tar.gz}` b=`echo ${a:(-16)}` #當前日誌年月日 c=`echo ${b%-*}` d=`echo ${c%-*}` #看是否在數組中,不在其中,而且不是當前時間,則刪除。 echo ${save_day_zu[*]} |grep -w $d &> /dev/null if [[ $? -ne 0 ]];then [[ "$d" != "$bak_timetwo" ]] && rm -rf $i fi else #不是當月的,其餘類型壓縮包,跳過 continue fi done #結束 last_time=`date +%F-%H-%M-%S` /usr/bin/echo "begin_time:${begin_time} last_time:${last_time}" >> ${log_dir} /usr/bin/echo "time:$(date +%F-%H-%M-%S) info:增量備份完成" >> ${log_dir} /usr/bin/echo >> ${log_dir}