binlog文件记录格式statement、row、rixed三种,5.7之前默认为statement模式,到5.7开始默认为row模式。
statement就是语句模式,binlog记录对数据做变动的所有语句,要看binlog记录详细内容可以用mysqlbing查看,现在来对statement模式进行测试:
这里事先创建一个t2表做测试
mysql> show create table t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`date_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> select @@binlog_format;
+-----------------+
| @@binlog_format |
+-----------------+
| STATEMENT
+-----------------+
mysql> insert into t2(name,date_time) values(uuid(),now());
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> select * from t2\G;
*************************** 1. row ***************************
id: 1
name: 7ba7e11e-f6eb-11e5-a293-000c29fa3584
date_time: 2016-03-31 10:51:35
1 row in set (0.00 sec)
现在来查看binlog日志记录
# at 6450
# at 6482
#160331 10:51:35 server id 249 end_log_pos 6482 CRC32 0xa3e29747 Intvar
SET INSERT_ID=1/*!*/;
#160331 10:51:35 server id 249 end_log_pos 6611 CRC32 0x9fc70343 Query thread_id=14 exec_time=0 error_code=0
SET TIMESTAMP=1459392695/*!*/;
insert into t2(name,date_time) values(uuid(),now())
/*!*/;
# at 6611
#160331 10:51:35 server id 249 end_log_pos 6642 CRC32 0xdff2aeb8 Xid = 170
COMMIT/*!*/;
binlog日志是有一个个的event组成,所以一条sql就会产生数行记录数据,由上面红色部分可以看到有个SET INSERT_ID=1和SET TIMESTAMP,经过对时间戳转换正好是我们插入数据的时间,表结构中ID其实是自增的,在这可以看出binlog在执行语句之前对ID和now()函数的值进行了记录保证从数据库数据一致,然而并未记录uuid()函数的值,这就会导致主从不一致。
row模式顾名思义就是对每个变更的行数据进行记录,下面就对它进行测试:
mysql> insert into t2(name,date_time) values(uuid(),now());
Query OK, 1 row affected (0.00 sec)
mysql> select * from t2 order by id desc limit 1;
+----+--------------------------------------+---------------------+
| id | name | date_time |
+----+--------------------------------------+---------------------+
| 7 | 00e3a5b8-f6ed-11e5-86fc-000c29fa3584 | 2016-03-31 11:02:28 |
+----+--------------------------------------+---------------------+
1 row in set (0.00 sec)
产看binlog日志记录,因为是row模式记录的是MySQL内部识别的编码所以需要加-v参数转换为我们能认识的行数据
BEGIN
/*!*/;
# at 337
#160331 11:02:28 server id 249 end_log_pos 385 CRC32 0xff339393 Table_map: `mc`.`t2` mapped to number 109
# at 385
#160331 11:02:28 server id 249 end_log_pos 468 CRC32 0x1560b09c Write_rows: table id 109 flags: STMT_END_F
BINLOG ‘
RJP8VhP5AAAAMAAAAIEBAAAAAG0AAAAAAAEAAm1jAAJ0MgADAw8SA5ABAAaTkzP/
RJP8Vh75AAAAUwAAANQBAAAAAG0AAAAAAAEAAgADB/gHAAAAJAAwMGUzYTViOC1mNmVkLTExZTUt
ODZmYy0wMDBjMjlmYTM1ODSZmP6wnJywYBU=
‘/*!*/;
### INSERT INTO `mc`.`t2`
### SET
### @1=7
### @2=‘00e3a5b8-f6ed-11e5-86fc-000c29fa3584‘
### @3=‘2016-03-31 11:02:28‘
# at 468
#160331 11:02:28 server id 249 end_log_pos 499 CRC32 0x17f01cac Xid = 9
COMMIT/*!*/;
上面绿色部分就是MySQL记录的行记录变更的编码,红色部分就是转化出来的数据,由红色部分看出其实是按我们表结构中字段的顺序定义为了1、2、3的变量进行了直接赋值,这样只要主从数据结构一样就能保证对函数产生的数据的一致性。
插入记录方式了解了,那现在来看下row模式的update、delete语句操作是怎样记录的。
mysql> select * from t2 where date_time=‘2016-03-31 11:09:11‘;
+----+--------------------------------------+---------------------+
| id | name | date_time |
+----+--------------------------------------+---------------------+
| 8 | f0cac692-f6ed-11e5-86fc-000c29fa3584 | 2016-03-31 11:09:11 |
+----+--------------------------------------+---------------------+
1 row in set (0.00 sec)
mysql> update t2 set name=‘a‘ where date_time=‘2016-03-31 11:09:11‘;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
BEGIN
/*!*/;
# at 939
#160331 11:19:03 server id 249 end_log_pos 987 CRC32 0x7ad2c2f9 Table_map: `mc`.`t2` mapped to number 109
# at 987
#160331 11:19:03 server id 249 end_log_pos 1032 CRC32 0x30d090d0 Update_rows: table id 109 flags: STMT_END_F
BINLOG ‘
J5f8VhP5AAAAMAAAANsDAAAAAG0AAAAAAAEAAm1jAAJ0MgADAw8SA5ABAAb5wtJ6
J5f8Vh/5AAAALQAAAAgEAAAAAG0AAAAAAAEAAgADAQL+CAAAAP4BAGHQkNAw
‘/*!*/;
### UPDATE `mc`.`t2`
### WHERE
### @1=8
### SET
### @2=‘a‘
# at 1032
#160331 11:19:03 server id 249 end_log_pos 1063 CRC32 0x9f1f68e7 Xid = 17
COMMIT/*!*/;
绿色部分就是要在从数据库执行的记录,可以看出来是用的where @1=8,按字段顺序@1为我们的主键ID,但是我们不是用date_time做的条件吗,不急........下面再看下
mysql> alter table t2 modify id int,drop primary key;
Query OK, 8 rows affected (0.06 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> select * from t2 where date_time=‘2016-03-31 11:09:11‘;
+------+------+---------------------+
| id | name | date_time |
+------+------+---------------------+
| 8 | a | 2016-03-31 11:09:11 |
+------+------+---------------------+
1 row in set (0.00 sec)
mysql> update t2 set name=‘aaa‘ where date_time=‘2016-03-31 11:09:11‘;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
BEGIN
/*!*/;
# at 1378
#160331 11:23:05 server id 249 end_log_pos 1426 CRC32 0x603c2455 Table_map: `mc`.`t2` mapped to number 110
# at 1426
#160331 11:23:05 server id 249 end_log_pos 1481 CRC32 0x5edd4f26 Update_rows: table id 110 flags: STMT_END_F
BINLOG ‘
GZj8VhP5AAAAMAAAAJIFAAAAAG4AAAAAAAEAAm1jAAJ0MgADAw8SA5ABAAdVJDxg
GZj8Vh/5AAAANwAAAMkFAAAAAG4AAAAAAAEAAgAD/wL4CAAAAAEAYZmY/rJL/gMAYWFhJk/dXg==
‘/*!*/;
### UPDATE `mc`.`t2`
### WHERE
### @1=8
### @2=‘a‘
### @3=‘2016-03-31 11:09:11‘
### SET
### @2=‘aaa‘
# at 1481
#160331 11:23:05 server id 249 end_log_pos 1512 CRC32 0x8f9b33cb Xid = 20
COMMIT/*!*/;
这次我们把主键删掉对记录做的update操作,看出绿色部分where条件对我们所有字段都做了匹配的,delete操作也是一样,由此可以看出在row模式下从服务器做数据更改时是根据主键对数据查找进行更新,如果没有主键就是全部扫描。
现在来对最后一个mixed混合模式进行测试:
mysql> insert into t2(name,date_time) values(uuid(),now());
Query OK, 1 row affected (0.00 sec)
mysql> insert into t2(name,date_time) values(‘a‘,now());
Query OK, 1 row affected (0.00 sec)
mysql> select * from t2 order by id desc limit 2;
+----+--------------------------------------+---------------------+
| id | name | date_time |
+----+--------------------------------------+---------------------+
| 14 | a | 2016-03-31 11:31:11 |
| 13 | f4bb52a8-f6f0-11e5-a33c-000c29fa3584 | 2016-03-31 11:30:46 |
+----+--------------------------------------+---------------------+
2 rows in set (0.00 sec)
BEGIN
/*!*/;
# at 337
#160331 11:30:46 server id 249 end_log_pos 385 CRC32 0xfb29d460 Table_map: `mc`.`t2` mapped to number 109
# at 385
#160331 11:30:46 server id 249 end_log_pos 468 CRC32 0x93c87ab6 Write_rows: table id 109 flags: STMT_END_F
BINLOG ‘
5pn8VhP5AAAAMAAAAIEBAAAAAG0AAAAAAAEAAm1jAAJ0MgADAw8SA5ABAAZg1Cn7
5pn8Vh75AAAAUwAAANQBAAAAAG0AAAAAAAEAAgADB/gNAAAAJABmNGJiNTJhOC1mNmYwLTExZTUt
YTMzYy0wMDBjMjlmYTM1ODSZmP63rrZ6yJM=
‘/*!*/;
### INSERT INTO `mc`.`t2`
### SET
### @1=13
### @2=‘f4bb52a8-f6f0-11e5-a33c-000c29fa3584‘
### @3=‘2016-03-31 11:30:46‘
# at 468
#160331 11:30:46 server id 249 end_log_pos 499 CRC32 0xa8544a3c Xid = 9
COMMIT/*!*/;
# at 499
#160331 11:31:11 server id 249 end_log_pos 564 CRC32 0x04029a22 GTID last_committed=1 sequence_number=2
SET @@SESSION.GTID_NEXT= ‘81570ee3-e47e-11e5-a7cd-000c29fa3584:17‘/*!*/;
# at 564
#160331 11:31:11 server id 249 end_log_pos 647 CRC32 0x0741bd22 Query thread_id=2 exec_time=0 error_code=0
SET TIMESTAMP=1459395071/*!*/;
BEGIN
/*!*/;
# at 647
# at 679
#160331 11:31:11 server id 249 end_log_pos 679 CRC32 0xe4ca7495 Intvar
SET INSERT_ID=14/*!*/;
#160331 11:31:11 server id 249 end_log_pos 805 CRC32 0x01e84a88 Query thread_id=2 exec_time=0 error_code=0
use `mc`/*!*/;
SET TIMESTAMP=1459395071/*!*/;
insert into t2(name,date_time) values(‘a‘,now())
/*!*/;
# at 805
#160331 11:31:11 server id 249 end_log_pos 836 CRC32 0xf9959f9c Xid = 10
COMMIT/*!*/;
有表查询结果限制我们第一次使用uuid插入时id值为13,在binlog中可以看出是使用了row模式记录,第二次未使用uuid函数时就直接记录的语句,由此可以得出mixed模式是当语句中含有uuid这种不安全的函数时就会使用row模式记录。
经过上面反复的的测试可以得出三种模式各自的优缺点:
statement
优点:直接记录操作的语句,binlog产生量小,易于网络传送
缺点:对uuid这种不安全的函数产生的数据不能保证主从一致
row
优点:记录每行的数据,在数据结构一致的情况下能很好的保证主从数据一致性
缺点:因为记录每行数据,产生数据量相对来说比较大,增加网络传输量(binlog_row_image=minimal 可以减少一部分日志量),每行的变更都会在从查找执行,增加从的执行压力,假如表未设置主键情况不容乐观
mixed
优点:根据情况自动判断采用什么模式,可以减少binlog日志量
缺点:增加一次判断也就增加了主机的压力,而且对不安全的因素不一定能完全正确
在隔离级别为RC的情况下,采用statement格式不使用不安全的函数也有可能导致主从数据不一致,大家都知道MySQL binlog是按事物的commit顺序记录,在一个事物对数据进行更新,为及时提交,另外一个事物插入满足前一个事物更新的条件,因为RC并没有间隙锁机制,就会引起从数据和主数据不一致。