您好,登錄后才能下訂單哦!
寫在前面:
????設想一下,你的線上環境使用了主從復制架構,如果不小心執行了,如:drop database db1、drop table tb1,或者說delete,update不加where條件的更新,當問題發生的時候,你是不是希望還有補救的機會?希望Slave主機不要重復Master主機的執行情況?可不可以將這個有害的SQL跳過后,繼續進行復制?答案是:可以的。主從延遲復制就是實現這個功能的
環境準備:
????搭建好主從架構(筆者采用的傳統的復制方式)
????設置好主從延遲變量(如:CHANGE MASTER TO master_delay=180)
????創建好測試表(在下面詳細說明)
如果執行了drop database db1或drop table tb1有害SQL:(drop database和drop table恢復方式相同,只是影響范圍不同而已)
測試表:
CREATE TABLE `edusoho_e`.`t1` (
? `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
? `xname` varchar(20) NOT NULL DEFAULT '',
? `address` char(20) NOT NULL DEFAULT '',
? `sex` tinyint(1) NOT NULL DEFAULT '1',
? `hobby` varchar(30) NOT NULL DEFAULT '',
? `age` tinyint(2) DEFAULT '18',
? PRIMARY KEY (`id`),
? KEY `idx_name` (`xname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `bbs`.`myhash_0` (
? `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
? `c1` int(10) NOT NULL DEFAULT '0',
? `c2` int(10) unsigned DEFAULT NULL,
? `c5` int(10) unsigned NOT NULL DEFAULT '0',
? `c3` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
? `c4` varchar(200) NOT NULL DEFAULT '',
? PRIMARY KEY (`id`),
? KEY `idx_c1` (`c1`),
? KEY `idx_c2` (`c2`)
) ENGINE=InnoDB? DEFAULT CHARSET=utf8
Master主機在正常的變更數據:
INSERT INTO `edusoho_e`.`t1` (`xname`, `address`, `hobby`) VALUES ('lzb', '石家莊', 'MySQL');
INSERT INTO `edusoho_e`.`t1` (`xname`, `address`, `hobby`) VALUES ('Python', '北京', '游戲');
INSERT INTO `bbs`.`myhash_0`(c1,c2,c5,c3,c4) VALUES(2,3,4,NOW(),5);
UPDATE `bbs`.`myhash_0` SET id=2 WHERE id=5;
上面的正常數據變更還沒有執行完,此時Master上突然間執行了某個有害SQL:
DROP DATABASE `bbs`;
發現問題后,馬上停止Slave復制:
mysql> stop slave;
而此時Master主機上其他庫是正常的:
INSERT INTO `edusoho_e`.`t1` (`xname`, `address`, `hobby`) VALUES ('PHP', '深圳', '學習');
分析:
drop語句發生的時候,drop語句之前的數據可能還沒完全同步至Slave主機(這很有可能,尤其是你的數據量大的情況下),所以,需要分析Master主機的binlog,找到drop語句發生的position,使Slave主機同步至drop語句之前,然后跳過drop語句,使Slave繼續同步Master的其他數據
分析Master的binlog:
mysqlbinlog -v --base64-output=decode mysql-bin.000001 | grep -i -C 10 --color 'drop'
###?? @3=2
###?? @4=3
###?? @5=1556612931
###?? @6=''
# at 1053
#190430 16:28:51 server id 2? end_log_pos 1084 CRC32 0x7644c8d2???? Xid = 2068
COMMIT/*!*/;
# at 1084
#190430 16:28:51 server id 2? end_log_pos 1180 CRC32 0x8fd4727e???? Query?? thread_id=11??? exec_time=0 error_code=0
SET TIMESTAMP=1556612931/*!*/;
DROP DATABASE `bbs`
/*!*/;
# at 1180
#190430 16:28:52 server id 2? end_log_pos 1262 CRC32 0xcfe6ddb1???? Query?? thread_id=11??? exec_time=0 error_code=0
SET TIMESTAMP=1556612932/*!*/;
BEGIN
/*!*/;
# at 1262
#190430 16:28:52 server id 2? end_log_pos 1323 CRC32 0x539e7626???? Table_map: `edusoho_e`.`t1` mapped to number 312
# at 1323
#190430 16:28:52 server id 2? end_log_pos 1383 CRC32 0xd286a3c0???? Write_rows: table id 312 flags: STMT_END_F
查看詳細的binlog信息:
mysql> show binlog events in 'mysql-bin.000001' from 1053 limit 10;
跳過有害SQL,繼續進行復制:
1、查看當前執行到的positon
mysql> show slave status\G;
Exec_Master_Log_Pos: 120
2、暫時將同步延遲關閉,使Slave立馬同步Master的數據
mysql> change master to master_delay=0;
3、同步數據至drop語句發生之前
mysql> start slave until master_log_file='mysql-bin.000001',master_log_pos=1084 user='repliter' password='123456';
4、再次查看執行到的position
mysql> show slave status\G;
Exec_Master_Log_Pos: 1084 (drop語句之前的數據已經同步過來了,去Slave相應的數據表驗證下,但是drop語句之后的數據還沒有同步過來)
現在跳過有害SQL之后,繼續Master的數據復制:
mysql> stop slave;
mysql> change master to master_log_pos=1262 [master_delay=180];(可加可不加)
mysql> start slave user='repliter' password='123456';
mysql> show slave status\G;
Exec_Master_Log_Pos: 1414?
去驗證drop語句之后的數據過去了沒
就這樣有害SQL被跳過了,保留了一份Slave還未被刪除的數據備份,之后是做主從切換,還是把數據導回到Master就根據你自己的情況了
筆者這里演示下,將Slave的同名數據庫導回到Master的過程(如果數據量很大的話,建議做主從切換,因為導回的成本也許比切換的成本大的多,自行評估,個人建議)
1、首先,將Slave的庫導成SQL文件,這里為bbs_new.sql(一定要有包含創建庫的語句,要是忘記了,你就自己創建)
2、給導入SQL文件更改權限
chown mysql.mysql bbs_new.sql
3、mysql -uroot -p bbs -e "SET @@session.sql_log_bin=0;source /root/bbs_new.sql;"?? (一定要加sql_log_bin=0,不然你懂得)
至此,drop database語句,成功跳過!
如果執行了delete from table(不加where條件)或truncate table有害SQL:
Master主機在正常的變更數據:
INSERT INTO `edusoho_e`.`t1` (`xname`, `address`, `hobby`,age) VALUES ('Games', '海南', '就是玩',28);?? ?
UPDATE `edusoho_e`.`t1` SET xname='劉備' WHERE id=5;
上面的正常數據變更還沒有執行完,此時Master上突然間執行了某個有害SQL:
DELETE FROM `edusoho_e`.`t1`;
因為是delete全表數據,表結構仍在,依據會有新數據產生和變更:
INSERT INTO `edusoho_e`.`t1` (`xname`, `address`, `hobby`) VALUES ('曹操', '魏國', '三國');
UPDATE `edusoho_e`.`t1` SET age=40 WHERE xname='曹操';
DELETE FROM `edusoho_e`.`t1` WHERE xname='lzb';?? #刪除一條不存在的數據(不會產生日志)
UPDATE `edusoho_e`.`t1` SET xname='孫權' WHERE xname='Python';??? #更新一條不存在的數據(不會產生日志)
發現問題后,馬上停止Slave復制:
mysql> stop slave;
分析:
此時,Master主機上其他數據庫、表也是不受影響的。delete全表語句發生的時候,delete全表語句之前的數據可能還沒完全同步至Slave主機(這很有可能,尤其是你的數據量大的情況下),所以,需要分析Master主機的binlog,找到delete全表語句發生的position,使Slave主機同步至delete全表語句之前,然后跳過delete全表語句,使Slave繼續同步Master的其他數據
在Master主機上根據時間分析binlog(因為筆者使用的是ROW格式,所以,會有很多條的delete語句,在delete全表語句之前,極有可能有正常的delete語句,你將分不清哪個才是該跳過的有害SQL,所以,問題發生的時候,一定要盡可能的知道發生的時間,對binlog進行分析才能更加有效)
mysqlbinlog -v --base64-output=decode mysql-bin.000001 | grep -i -C 10 --color 'delete from'(筆者自己測試,生產環境一定要加時間篩選)
COMMIT/*!*/;
# at 622
#190505? 8:34:35 server id 2? end_log_pos 704 CRC32 0xd237cd1f? Query thread_id=3 exec_time=0 error_code=0
SET TIMESTAMP=1557016475/*!*/;
BEGIN
/*!*/;
# at 704
#190505? 8:34:35 server id 2? end_log_pos 765 CRC32 0x9335b52a? Table_map: `edusoho_e`.`t1` mapped to number 281
# at 765
#190505? 8:34:35 server id 2? end_log_pos 913 CRC32 0xb6da4487? Delete_rows: table id 281 flags: STMT_END_F
### DELETE FROM `edusoho_e`.`t1`
### WHERE
###?? @1=1
###?? @2='lzb'
###?? @3='石家莊'
###?? @4=1
###?? @5='MySQL'
###?? @6=18
### DELETE FROM `edusoho_e`.`t1`
### WHERE
###?? @1=3
###?? @2='Python'
###?? @3='北京'
###?? @4=1
###?? @5='游戲'
###?? @6=18
### DELETE FROM `edusoho_e`.`t1`
### WHERE
###?? @1=5
###?? @2='劉備'
###?? @3='深圳'
###?? @4=1
###?? @5='學習'
###?? @6=18
### DELETE FROM `edusoho_e`.`t1`
### WHERE
###?? @1=7
###?? @2='Games'
###?? @3='海南'
###?? @4=1
###?? @5='就是玩'
###?? @6=28
# at 913
#190505? 8:34:35 server id 2? end_log_pos 944 CRC32 0x215741c7? Xid = 605
COMMIT/*!*/;
查看詳細的binlog信息:
mysql> show binlog events in 'mysql-bin.000001';(線上的binlog很大,一定要加from做篩選)
+------------------+------+-------------+-----------+-------------+---------------------------------------+
| Log_name???????? | Pos? | Event_type? | Server_id | End_log_pos | Info????????????????????????????????? |
+------------------+------+-------------+-----------+-------------+---------------------------------------+
| mysql-bin.000001 |??? 4 | Format_desc |???????? 2 |???????? 120 | Server ver: 5.6.16-log, Binlog ver: 4 |
| mysql-bin.000001 |? 120 | Query?????? |???????? 2 |???????? 202 | BEGIN???????????????????????????????? |
| mysql-bin.000001 |? 202 | Table_map?? |???????? 2 |???????? 263 | table_id: 281 (edusoho_e.t1)????????? |
| mysql-bin.000001 |? 263 | Write_rows? |???????? 2 |???????? 328 | table_id: 281 flags: STMT_END_F?????? |
| mysql-bin.000001 |? 328 | Xid???????? |???????? 2 |???????? 359 | COMMIT /* xid=587 */????????????????? |
| mysql-bin.000001 |? 359 | Query?????? |???????? 2 |???????? 441 | BEGIN???????????????????????????????? |
| mysql-bin.000001 |? 441 | Table_map?? |???????? 2 |???????? 502 | table_id: 281 (edusoho_e.t1)????????? |
| mysql-bin.000001 |? 502 | Update_rows |???????? 2 |???????? 591 | table_id: 281 flags: STMT_END_F?????? |
| mysql-bin.000001 |? 591 | Xid???????? |???????? 2 |???????? 622 | COMMIT /* xid=596 */????????????????? |
| mysql-bin.000001 |? 622 | Query?????? |???????? 2 |???????? 704 | BEGIN???????????????????????????????? |
| mysql-bin.000001 |? 704 | Table_map?? |???????? 2 |???????? 765 | table_id: 281 (edusoho_e.t1)????????? |
| mysql-bin.000001 |? 765 | Delete_rows |???????? 2 |???????? 913 | table_id: 281 flags: STMT_END_F?????? |
| mysql-bin.000001 |? 913 | Xid???????? |???????? 2 |???????? 944 | COMMIT /* xid=605 */????????????????? |
| mysql-bin.000001 |? 944 | Query?????? |???????? 2 |??????? 1026 | BEGIN???????????????????????????????? |
| mysql-bin.000001 | 1026 | Table_map?? |???????? 2 |??????? 1087 | table_id: 281 (edusoho_e.t1)????????? |
| mysql-bin.000001 | 1087 | Write_rows? |???????? 2 |??????? 1150 | table_id: 281 flags: STMT_END_F?????? |
| mysql-bin.000001 | 1150 | Xid???????? |???????? 2 |??????? 1181 | COMMIT /* xid=614 */????????????????? |
| mysql-bin.000001 | 1181 | Query?????? |???????? 2 |??????? 1263 | BEGIN???????????????????????????????? |
| mysql-bin.000001 | 1263 | Table_map?? |???????? 2 |??????? 1324 | table_id: 281 (edusoho_e.t1)????????? |
| mysql-bin.000001 | 1324 | Update_rows |???????? 2 |??????? 1416 | table_id: 281 flags: STMT_END_F?????? |
| mysql-bin.000001 | 1416 | Xid???????? |???????? 2 |??????? 1447 | COMMIT /* xid=623 */????????????????? |
+------------------+------+-------------+-----------+-------------+---------------------------------------+
跳過有害SQL,繼續進行復制:
1、暫時將同步延遲關閉,使Slave立馬同步Master的數據
mysql> change master to master_delay=0;
2、同步數據至drop語句發生之前
mysql> start slave until master_log_file='mysql-bin.000001',master_log_pos=622 user='repliter' password='123456';
3、再次查看執行到的position
mysql> show slave status\G;
Exec_Master_Log_Pos: 622? (delete全表語句之前的數據已經同步過來了,去Slave相應的數據表驗證下,但是delete全表語句之后的數據還沒有同步過來)
現在跳過有害SQL之后,繼續Master的數據復制:
mysql> stop slave;
mysql> change master to master_log_pos=1026 [master_delay=180];(可加可不加)
mysql> start slave user='repliter' password='123456';
mysql> show slave status\G;
Exec_Master_Log_Pos: 1447
去驗證delete全表語句之后的數據過去了沒
就這樣有害SQL被跳過了,保留了一份Slave還未被刪除的數據備份,之后是做主從切換,還是把數據導回到Master就根據你自己的情況了
筆者這里演示下,將Slave的同名數據庫導回到Master的過程(如果數據量很大的話,建議做主從切換,因為導回的成本也許比切換的成本大的多)
如果你的數據表數據量較小,可以在上述until語句執行完之后,將Master的數據表加上全局寫鎖,然后將Slave主機上的數據同步過去,因為數據表小,對業務影響也不會太大
將Master主機上的數據表加上寫鎖:(如果你知道你的數據表pk值不會被插入,而是依靠自增生成,那么你可能需要先將表清空,導入舊數據后,再導入新數據,這樣才能保證數據的一致性)
LOCK TABLE `edusoho_e`.`t1` WRITE;
然后再Slave主機上把until語句之前的數據導出來:
INSERT INTO `t1` VALUES (1,'lzb','石家莊',1,'MySQL',18),(3,'Python','北京',1,'游戲',18),(5,'劉備','深圳',1,'學習',18),(7,'Games','海南',1,'就是玩',28);
切換到Master:
mysql> show master status\G;
*************************** 1. row ***************************
???????????? File: mysql-bin.000001
???????? Position: 1447
SET @@session.sql_log_bin=0;? (一定要做,具體原因應該都知道)
把數據導回去(如果是SQL文件,則執行source導入)
INSERT INTO `t1` VALUES (1,'lzb','石家莊',1,'MySQL',18),(3,'Python','北京',1,'游戲',18),(5,'劉備','深圳',1,'學習',18),(7,'Games','海南',1,'就是玩',28);
mysql> show master status\G;
*************************** 1. row ***************************
???????????? File: mysql-bin.000001
???????? Position: 1447
釋放鎖:
UNLOCK TABLES;
至此,delete 全表語句,成功跳過!
如果執行了update table(不加限制條件)有害SQL:
測試表:
CREATE TABLE `orders` (
? `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
? `xname` varchar(10) NOT NULL DEFAULT '' COMMENT '用戶名稱',
? `chongzhi` int(11) NOT NULL DEFAULT '0' COMMENT '充值金額',
? `amount` int(11) NOT NULL DEFAULT '0' COMMENT '剩余金額',
? PRIMARY KEY (`id`)
) ENGINE=InnoDB? DEFAULT CHARSET=utf8 COMMENT='用戶訂單表'
正常的Master數據變更:
INSERT INTO `edusoho_e`.`orders`(xname,chongzhi,amount) VALUES('鄭千次',10000,100);
INSERT INTO `edusoho_e`.`orders`(xname,chongzhi,amount) VALUES('孫悟空',200,600);
INSERT INTO `edusoho_e`.`orders`(xname,chongzhi,amount) VALUES('柯南',666,888);
INSERT INTO `edusoho_e`.`orders`(xname,chongzhi,amount) VALUES('我',1,0);
UPDATE `edusoho_e`.`orders` SET chongzhi=chongzhi+1000 WHERE id=3;
UPDATE `edusoho_e`.`orders` SET amount=amount-200 WHERE id=5;
UPDATE `edusoho_e`.`orders` SET amount=amount-100;
此時,線上有一張用戶訂單表,執行了一條不加WHERE條件的UPDATE語句:
UPDATE `edusoho_e`.`orders` SET chongzhi=chongzhi+1000;
執行過后,用戶很高興,因為沒充錢,白得了1000塊;但是你的老板,絕對恨不得揍死你,為了不被揍,所以,你得趕緊恢復你的數據
發現問題后,馬上停止Slave復制:
mysql> stop slave;
分析:
發現問題之后,馬上對Master加寫鎖,因為此時數據雖然存在,但是已經是錯誤的數據;然后確定有害SQL的position,然后跳過它,繼續Master的復制
LOCK TABLE `edusoho_e`.`orders` WRITE;
在Master主機上根據時間分析binlog(因為筆者使用的是ROW格式,所以,會有很多條的update語句,如果在update不加限制條件語句之前,也有正常的update語句,你將分不清哪個才是該跳過的有害SQL,所以,問題發生的時候,一定要盡可能的知道發生的時間,對binlog進行分析才能更加有效)
分析Master日志,找到執行的問題SQL發生的position:
mysqlbinlog -v --base64-output=decode mysql-bin.000001 | grep -i -C 10 --color 'update'
COMMIT/*!*/;
# at 3554
#190505 10:04:20 server id 2? end_log_pos 3636 CRC32 0xd95ad4e9?? Query thread_id=3 exec_time=0 error_code=0
SET TIMESTAMP=1557021860/*!*/;
BEGIN
/*!*/;
# at 3636
#190505 10:04:20 server id 2? end_log_pos 3695 CRC32 0xa8208a81?? Table_map: `edusoho_e`.`orders` mapped to number 282
# at 3695
#190505 10:04:20 server id 2? end_log_pos 3897 CRC32 0xdb6fe2c1?? Update_rows: table id 282 flags: STMT_END_F
### UPDATE `edusoho_e`.`orders`
### WHERE
###?? @1=1
###?? @2='鄭千次'
###?? @3=10000
###?? @4=100
### SET
###?? @1=1
###?? @2='鄭千次'
###?? @3=11000
###?? @4=100
### UPDATE `edusoho_e`.`orders`
### WHERE
###?? @1=3
###?? @2='孫悟空'
###?? @3=1200
###?? @4=600
### SET
###?? @1=3
###?? @2='孫悟空'
###?? @3=2200
###?? @4=600
### UPDATE `edusoho_e`.`orders`
### WHERE
###?? @1=5
###?? @2='柯南'
###?? @3=666
###?? @4=688
### SET
###?? @1=5
###?? @2='柯南'
###?? @3=1666
###?? @4=688
### UPDATE `edusoho_e`.`orders`
### WHERE
###?? @1=7
###?? @2='我'
###?? @3=1
###?? @4=0
### SET
###?? @1=7
###?? @2='我'
###?? @3=1001
###?? @4=0
mysql> show binlog events in 'mysql-bin.000001' from 3554;
+------------------+------+-------------+-----------+-------------+----------------------------------+
| Log_name???????? | Pos? | Event_type? | Server_id | End_log_pos | Info???????????????????????????? |
+------------------+------+-------------+-----------+-------------+----------------------------------+
| mysql-bin.000001 | 3554 | Query?????? |???????? 2 |??????? 3636 | BEGIN??????????????????????????? |
| mysql-bin.000001 | 3636 | Table_map?? |???????? 2 |??????? 3695 | table_id: 282 (edusoho_e.orders) |
| mysql-bin.000001 | 3695 | Update_rows |???????? 2 |??????? 3897 | table_id: 282 flags: STMT_END_F? |
| mysql-bin.000001 | 3897 | Xid???????? |???????? 2 |??????? 3928 | COMMIT /* xid=893 */???????????? |
+------------------+------+-------------+-----------+-------------+----------------------------------+
4 rows in set (0.00 sec)
跳過有害SQL,繼續進行復制:
1、和問題發生人員溝通,確認update是怎樣執行的
在Master上執行:
SET @@session.sql_log_bin=0;(一定要加,不然你懂得)
UPDATE `edusoho_e`.`orders` SET chongzhi=chongzhi-1000;
此時,Master和SLave的數據都恢復了一致,只要Slave跳過有害的UPDATE語句就可以了
2、跳過有害SQL,繼續復制
mysql> change master to master_log_pos=3928 [master_delay=180];(可加可不加)
3、start slave user='repliter' password='123456';
4、釋放表的寫鎖
UNLOCK TABLES;
至此,update全表語句,成功跳過!
題外:
本文是筆者根據自己的理解,設想線上可能發生的部分問題后,針對性的利用 master_delay 參數特性,進行數據恢復做的測試,并沒有經過任何的實戰檢測。一方面,僅為廣大同行做個參考;另一方面,記錄筆者自己的心得和針對問題解決的思路做個總結,當問題真正發生的時候,有個方向可以進行參考,而不至于手忙腳亂,不知所措,所以,對其中有誤之處和理解不到位的地方,望請下方留言指正,不勝感激!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。