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_p_w_picpath=minimal 可以減少一部分日誌量,but..假如誤操作就無法反向恢復),每行的變更都會在從查找執行,增加從的執行壓力,假如表未設置主鍵情況不容樂觀
mixed
優點:根據情況自動判斷採用什麼模式,可以減少binlog日誌量
缺點:增加一次判斷也就增加了主機的壓力,而且對不安全的因素不一定能完全正確
在隔離級別爲RC的情況下,採用statement格式不使用不安全的函數也有可能導致主從數據不一致,大家都知道MySQL binlog是按事物的commit順序記錄,在一個事物對數據進行更新,爲及時提交,另外一個事物插入滿足前一個事物更新的條件,因爲RC並沒有間隙鎖機制,就會引起從數據和主數據不一致。
上面很多數據不一致的觀點並未到從庫應徵是否不一致,這方面的文檔網上有很多都介紹過,本文旨在詳細理解binlog各種模式,爲什麼會使數據不一致,各自得優缺點。