转载部分

MySQL-17.主备一致及核心基础binlog

2019-08-16  本文已影响80人  王侦

binlog 在 MySQL 的各种高可用方案上扮演了重要角色。以下内容可以说是所有 MySQL 高可用方案的基础。在这之上演化出了诸如多节点、半同步、MySQL group replication 等相对复杂的方案。

1.主备的基本原理

readonly 设置对超级 (super) 权限用户是无效的,而用于同步更新的线程,就拥有超级权限。

一个update 语句在节点 A 执行,然后同步到节点 B 的完整流程图。


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

2.binlog 的三种格式对比

 CREATE TABLE `test_24` (
 `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 test_24 values(1,1,'2018-11-13');
insert into test_24 values(2,2,'2018-11-12');
insert into test_24 values(3,3,'2018-11-11');
insert into test_24 values(4,4,'2018-11-10');
insert into test_24 values(5,5,'2018-11-09');

2.1 statement格式下执行情况

delete from test_24  /*comment*/ where a>=4 and t_modified<='2018-11-10' limit 1;

查看binlog如下(show binlog events):

| mysql-bin.000001 | 479 | Anonymous_Gtid |         1 |         544 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                 |
| mysql-bin.000001 | 544 | Query          |         1 |         639 | BEGIN                                                                                |
| mysql-bin.000001 | 639 | Query          |         1 |         797 | use `sqllearn`; delete from test_24  where a>=4 and t_modified<='2018-11-10' limit 1 |
| mysql-bin.000001 | 797 | Xid            |         1 |         828 | COMMIT /* xid=71 */   

关于xid:

看一下这条 delete 命令的执行效果图:

mysql> delete from test_24 /*comment*/ where a>=4 and t_modified<='2018-11-10' limit 1;
Query OK, 1 row affected, 1 warning (0.01 sec)

mysql> show warnings;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                         |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted. |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

可以看到,运行这条 delete 命令产生了一个 warning,原因是当前 binlog 设置的是statement 格式,并且语句中有 limit,所以这个命令可能是 unsafe 的。

为什么这么说呢?这是因为 delete 带 limit,很可能会出现主备数据不一致的情况。比如上面这个例子:

由于 statement 格式下,记录到 binlog 里的是语句原文,因此可能会出现这样一种情况:在主库执行这条 SQL 语句的时候,用的是索引 a;而在备库执行这条 SQL 语句的时候,却使用了索引 t_modified。因此,MySQL 认为这样写是有风险的。

2.2 row格式下执行情况

修改/etc/my.cnf 的binlog_format=row并重启systemctl restart mysqld。
查看是否修改成功:

mysql> show variables like 'binlog%';
+--------------------------------------------+--------------+
| Variable_name                              | Value        |
+--------------------------------------------+--------------+
| binlog_cache_size                          | 32768        |
| binlog_checksum                            | CRC32        |
| binlog_direct_non_transactional_updates    | OFF          |
| binlog_error_action                        | ABORT_SERVER |
| binlog_format                              | ROW          |
| binlog_group_commit_sync_delay             | 0            |
| binlog_group_commit_sync_no_delay_count    | 0            |
| binlog_gtid_simple_recovery                | ON           |
| binlog_max_flush_queue_time                | 0            |
| binlog_order_commits                       | ON           |
| binlog_row_image                           | FULL         |
| binlog_rows_query_log_events               | OFF          |
| binlog_stmt_cache_size                     | 32768        |
| binlog_transaction_dependency_history_size | 25000        |
| binlog_transaction_dependency_tracking     | COMMIT_ORDER |
+--------------------------------------------+--------------+

执行delete语句:

| mysql-bin.000002 | 724 | Anonymous_Gtid |         1 |         789 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'  |
| mysql-bin.000002 | 789 | Query          |         1 |         873 | BEGIN                                 |
| mysql-bin.000002 | 873 | Table_map      |         1 |         930 | table_id: 124 (sqllearn.test_24)      |
| mysql-bin.000002 | 930 | Delete_rows    |         1 |         978 | table_id: 124 flags: STMT_END_F       |
| mysql-bin.000002 | 978 | Xid            |         1 |        1009 | COMMIT /* xid=54 */  

与 statement 格式的 binlog 相比,前后的 BEGIN 和 COMMIT 是一样的。但是,row 格式的 binlog 里没有了 SQL 语句的原文,而是替换成了两个 event:Table_map 和Delete_rows。

详细信息需要借助 mysqlbinlog 工具,这个事务的 binlog 是从724这个位置开始的,所以可以用 start-position 参数来指定从这个位置的日志开始解析。

log-bin = /var/log/mysql-bin.log
binlog_format=row
expire-logs-days = 14
max-binlog-size = 500M
server-id = 1

mysqlbinlog -vv /var/log/mysql-bin.000002 --start-position=724;
[root@izbp15dzsx0qokz9qdqojkz log]# mysqlbinlog -vv /var/log/mysql-bin.000002 --start-position=724;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#190816  9:05:01 server id 1  end_log_pos 123 CRC32 0x3c06338a  Start: binlog v 4, server v 5.7.24-log created 190816  9:05:01 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
PQFWXQ8BAAAAdwAAAHsAAAABAAQANS43LjI0LWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAA9AVZdEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
AYozBjw=
'/*!*/;
# at 724
#190816  9:09:00 server id 1  end_log_pos 789 CRC32 0x27deacc9  Anonymous_GTID  last_committed=2    sequence_number=3   rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 789
#190816  9:09:00 server id 1  end_log_pos 873 CRC32 0xf74de073  Query   thread_id=2 exec_time=0 error_code=0
SET TIMESTAMP=1565917740/*!*/;
SET @@session.pseudo_thread_id=2/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 873
#190816  9:09:00 server id 1  end_log_pos 930 CRC32 0x31dd725a  Table_map: `sqllearn`.`test_24` mapped to number 124
# at 930
#190816  9:09:00 server id 1  end_log_pos 978 CRC32 0x1fd11aef  Delete_rows: table id 124 flags: STMT_END_F

BINLOG '
LAJWXRMBAAAAOQAAAKIDAAAAAHwAAAAAAAEACHNxbGxlYXJuAAd0ZXN0XzI0AAMDAxEBAAJact0x
LAJWXSABAAAAMAAAANIDAAAAAHwAAAAAAAEAAgAD//gFAAAABQAAAFvkXYDvGtEf
'/*!*/;
### DELETE FROM `sqllearn`.`test_24`
### WHERE
###   @1=5 /* INT meta=0 nullable=0 is_null=0 */
###   @2=5 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1541692800 /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */
# at 978
#190816  9:09:00 server id 1  end_log_pos 1009 CRC32 0x3f9ff1df     Xid = 54
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

可以看到以下几个信息:

可以看到,当 binlog_format 使用 row 格式的时候,binlog 里面记录了真实删除行的主键id,这样 binlog 传到备库去的时候,就肯定会删除 id=5 的行,不会有主备删除不同行的问题。

3.为什么会有 mixed 格式的 binlog?

mixed 格式可以利用 statment 格式的优点,同时又避免了数据不一致的风险。

因此,如果线上 MySQL 设置的 binlog 格式是 statement 的话,那基本上就可以认为这是一个不合理的设置。至少应该把 binlog 的格式设置为 mixed。

现在越来越多的场景要求把 MySQL 的 binlog 格式设置成 row。这么做的理由有很多,一个可以直接看出来的好处:恢复数据。

其实,由 delete、insert 或者 update 语句导致的数据操作错误,需要恢复到操作之前状态的情况,也时有发生。MariaDB 的Flashback工具就是基于上面介绍的原理来回滚数据的。

3.1 mixed模式下执行情况

修改binlog_format=mixed,重启mysql。

insert into test_24 values(10,10, now());
mysql> show binlog events in 'mysql-bin.000003';
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------+
| Log_name         | Pos  | Event_type     | Server_id | End_log_pos | Info                                                         |
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------+

| mysql-bin.000003 |  804 | Anonymous_Gtid |         1 |         869 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                         |
| mysql-bin.000003 |  869 | Query          |         1 |         964 | BEGIN                                                        |
| mysql-bin.000003 |  964 | Query          |         1 |        1094 | use `sqllearn`; insert into test_24 values(10,10, now())     |
| mysql-bin.000003 | 1094 | Xid            |         1 |        1125 | COMMIT /* xid=57 */  

再用 mysqlbinlog 工具来看看:

mysqlbinlog -vv /var/log/mysql-bin.000003 --start-position=804;
[root@izbp15dzsx0qokz9qdqojkz log]# mysqlbinlog -vv /var/log/mysql-bin.000003 --start-position=804;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#190816  9:35:48 server id 1  end_log_pos 123 CRC32 0x15c79aad  Start: binlog v 4, server v 5.7.24-log created 190816  9:35:48 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
dAhWXQ8BAAAAdwAAAHsAAAABAAQANS43LjI0LWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAB0CFZdEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
Aa2axxU=
'/*!*/;
# at 804
#190816  9:39:59 server id 1  end_log_pos 869 CRC32 0x17cb6017  Anonymous_GTID  last_committed=2    sequence_number=3   rbr_only=no
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 869
#190816  9:39:59 server id 1  end_log_pos 964 CRC32 0x637d51f3  Query   thread_id=3 exec_time=0 error_code=0
SET TIMESTAMP=1565919599/*!*/;
SET @@session.pseudo_thread_id=3/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 964
#190816  9:39:59 server id 1  end_log_pos 1094 CRC32 0x2501beec     Query   thread_id=3 exec_time=0 error_code=0
use `sqllearn`/*!*/;
SET TIMESTAMP=1565919599/*!*/;
insert into test_24 values(10,10, now())
/*!*/;
# at 1094
#190816  9:39:59 server id 1  end_log_pos 1125 CRC32 0xd1e8ff2c     Xid = 57
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

可以看到,原来 binlog 在记录 event 的时候,多记了一条命令:SET TIMESTAMP=1565919599。它用 SET TIMESTAMP 命令约定了接下来的 now() 函数的返回时间。

因此,不论这个 binlog 是 1 分钟之后被备库执行,还是 3 天后用来恢复这个库的备份,这个insert 语句插入的行,值都是固定的。也就是说,通过这条 SET TIMESTAMP 命令,MySQL就确保了主备数据的一致性。

如下方式重放binlog是有风险的:

用 binlog 来恢复数据的标准做法是,用 mysqlbinlog 工具解析出来,然后把解析结果整个发给 MySQL 执行。类似下面的命令:

mysqlbinlog /var/log/mysql-bin.000003 --start-position=2738 --stop-position=2973 | mysql -h127.0.0.1 -P13000 -u$user -p$pwd;

这个命令的意思是,将/var/log/mysql-bin.000003文件里面从第 2738 字节到第 2942 字节中间这段内容解析出来,放到 MySQL 去执行。

4.循环复制问题

binlog 的特性确保了在备库执行相同的 binlog,可以得到与主库相同的状态。实际生产上使用比较多的是双 M 结构:



双 M 结构和 M-S 结构,其实区别只是多了一条线,即:节点A 和 B 之间总是互为主备关系。

双 M 结构还有一个问题需要解决(循环复制问题):

MySQL 在 binlog 中记录了这个命令第一次执行时所在实例的server id。因此,可以用下面的逻辑,来解决两个节点间的循环复制的问题:

按照这个逻辑,如果设置了双 M 结构,日志的执行流就会变成这样:

问题

Ans:

解决方法:

上一篇下一篇

猜你喜欢

热点阅读