1. 程式人生 > 其它 >基於statement或mixed格式的主從複製真的安全嗎?

基於statement或mixed格式的主從複製真的安全嗎?

MySQL中的二進位制日誌格式從5.7開始預設為ROW格式,但仍有許多使用者出於各種原因堅持使用STATEMENT或MIXED格式。在某些情況下,修改老的應用程式上運行了多年的東西都有一種猶豫。但在其他情況下,可能存在嚴重的阻礙,最常見的是在設計不良的模式中缺少主鍵,這將導致副本出現嚴重的效能問題。

作為支援工程師,我仍然可以看到不少客戶使用STATEMENT或MIXED格式,即使他們已經使用 MySQL 8.0。在許多情況下這沒問題,但最近我不得不處理一個非常討厭的情況,發現不使用ROW格式會導致副本默默地丟失資料更新,而不會引發任何複製錯誤! 這是一些非常罕見的邊緣用例嗎? 一點也不! 讓我在下面演示一個非常簡單的測試用例,以說明最終陷入如此糟糕的情況是多麼容易。

測試一

主庫

mysql> select @@binlog_format,@@system_time_zone;
+-----------------+--------------------+
| @@binlog_format | @@system_time_zone |
+-----------------+--------------------+
| STATEMENT       | BST                |
+-----------------+--------------------+
1 row in set (0.00 sec)
​
mysql> CREATE TABLE `test1` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   `d` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    ->   `a` varchar(30) NOT NULL,
    ->   `name` varchar(25) DEFAULT NULL,
    ->   PRIMARY KEY (`a`),
    ->   UNIQUE KEY `id` (`id`)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected, 1 warning (0.02 sec)
​
mysql> insert into test1 values (null,now(),"test1",0);
Query OK, 1 row affected (0.00 sec)
​
mysql> insert into test1 values (null,now(),"test2",0);
Query OK, 1 row affected (0.01 sec)
​
mysql> insert into test1 values (null,now(),"test3",0);
Query OK, 1 row affected (0.01 sec)
​
mysql> select * from test1;
+----+---------------------+-------+------+
| id | d                   | a     | name |
+----+---------------------+-------+------+
|  1 | 2022-05-22 10:13:37 | test1 | 0    |
|  2 | 2022-05-22 10:13:37 | test2 | 0    |
|  3 | 2022-05-22 10:13:38 | test3 | 0    |
+----+---------------------+-------+------+
3 rows in set (0.00 sec)

從庫

mysql> select @@system_time_zone;
+--------------------+
| @@system_time_zone |
+--------------------+
| UTC                |
+--------------------+
1 row in set (0.00 sec)
​
mysql> select * from db1.test1;
+----+---------------------+-------+------+
| id | d                   | a     | name |
+----+---------------------+-------+------+
|  1 | 2022-05-22 09:13:37 | test1 | 0    |
|  2 | 2022-05-22 09:13:37 | test2 | 0    |
|  3 | 2022-05-22 09:13:38 | test3 | 0    |
+----+---------------------+-------+------+
3 rows in set (0.00 sec)

主庫update

mysql> UPDATE test1 SET name = 'foobar', d = CURRENT_TIMESTAMP WHERE a = 'test1' AND d = '2022-05-22 10:13:37';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
​
mysql> select * from test1;
+----+---------------------+-------+--------+
| id | d                   | a     | name   |
+----+---------------------+-------+--------+
|  1 | 2022-05-22 10:16:15 | test1 | foobar |
|  2 | 2022-05-22 10:13:37 | test2 | 0      |
|  3 | 2022-05-22 10:13:38 | test3 | 0      |
+----+---------------------+-------+--------+
3 rows in set (0.00 sec)

從庫檢視

mysql> select * from db1.test1;
+----+---------------------+-------+------+
| id | d                   | a     | name |
+----+---------------------+-------+------+
|  1 | 2022-05-22 09:13:37 | test1 | 0    |
|  2 | 2022-05-22 09:13:37 | test2 | 0    |
|  3 | 2022-05-22 09:13:38 | test3 | 0    |
+----+---------------------+-------+------+
3 rows in set (0.00 sec)
​
mysql> pager egrep "Running|SQL_Error"
PAGER set to 'egrep "Running|SQL_Error"'
​
mysql > show replica status\G
           Replica_IO_Running: Yes
          Replica_SQL_Running: Yes
               Last_SQL_Error: 
    Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
     Last_SQL_Error_Timestamp: 
1 row in set (0.00 sec)

 

測試二

另一個測試,使用UTC_TIME()函式

主庫

mysql> select * from test1 WHERE TIME(d) > DATE_SUB(UTC_TIME(), INTERVAL 11 HOUR) AND id=3;
+----+---------------------+-------+------+
| id | d                   | a     | name |
+----+---------------------+-------+------+
|  3 | 2022-05-22 10:13:38 | test3 | 0    |
+----+---------------------+-------+------+
1 row in set (0.00 sec)

從庫

mysql> select * from test1 WHERE TIME(d) > DATE_SUB(UTC_TIME(), INTERVAL 11 HOUR) AND id=3;
Empty set (0.00 sec)

測試三

主庫

mysql> update test1 set name="bar" WHERE TIME(d) > DATE_SUB(UTC_TIME(), INTERVAL 11 HOUR) AND id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
​
mysql> select * from test1 where id=3;
+----+---------------------+-------+------+
| id | d                   | a     | name |
+----+---------------------+-------+------+
|  3 | 2022-05-22 22:12:15 | test3 | bar  |
+----+---------------------+-------+------+
1 row in set (0.01 sec)

從庫

mysql> select * from test1 where id=3;
+----+---------------------+-------+------+
| id | d                   | a     | name |
+----+---------------------+-------+------+
|  3 | 2022-05-22 09:13:38 | test3 | 0    |
+----+---------------------+-------+------+
1 row in set (0.01 sec)
​
mysql > show replica status\G
           Replica_IO_Running: Yes
          Replica_SQL_Running: Yes
               Last_SQL_Error: 
    Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
     Last_SQL_Error_Timestamp: 
1 row in set (0.00 sec)

複製忽略了update,且沒有報任何複製錯誤。預計這種特殊情況會在地理分佈的資料庫環境中經常發生。

因為這些函式對主從複製是不安全的,兩個安全操作沒有被執行:

·當使用statement格式的時候,在錯誤日誌中沒有列印警告資訊

·使用mixed格式的時候,複製事件不是以RBR格式記錄的,而是使用和查詢一樣的格式

這個問題很危險,具體可以看bug提交:https://bugs.mysql.com/bug.php?id=107293

 

總結

基於ROW的複製已經成為MySQL中的標準並且是最可靠的一種。它也是唯一一種允許虛擬同步複製解決方案(如 Percona XtraDB Cluster/Galera 和 MySQL Group Replication)的解決方案。

同時,STATEMENT甚至MIXED格式都可能導致資料一致性問題,可能會長時間未被檢測到,從而導致最終發生複製錯誤時很難調查。

最好早點切換成row格式。