關於 MySQL 的 ibdata1 文件的這個問題:
當監控服務器發送一個關於 MySQL 服務器存儲的報警時,恐慌就開始了 —— 就是說磁盤快要滿了。
一番調查后你意識到大多數地盤空間被 InnoDB 的共享表空間 ibdata1 使用。而你已經啟用了 innodb_file_per_table,所以問題是:
ibdata1存了什么?
當你啟用了 innodb_file_per_table,表被存儲在他們自己的表空間里,但是共享表空間仍然在存儲其它的 InnoDB 內部數據:
- 數據字典,也就是 InnoDB 表的元數據
- 變更緩沖區
- 雙寫緩沖區
- 撤銷日志
其中的一些在 Percona 服務器上可以被配置來避免增長過大的。例如你可以通過 innodb_ibuf_max_size 設置最大變更緩沖區,或設置 innodb_doublewrite_file 來將雙寫緩沖區存儲到一個分離的文件。
MySQL 5.6 版中你也可以創建外部的撤銷表空間,所以它們可以放到自己的文件來替代存儲到 ibdata1。可以看看這個文檔。
什么引起 ibdata1 增長迅速?
當 MySQL 出現問題通常我們需要執行的第一個命令是:
SHOW ENGINE INNODB STATUS/G
這將展示給我們一些很有價值的信息。我們從** TRANSACTION(事務)**部分開始檢查,然后我們會發現這個:
---TRANSACTION 36E, ACTIVE 1256288 secMySQL thread id 42, OS thread handle 0x7f8baaccc700, query id 7900290 localhost rootshow engine innodb statusTrx read view will not see trx with id >= 36F, sees < 36F
這是一個最常見的原因,一個14天前創建的相當老的事務。這個狀態是活動的,這意味着 InnoDB 已經創建了一個數據的快照,所以需要在撤銷日志中維護舊頁面,以保障數據庫的一致性視圖,直到事務開始。如果你的數據庫有大量的寫入任務,那就意味着存儲了大量的撤銷頁。
如果你找不到任何長時間運行的事務,你也可以監控INNODB STATUS 中的其他的變量,“History list length(歷史記錄列表長度)”展示了一些等待清除操作。這種情況下問題經常發生,因為清除線程(或者老版本的主線程)不能像這些記錄進來的速度一樣快地處理撤銷。
我怎么檢查什么被存儲到了 ibdata1 里了?
很不幸,MySQL 不提供查看什么被存儲到 ibdata1 共享表空間的信息,但是有兩個工具將會很有幫助。第一個是馬克·卡拉漢制作的一個修改版 innochecksum ,它發布在這個漏洞報告里。
它相當易於使用:
# ./innochecksum /var/lib/mysql/ibdata10 bad checksum13 FIL_PAGE_INDEX19272 FIL_PAGE_UNDO_LOG230 FIL_PAGE_INODE1 FIL_PAGE_IBUF_FREE_LIST892 FIL_PAGE_TYPE_ALLOCATED2 FIL_PAGE_IBUF_BITMAP195 FIL_PAGE_TYPE_SYS1 FIL_PAGE_TYPE_TRX_SYS1 FIL_PAGE_TYPE_FSP_HDR1 FIL_PAGE_TYPE_XDES0 FIL_PAGE_TYPE_BLOB0 FIL_PAGE_TYPE_ZBLOB0 other3 max index_id
全部的 20608 中有 19272 個撤銷日志頁。這占用了表空間的 93%。
第二個檢查表空間內容的方式是傑里米·科爾制作的 InnoDB Ruby 工具。它是個檢查 InnoDB 的內部結構的更先進的工具。例如我們可以使用 space-summary 參數來得到每個頁面及其數據類型的列表。我們可以使用標准的 Unix 工具來統計撤銷日志頁的數量:
# innodb_space -f /var/lib/mysql/ibdata1 space-summary | grep UNDO_LOG | wc -l19272
盡管這種特殊的情況下,innochedcksum 更快更容易使用,但是我推薦你使用傑里米的工具去了解更多的 InnoDB 內部的數據分布及其內部結構。
好,現在我們知道問題所在了。下一個問題:
我該怎么解決問題?
這個問題的答案很簡單。如果你還能提交語句,就做吧。如果不能的話,你必須要殺掉線程開始回滾過程。那將停止 ibdata1 的增長,但是很顯然,你的軟件會出現漏洞,有些人會遇到錯誤。現在你知道如何去鑒定問題所在,你需要使用你自己的調試工具或普通的查詢日志來找出誰或者什么引起的問題。
如果問題發生在清除線程,解決方法通常是升級到新版本,新版中使用一個獨立的清除線程替代主線程。更多信息查看該文檔
有什么方法回收已使用的空間么?
沒有,目前還沒有一個容易並且快速的方法。InnoDB 表空間從不收縮...參見10 年之久的漏洞報告,最新更新自詹姆斯·戴(謝謝):
當你刪除一些行,這個頁被標為已刪除稍后重用,但是這個空間從不會被回收。唯一的方法是使用新的 ibdata1 啟動數據庫。要做這個你應該需要使用 mysqldump 做一個邏輯全備份,然后停止 MySQL 並刪除所有數據庫、ib_logfile*、ibdata1* 文件。當你再啟動 MySQL 的時候將會創建一個新的共享表空間。然后恢復邏輯備份。
總結
當 ibdata1 文件增長太快,通常是 MySQL 里長時間運行的被遺忘的事務引起的。嘗試去解決問題越快越好(提交或者殺死事務),因為不經過痛苦緩慢的 mysqldump 過程,你就不能回收浪費的磁盤空間。
也是非常推薦監控數據庫以避免這些問題。我們的 MySQL 監控插件包括一個 Nagios 腳本,如果發現了一個太老的運行事務它可以提醒你。
============================
這兩種文件都是存放Innodb 數據的文件,之所以有兩種文件來存放Innodb 的數據(包
括索引),是因為Innodb 的數據存儲方式能夠通過配置來決定是使用共享表空間存放存儲數
據,還是獨享表空間存放存儲數據。獨享表空間存儲方式使用“.ibd”文件來存放數據,且
每個表一個“.ibd”文件,文件存放在和MyISAM 數據相同的位置。如果選用共享存儲表空
間來存放數據,則會使用ibdata 文件來存放,所有表共同使用一個(或者多個,可自行配
置)ibdata 文件。
ibdata 文件可以通過innodb_data_home_dir 和innodb_data_file_path
兩個參數共同配置組成, innodb_data_home_dir 配置數據存放的總目錄, 而
innodb_data_file_path 配置每一個文件的名稱。當然, 也可以不配置
innodb_data_home_dir 而直接在innodb_data_file_path 參數配置的時候使用絕對路徑來
完成配置。innodb_data_file_path 中可以一次配置多個ibdata 文件。
文件可以是指定大
小,也可以是自動擴展的,但是Innodb 限制了僅僅只有最后一個ibdata 文件能夠配置成自
動擴展類型。當我們需要添加新的ibdata 文件的時候,只能添加在innodb_data_file_path
配置的最后,而且必須重啟MySQL 才能完成ibdata 的添加工作。
不過如果我們使用獨享表
空間存儲方式的話,就不會有這樣的問題,但是如果要使用裸設備的話,每個表一個裸設備,
可能造成裸設備數量非常大,而且不太容易控制大小,實現比較困難,而共享表空間卻不會
有這個問題,容易控制裸設備數量。我個人還是更傾向於使用獨享表空間存儲方式。當然,
兩種方式各有利弊,看大家各自應用環境的側重點在那里了。
上面僅僅介紹了兩種最常用存儲引擎的數據文件,此外其他各種存儲引擎都有各自的數
據文件,讀者朋友可以自行創建某個存儲引擎的表做一個簡單的測試,做更多的了解。
