mysql的binlog机制

mysql binlog是mysql server层维护的二进制日志。这句话的言外之意是,存储引擎层并不负责写binlog,与innodb引擎中的redo/undo log是完全不同的日志。他们大概有如下的区别:

  1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
  2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
  3. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

怎么开启binlog?

在 mysqld 配置文件中加上如下配置:

1
2
3
4
[mysqld]
log-bin = mysql-bin.log
gtid_mode = ON
enforce-gtid-consistency = ON

如果是windows 平台则在my.ini中配置。

配置完毕后,确认是否成功开启:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> show variables like '%log_bin%';
+---------------------------------+--------------------------------+
| Variable_name | Value |
+---------------------------------+--------------------------------+
| log_bin | ON |
| log_bin_basename | /var/lib/mysql/mysql-bin |
| log_bin_index | /var/lib/mysql/mysql-bin.index |
| log_bin_trust_function_creators | ON |
| log_bin_use_v1_row_events | OFF |
| sql_log_bin | ON |
+---------------------------------+--------------------------------+
6 rows in set (0.00 sec)


查看当前有多少binlog文件

1
2
3
4
5
6
7
8
9
10
mysql> show binary logs;
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql-bin.000001 | 180 | No |
| mysql-bin.000002 | 3032843 | No |
| mysql-bin.000003 | 4020 | No |
| mysql-bin.000004 | 157 | No |
+------------------+-----------+-----------+
4 rows in set (0.01 sec)

实际目录内也能看到binlog文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bash-4.4# pwd
/var/lib/mysql
bash-4.4# ls -alh
total 8.6M
-rw-r----- 1 mysql mysql 192K Jun 14 14:05 '#ib_16384_0.dblwr'
-rw-r----- 1 mysql mysql 8.2M May 21 12:55 '#ib_16384_1.dblwr'
drwxr-x--- 2 mysql mysql 34 Jun 14 14:03 '#innodb_redo'
drwxr-x--- 2 mysql mysql 12 Jun 14 14:03 '#innodb_temp'
drwxrwxr-x 8 mysql 1000 31 Jun 14 14:03 .
drwxr-xr-x 12 root root 13 Mar 8 20:41 ..
-rw-r----- 1 mysql mysql 56 May 21 12:55 auto.cnf
-rw------- 1 mysql mysql 1.7K May 21 12:55 ca-key.pem
-rw-r--r-- 1 mysql mysql 1.1K May 21 12:55 ca.pem
-rw-r--r-- 1 mysql mysql 1.1K May 21 12:55 client-cert.pem
-rw------- 1 mysql mysql 1.7K May 21 12:55 client-key.pem
-rw-r----- 1 mysql mysql 4.0K May 21 16:57 ib_buffer_pool
-rw-r----- 1 mysql mysql 12M Jun 14 14:03 ibdata1
-rw-r----- 1 mysql mysql 12M Jun 14 14:03 ibtmp1
drwxr-x--- 2 mysql mysql 8 May 21 12:55 mysql
-rw-r----- 1 mysql mysql 180 May 21 12:55 mysql-bin.000001
-rw-r----- 1 mysql mysql 2.9M May 21 12:55 mysql-bin.000002
-rw-r----- 1 mysql mysql 4.0K May 21 16:57 mysql-bin.000003
-rw-r----- 1 mysql mysql 157 Jun 14 14:03 mysql-bin.000004
-rw-r----- 1 mysql mysql 76 Jun 14 14:03 mysql-bin.index
-rw-r----- 1 mysql mysql 30M Jun 14 14:03 mysql.ibd
lrwxrwxrwx 1 mysql mysql 27 Jun 14 14:03 mysql.sock -> /var/run/mysqld/mysqld.sock
drwxr-x--- 2 mysql mysql 112 May 21 12:55 performance_schema
-rw------- 1 mysql mysql 1.7K May 21 12:55 private_key.pem
-rw-r--r-- 1 mysql mysql 452 May 21 12:55 public_key.pem
-rw-r--r-- 1 mysql mysql 1.1K May 21 12:55 server-cert.pem
-rw------- 1 mysql mysql 1.7K May 21 12:55 server-key.pem
drwxr-x--- 2 mysql mysql 3 May 21 12:55 sys
-rw-r----- 1 mysql mysql 16M Jun 14 14:05 undo_001
-rw-r----- 1 mysql mysql 16M Jun 14 14:05 undo_002

怎么查看具体event类型偏移?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> show binlog events in 'mysql-bin.000002' from 100 limit 10;
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| mysql-bin.000002 | 126 | Previous_gtids | 1 | 157 | |
| mysql-bin.000002 | 157 | Anonymous_Gtid | 1 | 234 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 234 | Query | 1 | 338 | use `mysql`; TRUNCATE TABLE time_zone /* xid=3 */ |
| mysql-bin.000002 | 338 | Anonymous_Gtid | 1 | 415 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 415 | Query | 1 | 524 | use `mysql`; TRUNCATE TABLE time_zone_name /* xid=4 */ |
| mysql-bin.000002 | 524 | Anonymous_Gtid | 1 | 601 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 601 | Query | 1 | 716 | use `mysql`; TRUNCATE TABLE time_zone_transition /* xid=5 */ |
| mysql-bin.000002 | 716 | Anonymous_Gtid | 1 | 793 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000002 | 793 | Query | 1 | 913 | use `mysql`; TRUNCATE TABLE time_zone_transition_type /* xid=6 */ |
| mysql-bin.000002 | 913 | Anonymous_Gtid | 1 | 993 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+

命令格式如下:

1
2
3
4
SHOW BINLOG EVENTS
[IN 'log_name']
[FROM pos]
[LIMIT [offset,] row_count]

不过这种方式不能看具体变更内容,甚至当某段日志变更内容过大时会直接报错:

1
2
mysql> show binlog events in 'mysql-bin.000002' from 310 limit 1;
ERROR 1220 (HY000): Error when executing command SHOW BINLOG EVENTS: Event too big

mysql内也可以看到同样的结果

1
2
3
4
5
6
7
mysql> select @@server_uuid;
+--------------------------------------+
| @@server_uuid |
+--------------------------------------+
| d0f27d44-f7d6-11ed-b68d-0242ac110002 |
+--------------------------------------+
1 row in set (0.00 sec)

另一部分就是随事务执行生成的id,这两个组合在一起就能全局(整个系统)唯一确定一个事务。

1
2
3
4
5
6
7
8
9
root@tanx 03:05  mysql>show master status;
+-------------------+----------+--------------+------------------+------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+------------------------------------------+
| master-bin.000003 | 1326 | | | f9a34f51-dc9e-11ed-ab5b-000c29751c59:1-6 |
+-------------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)


binlog的格式

基于行的 (ROW)

保存记录被修改的具体数据,不记录sql语句上下文相关信息。比如某个函数的执行不会被记录,只会记录最后实际需要保存的记录。

  • 优点:能非常清晰的记录下每行数据的修改细节。能规避某些函数,触发器无法复制到从节点的问题。
  • 缺点:可能会有大量重复数据,update的批量修改,每行都会产生binlog,会造成日志量大的问题

基于语句的 (STATEMENT)

每一条会修改数据的sql都会记录在binlog中,因此不会记录实际每行保存的数据,对于大更新语句产生的binlog小多了。然而对于会产生结果的函数,比如随机函数,会造成问题。

混合 (MIXED)

在slave日志同步过程中,对于使用now这样的时间函数,MIXED日志格式,会在日志中产生对应的unix_timestamp()*1000的时间字符串,slave在完成同步时,取用的是sqlEvent发生的时间来保证数据的准确性。另外对于一些功能性函数slave能完成相应的数据同步,而对于上面指定的一些类似于UDF函数,导致Slave无法知晓的情况,则会采用ROW格式存储这些Binlog,以保证产生的Binlog可以供Slave完成数据同步。

binlog的写入

在事务开启后,每次执行的写语句生成的binlog都会写入到每个线程自己的binlog cache ,在事务提交的时候将数据写入文件,然后根据 配置文件中 sync_binlog的配置决定是否立即执行一次刷盘。

  • sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;
  • sync_binlog=1 的时候,表示每次提交事务都会执行write并 fsync;
  • sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。
    在对可靠性要求高的场景下,会将sync_binlog配置为1,即每次写入binlog日志都立刻刷入磁盘,但是这样会降低IOPS。其他场景下2-3次事务进行一次刷盘是比较合理的trade off。

此外还有组提交机制,类似TCP用于避免小包问题的 Nagle算法,先发出的包等待多个小包一起触发搭“顺风车”。组提交也是相同的道理,先触发fsync的等等大伙,多凑几个组员才发车。所以一次提交里面组员越多,节约磁盘 IOPS 的效果越好。

binlog与从库

mysql主从架构可以做读写分离轻松扩展系统的读性能,可以主备切换提高系统可用性。

在读写分离架构中,大致类似这样的拓扑
1

备库跟主库之间维持了一个长连接。主库内部有一个线程,专门用于服务备库的这个长连接。一个事务日志同步的完整过程是这样的:

  1. 在备库上通过 change master 命令,设置主库的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
  2. 在备库上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。
  3. 主库校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给备库。
  4. 备库拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
  5. sql_thread 读取中转日志,解析出日志里的命令,并执行。

row binlog的结构

一个典型的insert语句生成的binlog应该是这个样子

1
2
3
4
5
6
| mysql-bin.000004 | 371 | Anonymous_Gtid |         1 |         450 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                   |
| mysql-bin.000004 | 450 | Query | 1 | 529 | BEGIN |
| mysql-bin.000004 | 529 | Table_map | 1 | 588 | table_id: 94 (onecloud.a) |
| mysql-bin.000004 | 588 | Write_rows | 1 | 632 | table_id: 94 flags: STMT_END_F |
| mysql-bin.000004 | 632 | Xid | 1 | 663 | COMMIT /* xid=39 */ |
| mysql-bin.000004 | 663 | Anonymous_Gtid | 1 | 742 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
  1. 这里 SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
    GTID,全称Global transaction identifiers,。MySQL-5.6.2开始支持,MySQL-5.6.10后完善,GTID 分成两部分 GTID=servier_uuid:transacton_id,一部分是mysql 的server UUid,UUID保存在mysql数据目录的auto.cnf文件中。GTID主要用于复杂主从复制拓扑下快速定位某个事物是那个节点执行的。
1
2
3
bash-4.4# cat auto.cnf
[auto]
server-uuid=d0f27d44-f7d6-11ed-b68d-0242ac110002

transacton_id,是一个整数,初始值是 1,每次提交事务的时候分配给这个事务,并加 1。

  1. 这里的Table_map中会指明 tableId对应的database名和table名的对应关系,紧接的WriteRow(insert),UpdateRow(update),DeleteRow(delete)事件中包含的tableId和这个对应,就能知道这些增删改事件执行在那个表上。

但是这里tableId和表名,database名的关系是动态的,必须每次都读取tablemap事件来获取这个绑定关系。

  1. Write_rows

show binlog events 这里需要借助mysqlbinlog工具才能查看具体信息。

一些处理binlog的方案

  1. canal
    首先就是阿里出品的canal,这个是一套完整的同步方案,包括阿里云内部很多产品都依赖这个方案。
    https://github.com/alibaba/canal

  2. mysql-binlog-connector-java
    这个是一套单纯解析binlog的方案,并没有上层可用性,主备切换方面的处理
    https://github.com/osheroff/mysql-binlog-connector-java

  3. flink-cdcflink-cdc
    这套就是配合flink食用的方案了,整体完全屏蔽了binlog的细节
    https://github.com/ververica/flink-cdc-connectors

  • 本文作者: fenix
  • 本文链接: https://fenix0.com/mysql-binlog/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC 许可协议。转载请注明出处!