MySQL教程(14)二进制日志binlog的原理与运维管理

MySQL教程(14)二进制日志binlog的原理与运维管理

一、Binlog 原理介绍

在 MySQL 中有一种非常特殊的二进制日志(binlog),该日志记录了数据库中每行数据的变动情况,数据库的主从复制、数据闪回等核心功能都需要依赖该日志来实现。binlog在记录每个事务时,会先写入到 binlog cache,等事务提交时再从 cache 写入 binlog 文件。在写入日志时,一个事务所产生的 binlog 不能被拆开,无论该事务有多大都需要一次性完整写入(这也是binlog大小有时候会超出单个文件限制的原因)。

另外需要搞清楚binlog和redo log的区别,两者虽然都可以用于数据恢复,但是侧重点完全不同。redo log 属于 InnoDB 引擎层,用于数据库实例崩溃恢复,而 binlog 属于 MySQL Server 层,用于主从复制和时间点恢复。由于一个事务在提交时这两份日志都需要写入,但是不可能同时写完,为了避免出现中途宕机而导致数据不一致的情况,这两个独立日志需要通过参数 sync_binlog=1 与 innodb_flush_log_at_trx_commit=1 实现两阶段提交,也就是双1参数。

二、Binlog 格式介绍

1、statement

标准模式,简称SBR,该格式下保存了每一条修改数据的SQL语句本身,日志记录量较少,对于一些执行结果不明确的语句会造成主从数据不一致,比如SYSDATE()函数、sql_mode、字符集、用户变量等。只要有一点点差异,同一条语句执行出的结果就不同,而且这类语句不一致在执行的时候不会报错,不容易发现

2、row

行模式,简称RBR,是 5.7.7 之后的默认格式,也是最推荐、最安全的模式。该日志会将数据的具体改变记录下来,不会因为某些执行结果不明确的语句导致主从数据不一致。该日志记录量较大,如果有对BLOB等大字段进行操作,记录会更耗时,因为row会记录BLOB字段的具体值,而不单单是生成数据的语句。该格式的文件内容无法直接查看,需要通过mysqlbinlog --base64-output=decode-rows 转码

delete from test; #假设这条SQL删除100万条数据,STATEMENT语句模式仅记录这一条SQL语句,ROW会记录执行删除时的每一个语句,会记录100万次

3、mixed

混合模式,简称MBR,是statement和row的结合模式,当基于statement的语句无法精确复制时,就自动采用行模式进行复制


三、binlog 查看与解析

1、查看当前所有binlog

#两条等价
sql > SHOW BINARY LOGS;
sql > SHOW MASTER LOGS;
Log_name                File_size
master-bin.047572	13059910614
master-bin.047573       1442223014
master-bin.047574       1744328041


2、查看当前正在写的binlog

File 是当前正在写的文件,Position 是当前位点,Executed_Gtid_Set 是已执行的 GTID 集合(开了 GTID 才有内容)。8.4 起这条命令改成了 SHOW BINARY LOG STATUS,原有语法会报错

mysql> SHOW MASTER STATUS;
+-------------------+-----------+----------------+------------------+-------------------+
| File              | Position  | Binlog_Do_DB   | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+-----------+----------------+------------------+-------------------+
| master-bin.047708 | 485658162 | restapi,market |                  |                   |
+-------------------+-----------+----------------+------------------+-------------------+
1 row in set (0.00 sec)


3、查看 binlog 存放路径

log_bin_basename 是路径和文件名前缀,log_bin_index 是索引文件的路径。这里的 .index 文件是文本文件,可以直接查看,该文件记录 MySQL 认为有效的所有 binlog 文件,SHOW BINARY LOGS 输出内容也来自这个文件。如果有binlog被手工删除过就会导致.index和目录里的实际文件不一致。如果 SHOW BINARY LOGS 报 “You are not using binary logging”,说明实例没有开启 binlog,用 SHOW VARIABLES LIKE 'log_bin'; 确认,值为 OFF 的话需要在 my.cnf 里配置 log_bin 和 server_id 后重启。

mysql> SHOW VARIABLES LIKE 'log_bin%';
+---------------------------------+--------------------------------------------+
| Variable_name                   | Value                                      |
+---------------------------------+--------------------------------------------+
| log_bin                         | ON                                         |
| log_bin_basename                | /data/mysql/var/lib/mysql/master-bin       |
| log_bin_index                   | /data/mysql/var/lib/mysql/master-bin.index |
| log_bin_trust_function_creators | OFF                                        |
| log_bin_use_v1_row_events       | OFF                                        |
+---------------------------------+--------------------------------------------+
5 rows in set (0.01 sec)


4、binlog 解析查看

由于binlog是二进制文件,不能使用vi等文本工具直接查看和修改,需要使用专门的命令或者工具

· show binlog events 命令

该命令可以快速看到每个position的大概操作,输出结果中 Event_type = Query 的行就是修改数据的具体 SQL ,Pos 和 End_log_pos 字段记录了开始和结束位置。由于每个DDL本身就是一个事件,所以能直接在Info中看到,而DML语句都是由BEGIN开头,COMMIT结束。

#显示指定二进制日志内容
mysql > show binlog  events in 'master-bin.000002';          

#显示指定N条二进制日志内容
mysql > show binlog  events in 'master-bin.000002' limit5;      

#从指定位置开始显示日志
mysql > show binlog  events in 'master-bin.000002' from 3390 limit5;    

#也支持使用 show relaylog events 命令查看relaylog
show relaylog events in 'relay-bin.000002';


如下图1596处Position执行了truncate语句,要使用该binlog进行数据恢复的话就需要恢复到1512处

binlog1.png

· mysqlbinlog工具

该工具是实际运维中binlog的解析利器,除了可以同时解析多个binlog,还可以用作数据恢复。主要参数有以下:

· 过滤范围

--start-datetime | --stop-datetime按起止时间截取 binlog ,--stop-datetime 所指定的时间节点并不包含该时间执行的事务,而是前一个事务,在进行还原的时候需要注意正确的位置
--start-position | --stop-position按position位点来截取binlog,解析多个文件时起始位点只对第一个文件生效,结束位点只对最后一个文件生效,中间的文件都是整个解析。--stop-position 所指定的位点并不包含该位点本身,而是前一个事务,在进行还原的时候需要注意正确的位置
-d | --database只输出指定库的内容
--include-gtids / --exclude-gtids只解析或排除指定 GTID 的事务,如--include-gtids='143c2270-3b22-11ea-9dea-14feb5dc2c77:2-8'


· 输出内容

--base64-output=decode-rows如果不加该参数,日志会用 base64 编码输出,人看不懂。该参数通常和 -v 配合使用,这样就可以看到数据库到底执行了什么操作。使用 mysqlbinlog 进行回放时不能该参数。
-v | -vv

在 decode-rows 的基础上加 -v 会增加以 ### 开头的伪 SQL,供人查看;加 -vv 会在每列后面增加类型注释,以及开启 binlog_rows_query_log_event 时的原始 SQL。使用 mysqlbinlog 进行回放时不能该参数。

-r / --result-file把输出写入指定文件,不用重定向


--base64-output=decode-rows 与 -v | -vv 输出区别展示

# 不加--base64-output=decode-rows
mysqlbinlog mysql-bin.000003  

# 输出结果中BINLOG后面是一串看不懂的二进制格式
# at 420
#261027 10:15:02 server id 1  end_log_pos 477  Table_map: `test1`.`students` mapped to number 108
# at 477
#261027 10:15:02 server id 1  end_log_pos 540  Update_rows: table id 108 flags: STMT_END_F

BINLOG '
Vk07ZhMBAAAAOQAAAN0BAAAAAGwAAAAAAAEABXRlc3QxAAhzdHVkZW50cwACAw8CKAAC
...
'/*!*/;


#加上 decode-rows
mysqlbinlog --base64-output=decode-rows mysql-bin.000003

# 可以看到有一个UPDATE的操作,但是依然看不到内容
# at 420
#261027 10:15:02 server id 1  end_log_pos 477  Table_map: `test1`.`students` mapped to number 108
# at 477
#261027 10:15:02 server id 1  end_log_pos 540  Update_rows: table id 108 flags: STMT_END_F
# at 540
#261027 10:15:02 server id 1  end_log_pos 571  Xid = 1025
COMMIT/*!*/;


# 加上-v可以看到出现了以 ### 开头的伪 SQL,列名会以 @1、@2 这样的序号进行显示,这也是闪回工具生成反向 SQL 的依据,把 WHERE 和 SET 对调即可
mysqlbinlog -v --base64-output=decode-rows mysql-bin.000003
# at 477
#261027 10:15:02 server id 1  end_log_pos 540  Update_rows: table id 108 flags: STMT_END_F
### UPDATE `test1`.`students`
### WHERE
###   @1=1
###   @2='zhangsan'
### SET
###   @1=1
###   @2='lisi'


# 加上-vv可以看到增加了列名的数据类型以及开启 binlog_rows_query_log_events 后所记录的原始 SQL
mysqlbinlog -vv --base64-output=decode-rows mysql-bin.000003
# at 420
# update students set name='lisi' where id=1
# at 477
### UPDATE `test1`.`students`
### WHERE
###   @1=1 /* INT meta=0 nullable=1 is_null=0 */
###   @2='zhangsan' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */
### SET
###   @1=1 /* INT meta=0 nullable=1 is_null=0 */
###   @2='lisi' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */


· 回放和恢复相关参数

· --skip-gtids=true

该参数如果为true表示输出里不带 GTID 信息,回放时被当作新事务,用于目标库已经有同样 GTID 的情,通过该参数跳过这些GTID,否则根据同一个GTID只能执行一次的特性不会执行。如果是恢复到其他实例并且不包含源实例的 GTID 信息,那么可忽略该参数


· 远程连接相关参数

-R / --read-from-remote-server

可以读取远程数据库的binlog,要配合 -h、-P、-u、-p指定远端数据库信息
--raw

和 -R 配合,把读取的远端 binlog 原样保存到磁盘,不做任何解析和格式转变,可以实现异地备份 binlog

--stop-never读完最新的文件后不退出,持续接收新产生的日志,相当于实时备份
--connection-server-id指定连接时使用的 server_id,同时跑多个实时备份时要避免冲突


mysqlbinlog命令示例

· 解析指定binlog或relaylog所有内容

#将rows的日志内容解码,可用于数据恢复时查找正确的操作
mysqlbinlog -vv --base64-output=decode-rows  master.bin.000003  > /tmp/master.bin.000003.txt


· 解析指定日期范围内的binlog或relaylog

mysqlbinlog -vv --base64-output=decode-rows --start-datetime='2018-02-13 11:37:00'  --stop-datetime='2018-02-15 12:00:00' master-bin.000001 > /tmp/master.bin.000001.txt


· 解析指定position范围内的binlog或relaylog

mysqlbinlog -vv --base64-output=decode-rows --start-position=3052 master-bin.000002    
mysqlbinlog -vv --base64-output=decode-rows --start-position=3052 --stop-position=3849 master-bin.000002


· 显示指定某个库的binlog或relaylog,并且对rows格式的日志解码

mysqlbinlog  -vvv --base64-output=decode-rows  --start-datetime='2018-10-12 14:42:00'  --stop-datetime='2018-10-12 14:45:00' --database=test_database mysql-bin.000008 > /tmp/bin.sql


· gtid模式下导出多个binlog或relaylog文件中的数据

mysqlbinlog --include-gtids='yourgtid:1-100' mysql-bin.00001 mysql-bin.00002 > /tmp/gtid.sql  #假设101的GTID为drop table,就截取到100即可


四、如何正确删除binlog日志

由于二进制日志会随着数据库操作的增多而不断增长,所以需要配置合适的清理策略以及必要时进行手动清理。但是binlog不能直接通过rm去删除,因为这样会导致和binlog索引文件不一致,需要用以下方式进行清除

· reset master命令

清空所有binlog文件,但是会导致从库异常,主从架构下无法使用

· expire_logs_days变量

通过该变量可以指定自动删除日期,如果日志过多,在删除时会有IO过高问题,可能导致性能抖动

set global expire_logs_days=3  #设置只保留3天的binlog
FLUSH BINARY LOGS;    #触发一次binlog的清理


· purge命令:推荐方法,可以快速删除指定binlog

mysql > PURGE MASTER LOGS TO 'mysql-bin.000003'  #删除binlog到指定的文件为止
mysql > PURGE MASTER LOGS BEFORE '2020-12-21 00:00:00'  #删除指定日期之前的文件

五、通过binlog还原数据

binlog 最重要的用途之一是可以实现时间点恢复,通过全备+binlog可以把数据回放到误操作之前。恢复的前提是数据库开启了binlog,并且从备份时间点到误操作时间点的binlog没有缺失,最后是全备文件里记录了对应的 binlog 位点

以今天10:30误操作执行了 drop table students 为例进行操作演示。如果是 delete 或 update 忘了加 where 这类操作,在开启 ROW 格式日志并且 binlog_row_image=FULL 时,可以直接用 binlog2sql、my2sql 解析出反向 SQL闪回。DROP、TRUNCATE 这类 DDL 操作只能通过下面的全备 + binlog 进行恢复。

1、止损操作

一是停止数据库写入,避免恢复期间数据继续变化;二是切换新的日志文件,让误操作在已关闭的文件里

set global super_read_only=1
FLUSH BINARY LOGS;


拷贝相关 binlog 到别的目录,后面的解析和回放都用拷贝

mkdir -p /backup/recover
cp /data/mysql/mysql-bin.0000{12,13,14} /backup/recover/


2、确定全备对应的 binlog 位点

· mysqldump 备份的 binlog 位点查看方法 

备份时如果带了 --single-transaction --master-data=2(8.0.26 起为 --source-data=2),位点记录在备份文件开头

grep -m1 'CHANGE MASTER' /backup/full.sql
# -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000012', MASTER_LOG_POS=154;


· xtrabackup 备份的 binlog 位点查看方法 

cat /backup/full/xtrabackup_binlog_info
# mysql-bin.000012   154


3、找到误操作的位置

解析binlog,找到误操作前后的语句

mysqlbinlog -vv --base64-output=decode-rows --start-datetime='2026-10-27 10:00:00' --stop-datetime='2026-10-27 11:00:00' \
/backup/recover/mysql-bin.000013 > /tmp/view.txt


grep -n -i -B8 -A3 'drop table' /tmp/view.txt
# 找到的内容如下,误操作事务起点是 5756(这个位点也是上一个事务的结束位置),终点是5940(end_log_pos)
# at 5756
#261027 10:30:11 server id 1  end_log_pos 5821 CRC32 0x5b1c0c4e GTID last_committed=20 sequence_number=21 rbr_only=no
SET @@SESSION.GTID_NEXT= '9e70471a-4175-11ed-b3b9-000c29d026c5:2301'/*!*/;
# at 5821
#261027 10:30:11 server id 1  end_log_pos 5940 CRC32 0x86db0796 Query thread_id=8 exec_time=0 error_code=0
use `test1`/*!*/;
drop table students



4、新起实例恢复数据

准备一个临时的全新实例,先恢复全备,然后再回放binlog,验证无误后再迁回生产,不要直接在生产库上操作。恢复全备的步骤这里跳过了,直接从回放binlog开始

# 先导出文件,检查首尾位点后再执行,不要直接管道给 mysql 执行,这里的154就是全量备份里的位点,而5756就是误操作前的位点
mysqlbinlog --start-position=154 --stop-position=5756 --skip-gtids -D \
  /backup/recover/mysql-bin.000012 \
  /backup/recover/mysql-bin.000013 > /tmp/recover.sql

# 确认最后一个事务是 drop 之前的那个
head -30 /tmp/recover.sql
tail -30 /tmp/recover.sql       

# 执行导入
mysql -uroot -p < /tmp/recover.sql


5、验证数据

对照业务上能确认的数据检查,如果没有问题再把数据从临时实例迁回生产。如果是误操作之后生产库又产生了数据,可以在临时实例上继续回放误操作之后的 binlog,跳过误操作本身

mysqlbinlog --start-position=5940 /backup/recover/mysql-bin.000013 /backup/recover/mysql-bin.000014 > /tmp/after.sql


六、实时同步binlog

通过mysqlbinlog可以部署 binlog server,实现实时拉取指定数据库实例上的binlog日志到本地进行统一存储,可应用于数据增量备份、MHA binlog server等场景。

· 自动同步脚本

该脚本可以自动判断需要拉取的正确binlog,无需手动指定同步文件

#!/bin/sh
MYSQL_BIN=/usr/local/mysql/bin/mysql
BACKUP_BIN=/usr/local/mysql/bin/mysqlbinlog
LOCAL_BACKUP_DIR=/data/mysql_binlog_backup/public/binlog
BACKUP_LOG=/data/mysql_binlog_backup/public/backuplog

REMOTE_HOST=10.3.0.221
REMOTE_PORT=3306
REMOTE_USER='binlog_backup'
REMOTE_PASS='binlog_backup'
SERVER_ID='3023401'
FIRST_BINLOG=`${MYSQL_BIN}  -uroot -p123456 -h${REMOTE_HOST} -P${REMOTE_PORT} -e "show master status;" 2> /dev/null | grep "mysql-bin" | awk '{print $1}'`

#create local_backup_dir if necessary
mkdir -p ${LOCAL_BACKUP_DIR}
cd ${LOCAL_BACKUP_DIR}

if [ `ls -A "${LOCAL_BACKUP_DIR}" | wc -l` -eq 0 ];then
  LASTFILE=${FIRST_BINLOG}
else
  LASTFILE=`ls -al ${LOCAL_BACKUP_DIR} | tail -n 1 | awk '{print $9}'`
fi
echo 'Starting binlog backup'
${BACKUP_BIN} -h${REMOTE_HOST} -P${REMOTE_PORT} -u${REMOTE_USER} -p${REMOTE_PASS} --raw --stop-never --read-from-remote-server --stop-never-slave-server-id=${SERVER_ID} ${LASTFILE}  ${LOCAL_BACKUP_DIR}
echo "实例binlog实时同步出错 $(date +%F\ %T)" | mail -s "实例binlog实时同步出错 $(date +%F\ %T)" your_email@domain


· 同步任务巡检脚本

#!/bin/bash
echo > /usr/local/shell/check_binlog_backup.log

#统计应同步实例数量
INSTANCE_COUNT=`ls /data/mysql_backup/ | grep -v archived |grep -v bi1 | wc -l`
#统计应同步实例名
ls /data/mysql_backup/ | grep -v archived |grep -v bi1 > /usr/local/shell/instance_list.txt

#统计当前同步实例数量
BINLOG_COUNT=`ps aux | grep "_binlog_backup" | egrep -v "/usr/local/mysql/bin/mysqlbinlog|--color|grep|check_binlog_backup"  |awk '{print $9,$12}'|wc -l`
#统计应同步实例名
ps aux | grep "_binlog_backup" | egrep -v "/usr/local/mysql/bin/mysqlbinlog|--color|grep|check_binlog_backup" |awk '{print $12}'|awk -F'/' '{print $5}' | awk -F'_' '{print $1}' > /usr/local/shell/binlog_instance_list.txt
#

#判断未同步实例并通知
if [ $BINLOG_COUNT -eq $INSTANCE_COUNT ];then
  echo "`date +%F\ %T` : Binlog Server运行数量与实例数量一致,Binlog备份正常" | mail -s "Binlog备份运行正常" ops@huize.com
else
  echo -e "`date +%F\ %T` : Binlog Server运行数:$BINLOG_COUNT , 应同步实例数:$INSTANCE_COUNT" >> /usr/local/shell/check_binlog_backup.log
  echo "" >> /usr/local/shell/check_binlog_backup.log
  echo -e "当前正常同步实例信息:" >> /usr/local/shell/check_binlog_backup.log
  ps aux | grep "_binlog_backup" | egrep -v "/usr/local/mysql/bin/mysqlbinlog|--color|grep|check_binlog_backup" |awk '{print $1,$9,$12}'>> /usr/local/shell/check_binlog_backup.log
  echo "" >> /usr/local/shell/check_binlog_backup.log
  echo -e "备份异常实例信息:" >> /usr/local/shell/check_binlog_backup.log
  for i in `grep -vwf /usr/local/shell/binlog_instance_list.txt /usr/local/shell/instance_list.txt`
  do
    echo "${i}实例Binlog备份异常,请运行/usr/local/shell/${i}_binlog_backup.sh &" >> /usr/local/shell/check_binlog_backup.log
  done
  cat /usr/local/shell/check_binlog_backup.log | mail -s "Binlog Server运行数量少于实例数,请检查" your_email@domain
fi


文章评论

猜你喜欢

MySQL | Oracle Oracle运维管理——Navicat连接Oracle 19c报ORA-28547错误的解决办法

Oracle运维管理——Navicat连接Oracle 19c报ORA-28547错误的解决办法
Oracle运维管理——Navicat连接Oracle 19c报ORA-28547错误的解决办法
Oracle运维管理——Navicat连接Oracle 19c报ORA-28547错误的解决办法
Oracle运维管理——Navicat连接Oracle 19c报ORA-28547错误的解决办法

一、问题描述由于 Navicat 版权管控非常严格,随时都在给盗版用户发律师函,所以我之前对于Oracle的管理都是使用的开源软件DBeaver顶着,最近公司采购了几套Navicat,我也就顺理成章转...

Oracle教程(8)Oracle 19C 单实例调整为 ADG 架构

MySQL | Oracle Oracle教程(8)Oracle 19C 单实例调整为 ADG 架构

一、ADG架构介绍Active Data Guard 简称ADG,是 Oracle Data Guard 架构下给物理备库(Physical Standby)追加的一个功能,非ADG模式下的物理备库只...

 Oracle教程(7)RMAN 物理备份教程与实战

MySQL | Oracle Oracle教程(7)RMAN 物理备份教程与实战

一、RMAN 介绍RMAN 是 Oracle 自带的物理备份工具,它的核心作用是保护数据库的物理完整性,确保在发生磁盘损坏、误删数据文件、服务器整体故障等灾难场景下能够将数据库恢复到某个可用的状态。R...

Oracle运维管理——修改用户密码过期时间

MySQL | Oracle Oracle运维管理——修改用户密码过期时间

在 Oracle 数据库中,用户的密码有效期通过PROFILE(资源限制配置文件)中的 PASSWORD_LIFE_TIME 参数控制。如果没有为用户单独创建 PROFILE,默认使用一个...

Oracle教程(6)SGA\PGA\REDO核心参数调优

MySQL | Oracle Oracle教程(6)SGA\PGA\REDO核心参数调优

一、Oracle核心参数介绍1、SGASGA(System Global Area)是 Oracle 实例启动时分配的共享内存区域,所有连接到该实例的会话都共用这块内存,是整个数据库实例对外提供服务的...