爲什麼MySQL裏的ibdata1文件一直變大?

本文的原文地址在此:http://www.percona.com/blog/2013/08/20/why-is-the-ibdata1-file-continuously-growing-in-mysql/,以下是譯文。
-----------------------------------------------------------這是一條分割線-----------------------------------------------------------

August 20, 2013 by Miguel Angel

我們在Percona Support上,經常會收到關於這個問題的提問。

這個問題的淺層來源是服務器監視程序發出了一個關於MySQL服務器存儲的警告:服務器的磁盤馬上就要滿了。

在經過一系列排查以後,你會發現大部分的磁盤空間都被InnoDB的共享表空間文件ibdata1佔用。可是你已經把innodb_file_per_table設爲了1,爲什麼ibdata1文件還會這麼大,ibdata1裏到底都包含什麼內容?

當把innodb_file_per_table設爲了1以後,所有的表數據都存儲在各自獨立的表空間文件裏,不過共享表空間文件(即ibdata1)依然存儲了InnoDB的以下數據:

1. data dictionary aka metadata of InnoDB tables
2. change buffer
3. doublewrite buffer
4. undo logs

有的數據在Percona Server中可以通過配置保存到其他地方,以避免ibdata1過大。例如你能夠通過改變innodb_ibuf_max_size改變change buffer的大小,通過改變innodb_doublewrite_file,將doublewrite buffer的內容存儲到一個單獨的文件中。

如果是MySQL5.6,同樣可以爲undo log配置一個獨立的表空間文件,而不是存儲在ibdata1中。細節可查看官方文檔

是什麼原因導致ibdata1增長過快?

當MySQL碰到這個問題時,我們通常會使用下面的命令:

SHOW ENGINE INNODB STATUS\G
這個命令會給我們提供非常有價值的信息。我們找到TRANSACTIONS這一節,並發現以下內容

---TRANSACTION 36E, ACTIVE 1256288 sec
MySQL thread id 42, OS thread handle 0x7f8baaccc700, query id 7900290 localhost root
show engine innodb status
Trx read view will not see trx with id >= 36F, sees < 36F
這是最常見的問題:一個創建於14天前的事務,且狀態是激活的(ACTIVE)。這就意味着從這個事務開始的至今,InnoDB將所有的舊數據都寫入到了undo日誌中。如果這個事務有很多寫入任務,那麼意味着ibdata1中存儲了非常多的undo日誌數據。

如果你當前沒有任何長期運行的事務,INNODB STATUS同樣可以爲你提供另外一個有意義的變量:History list length,指在回滾空間中的未清除的事務數。當purge線程(老版本是master線程)刪除undo記錄的速度趕不上寫入的速度時,也會造成History list length增大。

我如何查看ibdata1中存儲的數據?

很遺憾,MySQL沒有提供類似的工具。不過有另外兩個工具可以查看ibdata1中的數據。其中之一是Mark Callaghan在這個bug反饋報告中提交的一個修改過的innochecksum(注意,MySQL自帶的innochecksum是有bug的,使用後沒有效果,如果要想用,需要自己下載bug反饋報告中的源代碼編譯。——譯者注),非常易用的小東西,如下:

# <code>./innochecksum /var/lib/mysql/ibdata1</code>
0 bad checksum
13 FIL_PAGE_INDEX
19272 FIL_PAGE_UNDO_LOG
230 FIL_PAGE_INODE
1 FIL_PAGE_IBUF_FREE_LIST
892 FIL_PAGE_TYPE_ALLOCATED
2 FIL_PAGE_IBUF_BITMAP
195 FIL_PAGE_TYPE_SYS
1 FIL_PAGE_TYPE_TRX_SYS
1 FIL_PAGE_TYPE_FSP_HDR
1 FIL_PAGE_TYPE_XDES
0 FIL_PAGE_TYPE_BLOB
0 FIL_PAGE_TYPE_ZBLOB
0 other
3 max index_id
這裏UNDO_LOG佔用了19272的大小,而所有的數據加起來才20608(13+19272+230+1+892+2+195+1+1+1),相當於當前ibdata1中,93%都被UNDO_LOG佔用。

另外一種查看工具是由Jeremy Cole寫的InnoDB Ruby Tools。這個一個用來檢查InnoDB內部構成的更高級的工具。例如通過space-summary參數,我們能獲得一個包含每一頁(every page)的數據類型的清單。之後我們能使用標準的Unix工具獲得UNDO_LOG所佔頁的總數

# innodb_space -f /var/lib/mysql/ibdata1 space-summary | grep UNDO_LOG | wc -l
19272
儘管這個案例中innochecksum相比Jeremy的工具要快且容易一些,但我還是推薦你使用Jeremy的工具。因爲這樣你可以看到數據在InnoDB裏如如何分佈的,還可以看到InnoDB的內部情況。

好了,現在我們已經知道造成ibdata1龐大的原因了,那麼下一個問題是:

我如何才能解決這個問題?

這個問題不難。如果你還能夠提交(commit),就這麼幹。如果你已經無法提交,則只能殺掉這個線程,再使用rollback來重啓。不過你需要注意,殺死進程只能停止ibdata1的繼續變大,真正的問題原因是你的 軟件有bug,或是其他什麼原因造成了錯誤。現在你已經知道怎麼找到問題在哪,你還需要用debug工具或日誌來確定到底是什麼導致了這些問題。

如果問題原因是purge線程導致的,那麼你將其升級到最新版本,以使用purge線程取代master線程。更多細節請參閱官方文檔

是否有辦法恢復這些用掉的磁盤空間?

沒有,至少沒有一個簡單迅速的方法能做到這一點。InnoDB表空間從來不會變小>_<。這是一個歷史悠久的bug(超過10年):

當你刪掉一些行,page只會被標爲“已刪除”,然後等待今後的使用,而不是歸還這些空間。唯一的辦法是讓數據庫使用一個全新的ibdata1。爲了做到這一點,你需要使用mysqldump對整個數據庫進行邏輯備份;之後停掉MySQL;在然後刪除所有的數據文件(包括ib_logfile*和ibdata*文件)。之後在重啓MySQL時,MySQL會重建表空間文件(大小與配置文件指定的相同——譯者注)。再使用mysqldump恢復備份的數據。

總結

造成ibdata1大小飛速增長的最常見的原因,通常是MySQL中被我們所遺忘的,但長期處於激活狀態的transaction。你必須要儘快解決這個問題(commit或kill這個transaction),因爲你沒有任何辦法歸還這些磁盤空間,除非你願意忍受mysqldump那緩慢的備份/恢復過程。

要想避免這種問題,可以監控數據庫。我們提供的MySQL監控插件能夠及時的警告你那些長期處於激活狀態的transaction。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章