Part1:寫在最前mysql
當一張單表10億數據量的表放在你面前,你將面臨着什麼?sql
Part2:背景介紹數據庫
爲了提高數據庫資源利用率,一個實例中,在不互相影響,保證業務高效的前提下,咱們會將同一個大業務下的不一樣小業務放在一個實例中,咱們的磁盤空間是2T,告警閾值爲當磁盤剩餘空間10%時發出短信告警。筆者接到某業務主庫磁盤剩餘空間告警的短信後,通過一番查探,發現從幾天前開始,有一張表的數據量增加很是快,而在以前,磁盤空間降低率仍是較爲平緩的,該表存在大字段text,其大批量寫入更新,致使磁盤消耗迅猛。bash
咱們首先來看下該表的表結構:session
mysql> CREATE TABLE `tablename_v2` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`No` varchar(64) NOT NULL DEFAULT '',
`Code` varchar(64) NOT NULL DEFAULT '' ,
`log` varchar(64) DEFAULT '' ,
`log1` varchar(64) DEFAULT '' ,
.....
`Phone` varchar(20) DEFAULT '',
`createTime` bigint(20) unsigned NOT NULL DEFAULT '0',
`updateTime` bigint(20) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_mailNo` (`No`,`Code`),
KEY `idx_Phone` (`Phone`)
) ENGINE=InnoDB AUTO_INCREMENT=9794134664 DEFAULT CHARSET=utf8;
複製代碼
與業務瞭解得知,該表幾乎沒有刪除操做,因爲數據量過大,咱們模糊使用auto_increment來做爲表數量預估值,避免count()操做對線上形成的影響。併發
Part3:案例分析app
與業務溝通了解後得知,該表能夠清理4個月之前的老舊數據,所以可使用delete的方式清除,而咱們經過表結構能夠看出,該表在設計之初,並無對updateTime列建立索引,所以以時間爲範圍去進行delete操做,這顯然是不合理的。工具
通過與業務協商,咱們肯定了能夠將id做爲刪除條件,刪除id<2577754125以前的數據ui
也就是說,此時的delete語句變爲了:this
mysql> delete from tablename_v2 where id <2577754125;
複製代碼
且不說delete操做有多慢,直接執行這樣的SQL也會有諸如長事務告警,從庫大量延遲等併發症產生,所以毫不能在生產庫上進行這種大批量的危險操做。
Part1:監控
從監控圖咱們能看出磁盤降低的趨勢:
監控顯示,從6月14日-6月18日期間,磁盤消耗最爲嚴重,與業務溝通得知,618期間大促引起該表存儲量激增致使。
Part2:實戰操做
咱們經過查看binlog發現,集羣中binlog的刷新量雖不說像筆者上個案例那樣多麼迅猛,但也毫不是老實本分
咱們能夠看出,在高峯期間,binlog的刷新間隔最短達到了2分鐘寫滿1.1GB的binlog。所以筆者與業務溝通後,首先清理binlog日誌,將 expire_logs_days從7天調整至3天。
同時,清理一些可以清理的無用日誌、廢舊文件等等。
咱們也能在上面的監控圖看到在作完這些清理操做後,磁盤空間剩餘從4%提高至12%,但隨後依舊保持原有速率降低。
Part3:pt-archiver
真兇找到了,咱們怎麼辦,別急,使用pt-archiver。pt-archiver工具是percona工具集的一員,是歸檔MySQL大表數據的最佳輕量級工具之一。他能夠實現分chunk分批次歸檔和刪除數據,能避免一次性操做大量數據帶來的各類問題。
閒話很少說,一貫本着實戰的原則,咱們直接上命令:
pt-archiver --source
h=c3-helei-db01.bj,D=helei,t=tablename_v2,u=sys_admin,p=MANAGER
--where 'id<2577754125' --purge --progress 10000 --limit=10000
--no-check-charset --txn-size=10000 --bulk-delete --statistics --max-lag=20
--check-slave-lag c3-helei-db02.bj</pre>
複製代碼
簡單說下經常使用的參數:
| source | 目標節點 |
| where | 條件 |
| purge | 刪除source數據庫的相關匹配記錄 |
| progress | 每處理多少行顯示一次信息 |
| limit | 每次取出多少行處理 |
| no-check-charset | 不檢查字符集 |
| txn-size | 每多少行提交一次 |
| bulk-delete | 並行刪除 |
| statistics | 結束後輸出統計信息 |
| max-lag | 最大延遲 |
| check-slave-lag |
檢查某個目標從庫的延遲
|
****Warning:警********告****這裏就又有個小坑了,的確,咱們使用bulk-delete參數可以增長刪除速率,相比不使用bulk-delete速度可以提高10倍左右,但問題也就顯現出來,在使用上述命令期間,發現binlog每秒寫入量激增,這又回到了咱們說的,哪些狀況會致使binlog轉爲row格式。
首先咱們須要瞭解到使用bulk-delete時,sql是以下執行的:
mysql> delete from tablename_v2 where id >xxx and id < xxx limit 10000.
複製代碼
若是您以前關注過筆者的文章,應該知道,當使用了delete from xxx where xxx limit 語法時,會將binlog_format從mixed轉爲row,這樣的話,刪除的同時,binlog因爲轉爲了row格式也在激增,這與咱們的預期是不符的。
所以最終的命令爲:
pt-archiver --source
h=c3-helei-db01.bj,D=helei,t=tablename_v2,u=sys_admin,p=MANAGER
--where 'id<2577754125' --purge --progress 10000 --limit=10000
--no-check-charset --txn-size=10000 --statistics --max-lag=20
--check-slave-lag c3-helei-db02.bj</pre>
複製代碼
去掉了bulk-delete,這樣的話就可以保證正常的delete,而不加limit,binlog不會轉爲row格式致使磁盤消耗繼續激增。
對於Innodb引擎來講,delete操做並不會當即釋放磁盤空間,新的數據會優先填滿delete操做後的「空洞」,所以從監控來看就是磁盤不會進一步消耗了,說明咱們的pt-archiver工具刪除是有效的。
Part4:困惑
首先咱們要知道,當你面對一張數據量龐大的表的時候,有些東西就會受限制,例如:
不能alter操做,由於這會阻塞dml操做。
對於本案例,空間本就不足,也不能使用pt-online工具來完成。
對於不能alter實際上是比較要命的,好比開發要求在某個時間段儘快上線新業務,而新業務須要新增列,此時面對這麼龐大的量級,alter操做會異常緩慢。
所以,筆者與研發溝通,儘快採用物理分表的方式解決這個問題,使用物理分表,清理表的操做就會很容易,無需delete,直接drop 老表就能夠了。其次,物理分表讓alter語句不會卡住過久,使用pt-online工具也不會一次性佔據過多的磁盤空間誘發磁盤空間不足的告警。
再有就是遷移TiDB,TiDB相較MySQL更適合存儲這類業務。
Part5:再談binlog_format
咱們選取其中高峯期的binlog發現其update操做轉爲了row格式,記錄了全部列變動先後的全部信息,而binlog中並未出現update xxx limit這種操做,那又會是什麼引起的row格式記錄呢?
這裏這篇文章又拋出一個新的案例,在官網那篇什麼時候mixed轉row格式中又一個沒有記錄的狀況
官方文檔:
When running in MIXED logging format, the server automatically switches from statement-based to row-based logging under the following conditions:
When a DML statement updates an NDBCLUSTER table.
When a function contains UUID().
When one or more tables with AUTO_INCREMENT columns are updated and a trigger or stored function is invoked. Like all other unsafe statements, this generates a warning if binlog_format = STATEMENT.
When any INSERT DELAYED is executed.
When a call to a UDF is involved.
If a statement is logged by row and the session that executed the statement has any temporary tables, logging by row is used for all subsequent statements (except for those accessing temporary tables) until all temporary tables in use by that session are dropped.
This is true whether or not any temporary tables are actually logged.
Temporary tables cannot be logged using row-based format; thus, once row-based logging is used, all subsequent statements using that table are unsafe. The server approximates this condition by treating all statements executed during the session as unsafe until the session no longer holds any temporary tables.
When FOUND_ROWS() or ROW_COUNT() is used. (Bug #12092, Bug #30244)
When USER(), CURRENT_USER(), or CURRENT_USER is used. (Bug #28086)
When a statement refers to one or more system variables. (Bug #31168)
複製代碼
咱們這個案例中又出現了一個新的因素就是:
當表結構中存在多個惟一索引(包括主鍵id),本案例中存在主鍵和UNIQUE KEY uk_mailNo
這個惟一索引,且使用了
INSERT ... ON DUPLICATE KEY UPDATE</pre>
複製代碼
也就是說,只要業務解決了使用這種語法插入的話,磁盤空間降低迅猛的緣由也可以緩解很多。咱們統計發現,qps更高的其餘業務中,binlog保留7天的磁盤消耗量在60GB
而該業務咱們僅僅保留3天binlog,卻依舊消耗了430GB的磁盤空間,這已經超過了咱們整個2T磁盤空間的5分之一了。
——總結 ——
經過這個案例,咱們可以了****解到什麼狀況下binlog_format會由MIXED格式轉爲ROW格式,以及觸發的一系列併發症和解決辦法,還有pt工具pt-archiver的使用。因爲筆者的水平有限,編寫時間也很倉促,文中不免會出現一些錯誤或者不許確的地方,不妥之處懇請讀者批評指正。喜歡筆者的文章,點一波關注,謝謝!