一切福田,不離方寸,從心而覓,感無不通。

binlog文件分析与mysqlbinlog工具的修改

问题

本文主要带着以下问题进行学习:

1、什么是binlog,有什么作用

2、binlog有哪些格式

3、分析一条典型binlog ,说明从binlog中可以得到哪些信息

4、如何修改mysqlbinlog,使得可以显示最后一条记录

这里主要考虑binlog的使用及相关格式,而不是关注binlog的写入时机。

 

一、简介

binlog又叫二进制日志文件,它会将mysql中所有修改数据库数据的Query以二进制的形式记录到日志文件中,如:create,insert,drop,update等;(对于select操作则不会被记录到binlog里,因为它并没有修改数据库的数据)。binlog一般存储在数据目录下,并且命名为:mysql-bin.***(这个可以在配置文件中修改my.cnf:log-bin=mysql-bin,就是文件名的前缀;mysqld在每个 binlog 名后面添加一个数字扩展名。每次启动服务器或刷新日志时增加文件的大小大于max_binlog_size,一个事务不会被拆分开)。

binlog主要是用于保证数据完整的,如主从备份,通过从binlog文件中读取操作来在salve机上进行同样的操作,保证主从备份,当然不可能每次都从开始的地方redo,所以每条记录都有一个时间截TIMESTAMP。

 

二、简单的使用binlog

show binary logs;    #显示binlog文件

purge binary logsto 'mysql-bin.**'  #删除到**文件

bin/mysqlbinlog binlogfile    #解析binlog文件

 

利用binlog恢复数据:

bin/mysqlbinlog  --start-datetime=’2011-7-7 18:0:0′--stop-datetime=’2011-7-7 20:07:13′ data/mysql-bin.000008 |mysql -u root

 

三、类型

binlog的格式有三种,这也反应了mysql的复制技术:基于SQL语句的复制(statement-based replication, SBR),基于行的复制(row-based replication, RBR),混合模式复制(mixed-based replication, MBR)。相应地,binlog的格式也有三种:STATEMENT,ROW,MIXED。

mysql>showvariables like 'binlog_format'    #查看binlog的格式

 

使用mysqlbinlog解析的binlog:

MIXED(STATEMENT):

# at 193(开始位置)

#110708 10:03:06(时间截) server id(产生该事件的服务id) 1  end_log_pos(日志的结束位置) 280  Query(事件类型)  thread_id=10    exec_time=0     error_code=0

SETTIMESTAMP=1310090586/*!*/;

insert into tvalues(17)

/*!*/;

 

ROW模式:

BEGIN

/*!*/;

# at 174

# at 214

#110708 10:49:22server id 1  end_log_pos 214   Table_map: test.t mapped to number 14

#110708 10:49:22server id 1  end_log_pos 248   Write_rows: table id 14 flags: STMT_END_F

 

BINLOG '

MnAWThMBAAAAKAAAANYAAAAAAA4AAAAAAAEABHRlc3QAAXQAAQMAAQ==

MnAWThcBAAAAIgAAAPgAAAAAAA4AAAAAAAEAAf/+MgAAAA==

'/*!*/;

# at 248

#110708 10:49:22server id 1  end_log_pos 317   Query  thread_id=1     exec_time=0     error_code=0

SETTIMESTAMP=1310093362/*!*/;

COMMIT

 

STATEMENT是基于sql语句级别的binlog,每一条修改数据的sql都会被保存到binlog里;ROW是基于行级别的,他会记录每一行记录的变化,就是将每一行的修改都记录到binlog里面,记录的非常详细,但sql语句并没有在binlog里,在replication里面也不会因为存储过程触发器等造成Master-Slave数据不一致的问题,但是有个致命的缺点日志量比较大.由于要记录每一行的数据变化,当执行update语句后面不加where条件的时候或alter table的时候,产生的日志量是相当的大。MIXED:在默认情况下是statement,但是在某些情况下会切换到row状态,如当一个DML更新一个ndb引擎表,或者是与时间用户相关的函数等。在主从的情况下,在主机上如果是STATEMENT模式,那么binlog就是直接写now(),然而如果这样的话,那么从机进行操作的时间,也执行now(),但明显这两个时间不会是一样的,所以对于这种情况就必须把STATEMENT模式更改为ROW模式,因为ROW模式会直接写值而不是写语句(该案例是错误的,即使是STATEMENT模式也可以使用now()函数,具体原因以后再分析)。同样ROW模式还可以减少从机的相关计算,如在主机中存在统计写入等操作时,从机就可以免掉该计算把值直接写入从机。

 

四、binlog记录

每个binlog的开始都是由4个字节:fe 62 69 6e,组成的魔数(后面三个字节就是bin)。

然后接下来的就是一条记录的内容它包括:Common-Header,这部分不同版本的大小不一样,4.0以上的都是19个字节。在这个之后就是BODY。

Common-Header格式:(单位:字节)

Timestamp(4) Type(1) Server_id(4) Total_size(4) End_log_pos(4) Flag(2)

Timestamp:从1970开始

Type:此log event type如FORMAT_DESCRIPTION_EVENT、QUERY、LOAD_EVENT等,其中每个binlog的第一条记录的类型都是FORMAT_DESCRIPTION_EVENT,它记录了该binlog的相关信息,如版本,这些信息对于后序分析binlog记录是有用的,所以对于任务要读取binlog的内容的工具都必须先读取第一条记录。QUERY包括我们经常操作的如:create,drop,update,insert等。

Server_id:创建这个事件的server id。防止循环主从导致的主机被从写。The master’s server id (is preserved in therelay log; used to prevent from infinite loops in circular replication).

Total_size:该记录的大小,包括common_header及body。

End_log_pos:此下一条记录的开始位置。也是此条记录结束位置的上一个字节。

Flag:标志位。

 

QUERY类型的记录:

QUERY类型的记录除了开始的common-header之外,在body的开头是一个Post-header,然后之后才是真正的body内容。

Query Post-Header:(单位字节)

Thread_id(4) Exec_time(4) Db_len(1) Error_code(2) Status_var_len(2)

Thread_id:is used to distinguish temporary tables that belong to differentclients.

Exec_time:The time from whenthe query started to when it was logged in the binlog, in seconds.QUERY到达到这个binlog事件生成的时间间隔。

Db_len:当前数据库的名称长度。

Error_code:执行出错的错误号。

 

五、使用mysqlbinlog显示最后一条log

1. 使用脚本实现:(该脚本可以显示最后n条)

#!/bin/sh

#Access to the binlog’s last n records

#if don’t set -n, show the last record.

 

NUM="1"

 

function last_logs()

{

#get the total records

local rec_acc=./bin/mysqlbinlog $1 | grep -c '^# at [0-9][0-9]*$'

rec_acc=expr $rec_acc - $NUM

#Skip the first N entries.

./bin/mysqlbinlog -o $rec_acc $1

}

 

if [ $# -lt 1 ] || [ $# -gt 3 ]

then

echo "Usage: mysqlbinlog [-n0-9] filename"

exit

elif [ $# -eq 2 ]

then

if echo $1|grep -q '^-n[0-9][0-9]*$'

then

NUM=echo $1 | cut -d "n" -f 2

last_logs $2

else

echo "Usage:mysqlbinlog [-n0-9] filename"

fi

else

last_logs $1

fi

此本质是首先利用mysqlbinlog binlogfile打印出所有的记录,然后通过正则表达式(^# at [0-9][0-9]*$)判断记录个数M。最后在使用mysqlbinlog –o M-N binlogfile,来显示最后N条。

脚本的使用方法:./last_logs –n3 binlogfile #显示最后三条,不包括第一条FORMAT_DESCRIPTION_EVENT。

 

2.直接修改mysqlbinlog.cc

上面的脚本必须两次扫描binlog文件,这对于大的文件来说消耗可能比较大。修改后的工具,主要利用的是:最后一条记录的end_log_pos刚好为文件的大小。利用这个条件来判断是否需要解析打印。下面为主要的代码:

/*begin:xiangzhong.wxd at:2011-7-9 16:30*/

int is_last_flag(int &argc, char **argv)

{

if (argc == 3 && (!strcmp(argv[1],"-L") || !strcmp(argv[2],"-L")))

{

last_flag = 1;

if (!strcmp(argv[1],"-L"))

{

char * temp = argv[1];

argv[1] = argv[2];

argv[2] = temp;

}

//free(argv[2]);

//argv[2] = '\0';

argc--;

return 0;

}

return -1;

}

/*end:xiangzhong.wxd at:2011-7-9 16:30*/

 

/*begin:xiangzhong.wxd at:2011-7-9 */

struct stat file_buf;

stat(logname, &file_buf);

unsigned long file_size = (unsigned long)file_buf.st_size;

if((ev_type != FORMAT_DESCRIPTION_EVENT) && last_flag && (ev->log_pos != file_size))

goto end;

/*end:xiangzhong.wxd at:2011-7-9 */

其中int is_last_flag函数是判断是否要使用该新功能,如果使用就是在命令行里加个-L,并且只能再使用一个binlogfile作为参数,即与其它的参数如start-position一起使用无效。

使用方法:./bin/mysqlbinlog –L binlogfile

注意:不管是使用上面两种的任意一种,都会打印出#at 4,即第一条记录,所以实质上我们是总共打印了两条记录。(原因见上面的FORMAT_DESCRIPTION_EVENT解释)。

主要涉及的源文件:mysqlbinlog.cc、log_event.cc、log_event.h.

综上可以知道,其实binlog文件就像一个流文件,它每一条记录没有明显的开始及结束标志,它是通过长度来判断一条记录的结束位置,所以在分析binlog文件的时候总是必须从头开始,然后依次一条一条的读取。

from:http://blog.csdn.net/wudongxu/article/details/6598562