1. 程式人生 > 其它 >pt-archiver歸檔資料導致資料丟失

pt-archiver歸檔資料導致資料丟失

pt-archiver相信大家都非常熟悉了,該工具可以按指定條件匯出資料到檔案,也可以按指定條件清理資料,同時也可以按條件把資料歸檔到另外一個表。最近在工作中無意發現在某種場景下會導致歸檔資料丟失。

下面建立表復現資料丟失的場景:

通過把表t1的資料歸檔到t2,條件是insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59"

 CREATE TABLE `t1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `insert_time` datetime NOT NULL
DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`), KEY `insert_time` (`insert_time`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8

t1,t2表結構相同。其中t1的資料如下:

[root@localhost][yayun]> select * from t1;
+----+---------------------+
| id | insert_time         |
+----+---------------------+
|  1 | 2017-10
-01 14:00:00 | | 2 | 2018-01-01 14:00:00 | | 3 | 2018-01-01 14:00:00 | | 4 | 2018-02-01 14:00:00 | | 5 | 2018-02-01 14:00:00 | | 6 | 2018-03-01 14:00:00 | | 7 | 2018-03-01 14:00:00 | | 8 | 2018-04-01 14:00:00 | | 9 | 2018-04-01 14:00:00 | | 10 | 2018-05-01 14:00:00 | | 11 | 2018-05-01 14:00:00 | | 12 | 2018-06-01 14:00:00 | | 13 | 2018-06-01 14:00:00 | | 14 | 2018-07-01 14:00:00 | | 15 |
2018-07-01 14:00:00 | | 16 | 2018-08-01 14:00:00 | | 17 | 2018-08-01 14:00:00 | | 18 | 2018-09-01 14:00:00 | | 19 | 2018-09-01 14:00:00 | | 20 | 2019-06-01 14:00:00 | +----+---------------------+

可以看見符合insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59"這個條件的有18條資料。

使用命令進行歸檔【pt-archiver 3.3.1】:

pt-archiver  --source h=127.0.0.1,P=3307,u=root,p=xx,D=yayun,t=t1,i=insert_time --dest h=127.0.0.1,P=3307,u=root,p=xx,D=yayun,t=t2  --no-safe-auto-increment --charset=utf8 --where 'insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59"'  --progress 5000  --txn-size 10000 --local --no-delete --bulk-insert --limit=3 --statistics  --why-quit
TIME                ELAPSED   COUNT
2021-06-25T14:24:12       0       0
2021-06-25T14:24:12       0      14
Started at 2021-06-25T14:24:12, ended at 2021-06-25T14:24:12
Source: A=utf8,D=yayun,P=3307,h=127.0.0.1,i=insert_time,p=...,t=t1,u=root
Dest:   A=utf8,D=yayun,P=3307,h=127.0.0.1,i=insert_time,p=...,t=t2,u=root
SELECT 14
INSERT 14
DELETE 0
Action              Count       Time        Pct
bulk_inserting          5     0.0031      14.87
select                  6     0.0025      11.95
commit                  2     0.0005       2.21
print_bulkfile         14    -0.0000      -0.02
other                   0     0.0147      71.00
Exiting because there are no more rows.

可以看見只歸檔了14條資料,這是啥情況。我們直接查看錶看一下t2資料。

[root@localhost][yayun]> select * from t2; 
+----+---------------------+
| id | insert_time         |
+----+---------------------+
|  2 | 2018-01-01 14:00:00 |
|  3 | 2018-01-01 14:00:00 |
|  4 | 2018-02-01 14:00:00 |
|  6 | 2018-03-01 14:00:00 |
|  7 | 2018-03-01 14:00:00 |
|  8 | 2018-04-01 14:00:00 |
| 10 | 2018-05-01 14:00:00 |
| 11 | 2018-05-01 14:00:00 |
| 12 | 2018-06-01 14:00:00 |
| 14 | 2018-07-01 14:00:00 |
| 15 | 2018-07-01 14:00:00 |
| 16 | 2018-08-01 14:00:00 |
| 18 | 2018-09-01 14:00:00 |
| 19 | 2018-09-01 14:00:00 |
+----+---------------------+
14 rows in set (0.00 sec)

可以看見確實只有14條資料。。竟然少了4條資料,這是什麼情況。仔細發現主鍵id等於5,9,13,17的這4條資料丟失了。通過上面也能發現insert_time行資料不是唯一的,有資料重複。那麼為何會發生資料丟呢?我們開啟general_log一窺究竟。

id等於9的條資料的insert_time是2018-04-01 14:00:00。在仔細檢視general_log以後發現了真相。先後這2條SQL我們執行看一下獲取到的資料範圍。

# User@Host: root[root] @ 127.0.0.1:34759 []
# Query_time: 0.000282
SELECT /*!40001 SQL_NO_CACHE */ `id`,`insert_time` FROM `yayun`.`t1` FORCE INDEX(`insert_time`) WHERE (insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59") AND ((`insert_time` > '2018-02-01 14:00:00')) ORDER BY `insert_time` LIMIT 3;
# Time: 062521 14:24:12
# User@Host: root[root] @ 127.0.0.1:34759 []
# Query_time: 0.000265
SELECT /*!40001 SQL_NO_CACHE */ `id`,`insert_time` FROM `yayun`.`t1` FORCE INDEX(`insert_time`) WHERE (insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59") AND ((`insert_time` > '2018-04-01 14:00:00')) ORDER BY `insert_time` LIMIT 3;

SQL1獲取到的資料範圍的主鍵id是6,7,8

SQL2獲取到的資料範圍的主鍵id是10,11,12

剛好把主鍵ID等於9的這條資料漏掉了。。漏掉了。。為啥出現這種情況?仔細看SQL就知道了,因為 insert_time="2018-04-01 14:00:00"的資料有2條

[root@localhost][yayun]> select * from t1 where insert_time='2018-04-01 14:00:00';
+----+---------------------+
| id | insert_time         |
+----+---------------------+
|  8 | 2018-04-01 14:00:00 |
|  9 | 2018-04-01 14:00:00 |
+----+---------------------+
2 rows in set (0.01 sec)

而SQ2的是這樣的:

SELECT /*!40001 SQL_NO_CACHE */ `id`,`insert_time` FROM `yayun`.`t1` FORCE INDEX(`insert_time`) WHERE (insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59") AND ((`insert_time` > '2018-04-01 14:00:00')) ORDER BY `insert_time` LIMIT 3;

條件是 AND ((`insert_time` > '2018-04-01 14:00:00')) 剛好把這條資料遺漏。再回來看看我的歸檔命令:

pt-archiver  --source h=127.0.0.1,P=3307,u=root,p=xx,D=yayun,t=t1,i=insert_time --dest h=127.0.0.1,P=3307,u=root,p=xx,D=yayun,t=t2  --no-safe-auto-increment --charset=utf8 --where 'insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59"'  --progress 5000  --txn-size 10000 --local --no-delete --bulk-insert --limit=3 --statistics  --why-quit

我這裡的--source h=127.0.0.1,P=3307,u=root,p=xx,D=yayun,t=t1,i=insert_time,設定了強制走insert_time索引,所以看見SQL裡面是INDEX(`insert_time`) 。那麼我為什麼要強制走insert_time索引?這是由於不加該引數,工具預設強制走主鍵,這在某些場景下會導致查詢效率異常低下。預設走主鍵是不會導致資料丟失的,因為主鍵是不重複的。反推過來就是如果指定走某個索引,如果這個索引是唯一索引,那麼不會導致資料丟失。反之就是普通的二級索引,就會有可能導致資料丟失。這還和你設定的limit有關係,我上面的測試把limit改成2,就不存在丟失資料。

看看預設走主鍵的SQL是怎樣的。

SELECT /*!40001 SQL_NO_CACHE */ `id`,`insert_time` FROM `yayun`.`t1` FORCE INDEX(`PRIMARY`) WHERE (insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59") AND ((`id` > '7')) ORDER BY `id` LIMIT 3;
SELECT /*!40001 SQL_NO_CACHE */ `id`,`insert_time` FROM `yayun`.`t1` FORCE INDEX(`PRIMARY`) WHERE (insert_time >= "2018-01-01 00:00:00" and insert_time <="2018-12-31 23:59:59") AND ((`id` > '10')) ORDER BY `id` LIMIT 3;

這個在資料少的情況下沒問題,在資料量大的情況下存在效能問題。最佳實踐就是加上最小主鍵id和最大主鍵id,預設走主鍵查詢資料歸檔,這樣即兼顧了效能也不會導致資料丟失。

Auto Copied