binlog主要用于主从复制和数据恢复,本文就一起来看下其都有哪些格式,以及这些模式都有哪些优点和缺点。
my.ini
[mysqld]
...
log_bin=D:\\program_files\\phpstudy\\PHPTutorial\\MySQL\\binlog\\mysql-bin
server-id=1
#设置日志三种格式:STATEMENT、ROW、MIXED 。
binlog_format=ROW
...
查看:
mysql> show variables like '%binlog%';
+-----------------------------------------+----------------------+
| Variable_name | Value |
+-----------------------------------------+----------------------+
| binlog_cache_size | 32768 |
| binlog_direct_non_transactional_updates | OFF |
| binlog_format | ROW |
| binlog_stmt_cache_size | 32768 |
| innodb_locks_unsafe_for_binlog | OFF |
| max_binlog_cache_size | 18446744073709547520 |
| max_binlog_size | 1073741824 |
| max_binlog_stmt_cache_size | 18446744073709547520 |
| sync_binlog | 0 |
+-----------------------------------------+----------------------+
9 rows in set (0.04 sec)
mysql> select version();
+------------+
| version() |
+------------+
| 5.5.53-log |
+------------+
1 row in set (0.05 sec)
win10
CREATE TABLE `binlog_demo` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `a` (`a`),
KEY `t_modified`(`t_modified`)
) ENGINE=InnoDB;
insert into binlog_demo values(1,1,'2020-07-01');
insert into binlog_demo values(2,2,'2020-07-02');
insert into binlog_demo values(3,3,'2020-07-02');
insert into binlog_demo values(4,4,'2020-07-04');
insert into binlog_demo values(5,5,'2020-07-05');
注意数据恢复为初始状态!!!
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set session binlog_format='statement';
Query OK, 0 rows affected (0.04 sec)
mysql> show variables like 'binlog_format';
+---------------+-----------+
| Variable_name | Value |
+---------------+-----------+
| binlog_format | STATEMENT |
+---------------+-----------+
1 row in set (0.00 sec)
mysql>
mysql> show master status;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000002 | 5083 | | |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)
mysql> show binlog events in 'mysql-bin.000002';
+------------------+------+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+------+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mysql-bin.000002 | 4 | Format_desc | 1 | 107 | Server ver: 5.5.53-log, Binlog ver: 4 |
... |
| mysql-bin.000002 | 5056 | Xid | 1 | 5083 | COMMIT /* xid=58 */ |
+------------------+------+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
这里需要记住5083
这个日志位置,后续我们需要用到,另外xid用来关联redo log和binlog,在崩溃恢复时会使用到。
mysql> delete from binlog_demo where a>=4 and t_modified<='2020-07-10' limit 1;
Query OK, 1 row affected, 1 warning (0.07 sec)
C:\Users\Administrator>mysqlbinlog -vv D:\program_files\phpstudy\PHPTutorial\MySQL\binlog\mysql-bin.000002 --start-position=5083
...
delete from binlog_demo where a>=4 and t_modified<='2020-07-10' limit 1
/*!*/;
# at 5301
...
这里只保留了最关键的内容,可以看到记录的正是我们执行的sql语句,但是这样的记录在进行主从同步或者是数据恢复时可能会导致数据不一致的情况,为什么?其中一种可能原因是优化器在不同环境下选择了不同的索引,而不同的索引选择,则会导致limit 1
的结果不同,即最终的结果就是删除了不同的数据,如下可以证明这种现象。
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` varchar(36) DEFAULT NULL,
`d` varchar(36) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
delimiter ;;
create procedure t_1003()
begin
declare i int;
set i=1;
while(i<=100000) do
insert into t values(i,substring(md5(rand()), 10, 30),substring(md5(rand()), 10, 30));
set i=i+1;
end while;
end;;
delimiter ;
begin;
call t_1003();
commit;
mysql> alter table t add index idx_c(`c`);
Query OK, 0 rows affected (0.54 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table t add index idx_d(`d`);
Query OK, 0 rows affected (0.62 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from t\G
*************************** 1. row ***************************
Table: t
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 100282
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: t
Non_unique: 1
Key_name: idx_c
Seq_in_index: 1
Column_name: c
Collation: A
Cardinality: 200
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
*************************** 3. row ***************************
Table: t
Non_unique: 1
Key_name: idx_d
Seq_in_index: 1
Column_name: d
Collation: A
Cardinality: 200
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
3 rows in set (0.00 sec)
mysql> select id from t force index(idx_c) where c>'a' and d>'5' limit 1;
+------+
| id |
+------+
| 4972 |
+------+
1 row in set (0.00 sec)
mysql> select id from t force index(idx_d) where c>'a' and d>'5' limit 1;
+-------+
| id |
+-------+
| 60716 |
+-------+
1 row in set (0.00 sec)
binlog statement模式不仅仅有上述的问题,还有使用诸如now()等和当时环境有关的函数时,也会造成数据的不一致。
注意数据恢复为初始状态!!!
binlog_row_image参数需要设置为full,不然不会记录所有字段信息,只会记录能够表示一行的字段,如ID,从而导致无法使用其进行数据恢复:
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.6.24 |
+-----------+
1 row in set (0.01 sec)
mysql> show variables like '%binlog_row_image%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| binlog_row_image | FULL |
+------------------+-------+
1 row in set (0.01 sec)
mysql> set session binlog_format='row';
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
1 row in set (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000005 | 1779 | | |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)
这里需要记住1779
这个日志位置,后续我们需要用到。
mysql> delete from binlog_demo where a>=4 and t_modified<='2020-07-10' limit 1;
Query OK, 1 row affected, 1 warning (0.07 sec)
C:\Users\Administrator>mysqlbinlog -vv D:\program_files\phpstudy\PHPTutorial\MySQL\binlog\mysql-bin.000005 --start-position=1779
...
### DELETE FROM `test`.`binlog_demo`
### WHERE
### @1=4 /* INT meta=0 nullable=0 is_null=0 */
### @2=4 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593792000 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
# at 1949
...
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
只保留了最关键的内容,可以看到此时记录的是可以具体定位到某行的sql信息,相当于如下的sql语句:
DELETE FROM `test`.`binlog_demo`
WHERE
id=4
a=4
t_modified=1593792000
这样就避免了因为选错索引而导致删除不同数据的问题,也就是说row会将对应的语句翻译为对所有影响行的操作
,如下测试影响多行的情况:
mysql> show master status;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000005 | 1976 | | |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)
mysql> select id from binlog_demo where id<900;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 5 |
+----+
4 rows in set (0.00 sec)
mysql> update binlog_demo set a='aa' where id<900;
Query OK, 4 rows affected, 4 warnings (0.07 sec)
Rows matched: 4 Changed: 4 Warnings: 4
此时生成的binlog如下可以看到是针对多个行的update
:
C:\Users\Administrator>mysqlbinlog -vv D:\program_files\phpstudy\PHPTutorial\MySQL\binlog\mysql-bin.000005 --start-position=1976
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
...
'/*!*/;
### UPDATE `test`.`binlog_demo`
### WHERE
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2=1 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593532800 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593532800 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### UPDATE `test`.`binlog_demo`
### WHERE
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
### @2=2 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593619200 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### SET
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593619200 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### UPDATE `test`.`binlog_demo`
### WHERE
### @1=3 /* INT meta=0 nullable=0 is_null=0 */
### @2=3 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593619200 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### SET
### @1=3 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593619200 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### UPDATE `test`.`binlog_demo`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=5 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593878400 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593878400 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
...
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
那么针对使用了比如now()函数的更新会怎么记录呢?如下测试:
mysql> show master status;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000007 | 318 | | |
+------------------+----------+--------------+------------------+
1 row in set (0.00 sec)
mysql> update binlog_demo set a=9876 where id=5;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0
查看binlog如下:
C:\Users\Administrator>mysqlbinlog -vv D:\program_files\phpstudy\PHPTutorial\MySQL\binlog\mysql-bin.000007 --start-position=318
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
...
'/*!*/;
### UPDATE `test`.`binlog_demo`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=1 is_null=0 */
### @3=1659417981 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=9876 /* INT meta=0 nullable=1 is_null=0 */
### @3=1659417981 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
# at 494
...
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
可以看到将now()转成了秒
:
不仅如此,还set了其他列的值。
由于row格式记录了删除数据完整信息,以及更新数据的更新前和更新后信息,那么当我们需要恢复数据时,完全也可以使用到这些信息,比如如下的binlog记录:
### DELETE FROM `test`.`binlog_demo`
### WHERE
### @1=4 /* INT meta=0 nullable=0 is_null=0 */
### @2=4 /* INT meta=0 nullable=1 is_null=0 */
### @3=1593792000 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
如果是要恢复数据的话,就可以推导出对应的恢复数据sql语句是:
insert into `test`.`binlog_demo`(@1,@2,@3) values(4,4,1593792000);
再比如update对应的binlog:
### UPDATE `test`.`binlog_demo`
### WHERE
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=0 /* INT meta=0 nullable=1 is_null=0 */
### @3=1659417981 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
### SET
### @1=5 /* INT meta=0 nullable=0 is_null=0 */
### @2=9876 /* INT meta=0 nullable=1 is_null=0 */
### @3=1659417981 /* TIMESTAMP meta=0 nullable=0 is_null=0 */
则对应的恢复数据的sql语句就是:
UPDATE `test`.`binlog_demo`
WHERE
@1=5
@2=9876
@3=1659417981
SET
@1=5
@2=0
@3=1659417981
注意数据恢复为初始状态!!!
MySQL server层智能选择到底是使用statement,还是row,但是这种智能并不能保证百分百的正确,毕竟,在优化器智能的优化sql,选择合适的索引时,也会出现错选索引的情况,而选错索引,最多就是数据查询的慢一点,但是如果是智能选错了binlog的模式,可能就会出现数据一致性的问题,这个结果就比较严重了。所以,在生产环境,还是建议使用row。另外还有一个非常有力的理由让我们选择row(美团myflash工具就依赖于此)
,即:恢复数据!!!
参考文章列表:
MySql Binlog statement row mixed 三种模式初探 。