阿里云
阿里云多端小程序中小企业获客首选
发表主题 回复主题
  • 14251阅读
  • 4回复

[数据库]初探 MySQL 的 Binlog

级别: 论坛版主
发帖
9349
云币
14165
— 本帖被 ivmmff 设置为精华(2015-08-11) —
这样一篇神器的文章来https://xcoder.in/2015/08/10/mysql-binlog-try/ :=y5713  
感谢博客作者! g>h/|b w4  
T='uqKW\  
u ,3B[  
Y]L4,V  
    花瓣网的搜索架构需要重构,尤其是在索引建立或者更新层面。 P 9yMf~  
  目前的一个架构导致的结果就是时间越久,数据本体与搜索引擎索引中的数据越不同步,相差甚大。 3!l>\#q6  
  新的一个架构打算从MySQL 的Binlog 中读取数据更新、删除、新增等历史记录,并把相应信息提取出来丢到队列中慢慢去同步。 6Yn>9llo}=  
  所以我就在这里小小去了解一下Binlog。 uX1;  
fYjmG[4  
准备工作 U\Vg&"P  
P4Pc;8T@!  
什么是Binlog l SdA7  
iY/2 `R  
  MySQL Server 有四种类型的日志——Error Log、General Query Log、Binary Log 和Slow Query Log。 QUW`Yc  
  第一个是错误日志,记录mysqld 的一些错误。第二个是一般查询日志,记录mysqld 正在做的事情,比如客户端的连接和断开、来自客户端每条Sql Statement 记录信息;如果你想准确知道客户端到底传了什么瞎[哔哔] 玩意儿给服务端,这个日志就非常管用了,不过它非常影响性能。第四个是慢查询日志,记录一些查询比较慢的SQL 语句——这种日志非常常用,主要是给开发者调优用的。 |gI>Sp%Fu  
  剩下的第三种就是Binlog了,包含了一些事件,这些事件描述了数据库的改动,如建表、数据改动等,也包括一些潜在改动,比如DELETE FROM ran WHERE bing = luan,然而一条数据都没被删掉的这种情况。除非使用Row-based logging,否则会包含所有改动数据的SQL Statement。 sMGo1pG(  
  那么Binlog 就有了两个重要的用途——复制和恢复。比如主从表的复制,和备份恢复什么的。 ) n O ^Ay  
}y6@YfV${  
启用Binlog FzX ;~CA  
C zJ-tEO  
  通常情况MySQL 是默认关闭Binlog 的,所以你得配置一下以启用它。 D/Ki^E  
  启用的过程就是修改配置文件my.cnf了。 ybiTWM  
  至于my.cnf位置请自行寻找。例如通过OSX的brew安装的mysql默认配置目录通常在
  1. /usr/local/Cellar/mysql/$VERSION/support-files/my-default.cnf
pF8$83S  
  这个时候需要将它拷贝到/etc/my.cnf下面。
详见< StackOverflow - MySQL 'my.cnf' location? >。
Y:;_R=M  
  紧接着配置log-bin和log-bin-index的值,如果没有则自行加上去。 Gw3+TvwU+Q  
  1. log-bin= master-bin
  2. log-bin-index= master-bin.index
_">F]ptI;  
 这里的log-bin是指以后生成各Binlog文件的前缀,比如上述使用master-bin,那么文件就将会是master-bin.000001、master-bin.000002等。而这里的log-bin-index则指binlog index文件的名称,这里我们设置为master-bin.index。 n#x_da-m]  
 如果上述工作做完之后重启MySQL 服务,你可以进入你的MySQL CLI 验证一下是否真的启用了。 C/Q20  
  1. mysql -u $USERNAME ...
然后在终端里面输入下面一句SQL 语句: Ob]\t/:%P  
  1. SHOW  VARIABLES  LIKE  '%log_bin%' ;
 如果结果里面出来这样类似的话就表示成功了: HNS^:X R  
  1. +---------------------------------+--------------- ------------------------+
  2. | Variable_name | Value |
  3. +------------------- --------------+----------------------------------- ----+
  4. | log _bin | ON |
  5. | log _bin_basename | /usr/ local /var/mysql/master-bin |
  6. | log _bin_index | /usr/ local /var/mysql/master-bin.index |
  7. | log _bin_trust_ function _creators | OFF |
  8. | log _bin_use_v1_row_events | OFF |
  9. | sql_ log _bin | ON |
  10. +------------------------------- --+---------------------------------------+
  11. 6 rows in  set ( 0.00 sec )
%>Gb]dv?  
更多的一些相关配置可以参考这篇《MySQL的binary log初探》。 ~|Vq v{  
k_]'?f7Z  
随便玩玩 ):PN0.H8  
^WB[uFt-  
Eh8GqFEM  
  然后你就可以随便去执行一些数据变动的SQL 语句了。当你执行了一堆语句之后就可以看到你的Binlog 里面有内容了。 > Hv9Xz  
  如上表所示,log_bin_basename的值是/usr/local/var/mysql/master-bin就是Binlog的基础文件名 ​​了。 P_3IFHe  
  那我们进去看,比如我的这边就有这么几个文件: N9M}H#  
^5]9B<i[Y  
,JdBVt  
    很容易发现,里面有master-bin.index和master-bin.000001两个文件,这两个文件在上文中有提到过了。 M _< |n  
  我们打开那个master-bin.index文件,会发现这个索引文件就是一个普通的文本文件,然后列举了各binlog的文件名 ​​。而master-bin.000001文件就是一堆乱码了——毕竟人家是二进制文件。 L;opQ~g  
B0b|+5WhR  
结构解析 !O"2)RU1  
<~uzHg%Y  
索引文件 Q.$8>)  
v];YC6shx  
  索引文件就是上文中的master-bin.index文件,是一个普通的文本文件,以换行为间隔,一行一个文件名 ​​。比如它可能是: ]~]TZb  
  1. master-bin .000001
  2. master-bin .000002
  3. master-bin .000003
ppIXS(  
  然后对应的每行文件就是一个Binlog 实体文件了。 %TFsk  
S*G^U1Sc+  
>oq\`E  
]mtiIu[  
文件头 ltNI+G  
OSO MFt  
  文件头由一个四字节Magic Number,其值为1852400382,在内存中就是"\xfe\x62\x69\x6e",参考MySQL源码的log_event.h,也就是'\0xfe' 'b' 'i' 'n'。 >6Pe~J5,:  
  与平常二进制一样,通常都有一个Magic Number 进行文件识别,如果Magic Number 不吻合上述的值那么这个文件就不是一个正常的Binlog。 %TI3Eb  
;v]C8}L^  
事件 :7X4VHw/  
g0B-<>E  
  在文件头之后,跟随的是一个一个事件依次排列。每个事件都由一个事件头和事件体组成。 Hx+r9w  
  事件头里面的内容包含了这个事件的类型(如新增、删除等)、事件执行时间以及是哪个服务器执行的事件等信息。 s2,6aW C  
  第一个事件是一个事件描述符,描述了这个Binlog文件格式的版本。接下去的一堆事件将会按照第一个事件描述符所描述的结构版本进行解读。最后一个事件是一个衔接事件,指定了下一个Binlog文件名 ​​——有点类似于链表里面的next指针。 #Db^*  
  根据《High-Level Binary Log Structure and Contents》所述,不同版本的Binlog格式不一定一样,所以也没有一个定性。在我写这篇文章的时候,目前有三种版本的格式。
  • v1,用于MySQL 3.2.3
  • v3,用于MySQL 4.0.2 以及4.1.0
  • v4,用于MySQL 5.0 以及更高版本
" l|`LjP5M  
  实际上还有一个v2 版本,不过只在早期4.0.x 的MySQL 版本中使用过,但是v2 已经过于陈旧并且不再被MySQL 官方支持了。
4PD5i  
引用
通常我们现在用的MySQL 都是在5.0 以上的了,所以就略过v1 ~ v3 版本的Binlog,如果需要了解v1 ~ v3 版本的Binlog 可以自行前往上述的《High-level…》文章查看。
a:*N0  
FUSe!f  
事件头 k+[oYd  
uy2~<)  
  一个事件头有19 字节,依次排列为四字节的时间戳、一字节的当前事件类型、四字节的服务端ID、四字节的当前事件长度描述、四字节的下个事件位置(方便跳转)以及两字节的标识。 =C$"e4%Be  
  用ASCII Diagram 表示如下: TXYO{  
  1. +---------+---------+---------+------------+------ -------+-------+
  2. | timestamp | type code | server_id | event_length | next_position | flags   |
  3. | 4 bytes   | 1 byte    | 4 bytes   | 4 bytes      | 4 bytes       | 2 bytes |
  4. + ---------+---------+---------+------------+------- ------+-------+
也可以字节编造一个结构体来解读这个头: j9c:SP5  
  1. struct BinlogEventHeader
  2. {
  3.     int    timestamp;
  4.     char   type_code;
  5.     int    server_id;
  6.     int    event_length;
  7.     int    next_position;
  8.     char   flags[ 2 ];
  9. };
F}mt *UcMG  
K+ /wJ9^B  
zK~_e\m  
引用
如果你要直接用这个结构体来读取数据的话,需要加点手脚。 ?i0u)< H  
因为默认情况下GCC或者G++编译器会对结构体进行字节对齐,这样读进来的数据就不对了,因为Binlog并不是对齐的。为了统一我们需要取消这个结构体的字节对齐,一个方法是使用#pragma pack(n),一个方法是使用__attribute__((__packed__)),还有一种情况是在编译器编译的时候强制把所有的结构体对其取消,即在编译的时候使用fpack-struct参数,如:
Il\{m?Y  
N68]r 3/K  
  1. $ g++ temp.cpp -oa -fpack-struct= 1
 根据上述的结构我们可以明确得到各变量在结构体里面的偏移量,所以在MySQL源码里面(libbinlogevents/include/binlog_event.h)有下面几个常量以快速标记偏移: 5hEA/G  
  1. # define EVENT_TYPE_OFFSET     4
  2. # define SERVER_ID_OFFSET      5
  3. # define EVENT_LEN_OFFSET      9
  4. # define LOG_POS_OFFSET        13
  5. # define FLAGS_OFFSET          17
x2fqfrr_]  
  而具体有哪些事件则在libbinlogevents/include/binlog_event.h#L245里面被定义。如有个FORMAT_DESCRIPTION_EVENT事件的type_code是15、UPDATE_ROWS_EVENT的type_code是31。 jHT^I as  
  还有那个next_position,在v4版本中代表从Binlog一开始到下一个事件开始的偏移量,比如到第一个事件的next_position就是4,因为文件头有一个字节的长度。然后接下去对于事件n和事件n + 1来说,他们有这样的关系:
  1. next_position(n + 1) = next_position(n) + event_length(n)
bO$KV"*!  
  关于flags暂时不需要了解太多,如果真的想了解的话可以看看MySQL的相关官方文档 *eXs7"H  
VXk[p  
事件体 )/!HI0TU  
DJdhOLx  
  事实上在Binlog事件中应该是有三个部分组成,header、post-header和payload,不过通常情况下我们把post-header和payload都归结为事件体,实际上这个post-header里面放的是一些定长的数据,只不过有时候我们不需要特别地关心。想要深入了解可以去查看MySQL的官方文档。 d\Up6F  
  所以实际上一个真正的事件体由两部分组成,用ASCII Diagram 表示就像这样: C+!=C{@7di  
  1. +=====================================+
  2. | event | fixed part (post-header) |
  3. | data    +----------------------------+
  4. | | variable part (payload) |
  5. +========= ============================+
~~WY?I-  
 而这个post-header对于不同类型的事件来说长度是不一样的,同种类型来说是一样的,而这个长度的预先规定将会在一个“格式描述事件”中定好。 oJ5n*[qUI  
,^1 #Uz8  
格式描述事件 {pXX%>  
G?~Yw'R^8  
  在上文我们有提到过,在Magic Number 之后跟着的是一个格式描述事件(Format Description Event),其实这只是在v4 版本中的称呼,在以前的版本里面叫起始事件(Start Event)。 6&i])iH  
  在v4 版本中这个事件的结构如下面的ASCII Diagram 所示。 !+Cc^{  
  1. +=====================================+
  2. | event   | timestamp 0 : 4     |
  3. | header + ----------------------------+
  4. |         | type_code 4 : 1     | = FORMAT_DESCRIPTION_EVENT = 15
  5. |         +----------------------------+
  6. |         | server_id 5 : 4     |
  7. |         +----------------------------+
  8. |         | event_length 9 : 4     | >= 91
  9. |         +----------------------------+
  10. |         | next_position 13 : 4     |
  11. |         +----------------------------+
  12. |         | flags 17 : 2     |
  13. +=====================================+
  14. | event   | binlog_version 19 : 2     | = 4
  15. | data +----------------------------+
  16. |         | server_version 21 : 50    |
  17. |         +----------------------------+
  18. |         | create_timestamp 71 : 4     |
  19. |         +----------------------------+
  20. |         | header_length 75 : 1     |
  21. |         +----------------------------+
  22. |         | post-header 76 : n     | = array of n bytes, one byte per event
  23. |         | lengths for all             |    type that the server knows about
  24. |         | event types                 |
  25. +=====================================+
$pPc}M[h  
Xeja\5zB  
 这个事件的type_code是15,然后event_length是大于等于91的值的,这个主要取决于所有事件类型数。 \{*`-P v  
  因为从第76字节开始后面的二进制就代表一个字节类型的数组了,一个字节代表一个事件类型的post-header长度,即每个事件类型固定数据的长度。 DV?c%z`YO  
  那么按照上述的一些线索来看,我们能非常快地写出一个简单的解读Binlog 格式描述事件的代码。 _W Hi<,-  
引用
如上文所述,如果需要正常解读Binlog文件的话,下面的代码编译时候需要加上-fpack-struct=1这个参数。
lGT[6S\as  
  1. # include  <cstdio>
  2. # include  <cstdlib>
  3. struct BinlogEventHeader
  4. {
  5.     int   timestamp;
  6.     unsigned  char type_code;
  7.     int   server_id;
  8.     int   event_length;
  9.     int   next_position;
  10.     short flags;
  11. };
  12. int  main ()
  13. {
  14.     FILE* fp = fopen( "/usr/local/var/mysql/master-bin.000001" , "rb" );
  15.     int magic_number;
  16.     fread(&magic_number, 4 , 1 , fp);
  17.     printf ( "%d - %s\n" , magic_number, ( char *)(&magic_number));
  18.     struct BinlogEventHeader format_description_event_header;
  19.     fread(&format_description_event_header, 19 , 1 , fp);
  20.     printf ( "BinlogEventHeader\n{\n" );
  21.     printf ( " timestamp: %d\n" , format_description_event_header.timestamp);
  22.     printf ( " type_code: %d\n" , format_description_event_header.type_code);
  23.     printf ( " server_id: % d\n" , format_description_event_header.server_id);
  24.     printf ( " event_length: %d\n" , format_description_event_header.event_length);
  25.     printf ( " next_position: %d\n" , format_description_event_header.next_position);
  26.     printf ( " flags[]: % d\n}\n" , format_description_event_header.flags);
  27.     short binlog_version;
  28.     fread(&binlog_version, 2 , 1 , fp);
  29.     printf ( "binlog_version: %d\n" , binlog_version);
  30.     char server_version[ 51 ];
  31.     fread(server_version, 50 , 1 , fp);
  32.     server_version[ 50 ] = '\0' ;
  33.     printf ( "server_version: %s\n" , server_version);
  34.     
  35.     int create_timestamp;
  36.     fread(&create_timestamp, 4 , 1 , fp);
  37.     printf ( "create_timestamp: %d\n" , create_timestamp);
  38.     char header_length;
  39.     fread(&header_length, 1 , 1 , fp);
  40.     printf ( "header_length: %d\n" , header_length);
  41.     int type_count = format_description_event_header.event_length - 76 ;
  42.     unsigned  char post_header_length[type_count];
  43.     fread(post_header_length, 1 , type_count, fp);
  44.     for ( int i = 0 ; i < type_count; i++)
  45.     {
  46.         printf ( " - type %d: % d\n" , i + 1 , post_header_length[i]);
  47.     }
  48.     return  0 ;
  49. }
  这个时候你得到的结果有可能就是这样的了: ZBK)rmhMx  
RK&RMN8@  
  1. 1852400382 -  binpz  
  2. BinlogEventHeader
  3. {
  4.     timestamp: 1439186734
  5.      type_code: 15
  6.      server_id: 1
  7.      event_length: 116
  8.      next_position: 120
  9.      flags[]: 1
  10. }
  11. binlog_version: 4
  12. server_version: 5.6 .24 - log
  13. create_timestamp: 1439186734
  14. header_length: 19
  15.    - type 1 : 56
  16.    - type 2 : 13
  17.    - type 3 : 0
  18.    - type 4 : 8
  19.    - type 5 : 0
  20.    - type 6 : 18
  21.    - ...
USgO`l\}4  
  一共会输出40种类型(从1到40),如官方文档所说,这个数组从START_EVENT_V3事件开始(type_code是1)。 ,{ 0&NX  
C@XnV=J  
跳转事件 1'&HmBfcb  
66W J=? JV  
bjwl21;{  
  跳转事件即ROTATE_EVENT,其type_code是4,其post-header长度为8。 o<s~455m/  
  当一个Binlog文件大小已经差不多要分割了,它就会在末尾被写入一个ROTATE_EVENT ——用于指出这个Binlog的下一个文件。 ?` ebi|6  
  它的post-header是8字节的一个东西,内容通常就是一个整数4,用于表示下一个Binlog文件中的第一个事件起始偏移量。我们从上文就能得出在一般情况下这个数字只可能是四,就偏移了一个魔法数字。当然我们讲的是在v4这个Binlog版本下的情况。 LChwHkRHJI  
  然后在payload位置是一个字符串,即下一个Binlog文件的文件名 ​​。 -idbR[1{?  
s}gdi  
各种不同的事件体 _!Z}HCk  
'./qBJ  
BG-nf1K(  
  由于篇幅原因这里就不详细举例其它普通的不同事件体了,具体的详解在MySQL文档中一样有介绍,用到什么类型的事件体就可以自己去查询。 {9F}2 SJ  
;;^?vS  
小结 <pT1p4T<  
0x,4H30t(  
  本文大概介绍了Binlog 的一些情况,以及Binlog 的内部二进制解析结构。方便大家造轮子用——不然老用别人的轮子,只知其然而不知其所以然多没劲。 1X&scVw  
  好了要下班了,就写到这里过吧。 >Zm|R|{BE  
ZQ\O| n8  
参考 KAy uv  
  1. MySQL's binary log结构简介,目测原文在TaobaoDBA(已无法访问)
  2. MySQL Binlog 的介绍
  3. MySQL 的binary log 初探
  4. High-Level Binary Log Structure and Contents and related official documents
  5. #pragma pack vs -fpack-struct for Intel C
YpSK |(  
Y$hLsM\%  
这样一篇神器的文章来自:https://xcoder.in/2015/08/10/mysql-binlog-try/感谢博客作者!
级别: 小白
发帖
16
云币
21
只看该作者 沙发  发表于: 2015-08-14
Re初探MySQL的Binlog
感谢楼主分享,赞!
级别: 论坛版主
发帖
9349
云币
14165
只看该作者 板凳  发表于: 2015-08-14
回 1楼(行·人) 的帖子
你的号好多
级别: 小白
发帖
16
云币
21
只看该作者 地板  发表于: 2015-08-14
回2楼ivmmff的帖子
你认识我?我是看有个什么阿里官方的取名行人,我也来模仿一下 增加发帖权威性!!!
级别: 论坛版主
发帖
9349
云币
14165
只看该作者 4楼 发表于: 2015-08-14
回 3楼(行·人) 的帖子
  原来是这样。我还以为是小号呢
发表主题 回复主题
« 返回列表上一主题下一主题

限100 字节
批量上传需要先选择文件,再选择上传
 
验证问题: 阿里云官网域名是什么? 正确答案:www.aliyun.com
上一个 下一个
      ×
      全新阿里云开发者社区, 去探索开发者的新世界吧!
      一站式的体验,更多的精彩!
      通过下面领域大门,一起探索新的技术世界吧~ (点击图标进入)