使用MySQL的過程,經常會遇到一個問題,比如說某張”log”表,用於保存某種記錄,隨着時間的不斷的累積數據,但是只有最新的一段時間的數據是有用的;這個時候會遇到性能和容量的瓶頸,需要將表中的歷史數據進行歸檔。下面來說說幾種常見的數據歸檔方式。
一、使用分區,再利用分區交換技術能夠很好地把指定分區中的數據移動到指定表中,這個需要在項目之處就進行此操作。
二、利用存儲過程和事件來定期進行數據的導出刪除操作。
1 、創建一個新表,表結構和索引與舊表一模一樣
create table table_new like table_old;
2 、新建存儲過程,查詢30天的數據並歸檔進新數據庫,然后把30天前的舊數據從舊表里刪除
delimiter $
create procedure sp()
begin
insert into tb_new select * from table_old where rectime < NOW() - INTERVAL 30 DAY;
delete from db_smc.table_old where rectime < NOW() - INTERVAL 30 DAY;
end
3、創建EVENT,每天晚上凌晨00:00定時執行上面的存儲過程
create event if not exists event_temp
on schedule every 1 day
on completion preserve
do call sp();
備注:第一次執行存儲過程的時候因為歷史數據過大, 可能發生意外讓該次執行沒有成功。重新執行時會遇到報錯ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,應急解決方案如下:
1、執行show full processlist;查看所有MySQL線程。
2、執行SELECT * FROM information_schema.INNODB_TRX\G; 查看是否有錯誤線程,線程id在show full processlist;的結果中狀態為sleep。
3、kill進程id。
另外寫存儲過程的時候可以控制事務的大小,比如說可以根據時間字段每次歸檔一天或者更小時間段的數據,這樣就不會有大事務的問題,里面還可以加入日志表,每次歸檔操作的行為都寫入日志表,以后查起來也一目了然。
三、使用percona-toolkit的pt-archiver工具來進行歷史數據歸檔,支持刪除和不刪除元數據的選擇。
pt-archiver使用的場景:
1、清理線上過期數據。
2、清理過期數據,並把數據歸檔到本地歸檔表中,或者遠端歸檔服務器。
3、兩張表之間的數據不完全相同,希望合並。此時加上–ignore或–replace選項,可以輕松實現。
4、導出線上數據,到線下數據作處理。
其它作用:
1、用於清理過期數據purge
$ pt-archiver --source h=10.99.73.9,P=3306,u=mha,p=123456,D=sbtest,t=sbtest \
--no-check-charset \
--where 'id<50000' \
--purge \
--limit=2 \
--statistics
注意: --source后的DSN之間不能空格出現,否則會出錯。 --where條件的值,有字符串的,要用引號括起來。 --limit表示,每組一次刪除多少條數據(注意:如果數據比較多時,也可以設置大一些,減少循環次數),最終的清理操作,還是通過Where pK=xx來處理的。
2、用於把數據導出文件,不用刪除原表中數據
$ pt-archiver --source h=10.99.73.9,P=3306,u=mha,p=123456,D=sbtest,t=sbtest \
--dest h=10.99.73.9,P=3306,u=mha,p=123456,D=sbtest,t=sbtest_201703 \
--no-check-charset \
--where 'id>50000' \
--progress 5000 \
--no-delete \
--file "/tmp/pt-archiver.dat" \
--limit=10000 \
--txn-size=10000 \
--statistics
參數說明:
--statistics:結束的時候給出統計信息:開始的時間點,結束的時間點,查詢的行數,歸檔的行數,刪除的行數,以及各個階段消耗的總的時間和比例,便於以此進行優化。
--where:給出表中要處理的數據的查詢條件。
--progress:每處理progress指定的行數后,就打印一次信息。
--no-delete:表示不刪除原來的數據,注意:如果不指定此參數,所有處理完成后,都會清理原表中的數據。
--limit:表示每次事務刪除多少條數據,默認1條(注意:如果數據比較多時,也可以設置大一些,減少循環次數)。
--txn-size:每個事務提交的數據行數(包括讀寫操作),批量提交,增加該值可以提升歸檔性能。
--file:數據存放的文件,最好指定絕對路徑,文件名可以靈活地組合(另外,我測試過寫文件與不寫文件速度幾乎差不多,原本以為不寫文件速度會快)。
%d Day of the month, numeric (01..31)
%H Hour (00..23)
%i Minutes, numeric (00..59)
%m Month, numeric (01..12)
%s Seconds (00..59)
%Y Year, numeric, four digits
%D Database name
%t Table name
注意字符集問題
如果你的數據庫字符集是utf8的話,需要在運行pt-archive的機器上,在/etc/my.cnf文件中的[client]下面添加default-character-set = utf8,否則導出的文件內容中文會亂碼,我就被這個問題坑了。
測試歸檔
首先壓測100萬數據。
mysql> create database sbtest charset utf8;
$sysbench /usr/share/sysbench/oltp_read_only.lua --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-socket=/var/lib/mysql/mysql.sock --mysql-db=sbtest --db-driver=mysql --tables=1 --table-size=100000 --report-interval=10 --threads=128 --time=120 prepare
mysql> select count(1) from sbtest1;
+----------+
| count(1) |
+----------+
| 100000 |
+----------+
1 row in set (0.04 sec)
創建一張歸檔表,表結構跟原表一樣。
mysql> show create table sbtest1\G
*************************** 1. row ***************************
Table: sbtest
Create Table: CREATE TABLE `sbtest` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> CREATE TABLE `sbtest_2018` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8;
開始進行歸檔表操作,並且刪除原有表數據記錄(如果不刪除原表數據需要加 --no-delete)
$pt-archiver --source h=localhost,u=root,D=sbtest,t=sbtest1 --dest h=localhost,u=root,D=sbtest,t=sbtest1_2018 --no-check-charset --where 'id>25000' --progress 5000 --file "/tmp/pt-archiver.dat" --limit=10000 --txn-size=10000 --statistics
TIME ELAPSED COUNT
2018-02-25T16:03:32 0 0
2018-02-25T16:03:34 2 5000
2018-02-25T16:03:37 4 10000
2018-02-25T16:03:39 6 15000
2018-02-25T16:03:41 8 20000
2018-02-25T16:03:43 10 25000
2018-02-25T16:03:43 10 25000
Started at 2018-02-25T16:03:32, ended at 2018-02-25T16:03:43
Source: D=sbtest,h=localhost,t=sbtest1,u=root
Dest: D=sbtest,h=localhost,t=sbtest1_2018,u=root
SELECT 25000
INSERT 25000
DELETE 25000
Action Count Time Pct
inserting 25000 4.1222 37.47
deleting 25000 3.7808 34.37
print_file 25000 0.1496 1.36
select 4 0.0590 0.54
commit 6 0.0397 0.36
other 0 2.8491 25.90
mysql> select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
| 25001 |
+----------+
1 row in set (0.01 sec)
mysql> select count(*) from sbtest1_2018;
+----------+
| count(*) |
+----------+
| 25000 |
+----------+
1 row in set (0.01 sec)
可以看到數據已經歸檔完畢,並且刪除數據完畢。生產環境中一般都是根據日期來歸檔數據,比如常見需求保留30天即可,此時where可以這么寫“CreateTime <= date_add(now(), interval -30 day)”。
除了用pt-archiver歸檔之外,還有一個特別大的用處,我給稱之為“無鎖導入數據”。在數據歸檔中還有一種需求(我經常遇到),為了不影響業務在某些情況下會對一些日志表或者其他表做歸檔,當表特別大時第一次處理此表就不太好處理,並且就算把表數據刪除了后而表文件還是無法縮小。這個時候就可以用MySQL的神奇rename命令對表進行重命名,當然是業務允許情況下,如 rename table Deal to Deal_201801, Deal_2018 to Deal;,此操作是一個原子操作且特別快。做完這個動作之后,一般還會有一個需求就是把原表中某一段時間的數據導入到新的表中,可能是業務跑批需要或者后台查詢需要。導數據該怎么弄呢?很自然可能想到使用 insert into Deal select * from Deal_201801 where ...導入操作,但是不好意思,這個動作是鎖表的,在導入數據的時候無法對新表進行操作,會導致業務異常。
如果換其他方式呢?寫python或shell把數據讀出來寫入到文件,然后再從文件讀出循環插入到新表,這當然是可以的。但當數據特別多時,也需要寫多線程了。其實這個時候就可以借助pt-archiver進行數據導入了,從老的表讀出來然后直接插入到新的表,他的原理與我們上面說的方式類似,但是它更友好,且更快。
四、使用union或union all來進行結果合並
當歷史數據進行歸檔后,這個時候就有需求了。當需要查看歷史數據和現有表數據時有沒有什么好的方法呢?其實可以使用union或union all來進行多表結果合並操作。
在數據庫中,union和union all關鍵字都是將兩個結果集合並為一個,但這兩者從使用和效率上來說都有所不同。union在進行表鏈接后會篩選掉重復的記錄,所以在表鏈接后會對所產生的結果集進行排序運算,刪除重復的記錄再返回結果。
如:
select * from test_union1
union
select * from test_union2
這個SQL在運行時先取出兩個表的結果,再用排序空間進行排序刪除重復的記錄,最后返回結果集,如果表數據量大的話可能會導致用磁盤進行排序。
而union all只是簡單的將兩個結果合並后就返回。這樣,如果返回的兩個結果集中有重復的數據,那么返回的結果集就會包含重復的數據了。 從效率上說,union all要比union快很多,所以,如果可以確認合並的兩個結果集中不包含重復的數據的話,那么就使用union all,如下:
select * from test_union1
union all
select * from test_union2
使用union組合查詢的結果集有兩個最基本的規則:
1、所有查詢中的列數和列的順序必須相同。
2、數據類型必須兼容。
雖然這個可以簡便解決數據查詢問題,但是還是需要代碼層面的調整。
union還有一個地方可能會用到,如web項目中經常會碰到整站搜索的問題,即客戶希望在網站的搜索框中輸入一個詞語,然后在整個網站中只要包含這個詞的頁面都要出現在搜索結果中。由於一個web項目不可能用一張表就全部搞定的,所以這里一般都是要用union聯合搜索來解決整個問題的。
作者:張偉科
鏈接:https://www.jianshu.com/p/a262271f06a5
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。