1. 程式人生 > >mysql資料庫ibdata1檔案瘦身

mysql資料庫ibdata1檔案瘦身

遇到InnoDB的共享表空間檔案ibdata1檔案大小暴增時,應該如何處理?

1、問題背景

MySQL/InnoDB的童鞋可能也會有過煩惱,不知道為什麼原因,ibdata1檔案莫名其妙的增大,不知道該如何讓它縮回去,就跟30歲之後男人的肚腩一樣,汗啊,可喜可賀的是我的肚腩還沒長出來,hoho~

正式開始之前,我們要先知道ibdata1檔案是幹什麼用的。

ibdata1檔案是InnoDB儲存引擎的共享表空間檔案,該檔案中主要儲存著下面這些資料:

  • data dictionary
  • double write buffer
  • insert buffer/change buffer
  • rollback segments
  • undo space
  • Foreign key constraint system tables

另外,當選項 innodb_file_per_table = 0 時,在ibdata1檔案中還需要儲存 InnoDB 表資料&索引。ibdata1檔案從5.6.7版本開始,預設大小是12MB,而在這之前預設大小是10MB,其相關選項是innodb_data_file_path,比如我一般是這麼設定的:

innodb_data_file_path = ibdata1:1G:autoextend

當然了,無論是否啟用了 innodb_file_per_table = 1,ibdata1檔案都必須存在,因為它必須儲存上述 InnoDB 引擎所依賴&必須的資料,尤其是上面加粗標識的 rollback segments 和 undo space,它倆是引起 ibdata1 檔案大小增加的最大原因,我們下面會詳細說。

2、原因分析

我們知道,InnoDB是支援MVCC的,它和Oracle類似,採用 undo log、redo log來實現MVCC特性的。在事務中對一行資料進行修改時,InnoDB 會把這行資料的舊版本資料儲存一份在undo log中

,如果這時候有另一個事務又要修改這行資料,就又會把該事物最新可見的資料版本儲存一份在undo log中,以此類推,如果該資料當前有N個事務要對其進行修改,就需要儲存N份歷史版本(和ORACLE略有不同的是,InnoDB的undo log不完全是物理block,主要是邏輯日誌,這個可以檢視 InnoDB 原始碼或其他相關資料)。這些 undo log 需要等待該事務結束後,並再次根據事務隔離級別所決定的對其他事務而言的可見性進行判斷,確認是否可以將這些 undo log 刪除掉,這個工作稱為 purge(purge 工作不僅僅是刪除過期不用的 undo log,還有其他,以後有機會再說)。

那麼問題來了,如果當前有個事務中需要讀取到大量資料的歷史版本,而該事務因為某些原因無法今早提交或回滾,而該事務發起之後又有大量事務需要對這些資料進行修改,這些新事務產生的 undo log 就一直無法被刪除掉,形成了堆積,這就是導致 ibdata1 檔案大小增大最主要的原因之一。這種情況最經典的場景就是大量資料備份,因此我們建議把備份工作放在專用的 slave server 上,不要放在 master server 上。

另一種情況是,InnoDB的 purge 工作因為本次 file i/o 效能是在太差或其他的原因,一直無法及時把可以刪除的 undo log 進行purge 從而形成堆積,這是導致 ibdata1 檔案大小增大另一個最主要的原因。這種場景發生在伺服器硬體配置比較弱,沒有及時跟上業務發展而升級的情況。

比較少見的一種是在早期執行在32位系統的MySQL版本中存在bug,當發現待 purge 的 undo log 總量超過某個值時,purge 執行緒直接放棄抵抗,再也不進行 purge 了,這個問題在我們早期使用32位MySQL 5.0版本時遇到的比較多,我們曾經遇到這個檔案漲到100多G的情況。後來我們費了很大功夫把這些例項都遷移到64位系統下,終於解決了這個問題。

最後一個是,選項 innodb_data_file_path 值一開始就沒調整或者設定很小,這就必不可免導致 ibdata1 檔案增大了。Percona官方提供的 my.cnf 參考檔案中也一直沒把這個值加大,讓我百思不得其解,難道是為了像那個經常被我吐槽的xx那樣,故意留個暗門,好方便後續幫客戶進行優化嗎?(我心理太陰暗了,不好不好~~)

稍微總結下,導致ibdata1檔案大小暴漲的原因有下面幾個:

  • 有大量併發事務,產生大量的undo log;
  • 有舊事務長時間未提交,產生大量舊undo log;
  • file i/o效能差,purge進度慢;
  • 初始化設定太小不夠用;
  • 32-bit系統下有bug。

稍微題外話補充下,另一個熱門資料庫 PostgreSQL 的做法是把各個歷史版本的資料 和 原資料表空間 儲存在一起,所以不存在本案例的問題,也因此 PostgreSQL 的事務回滾會非常快,並且還需要定期做 vaccum 工作(具體可參見PostgreSQL的MVCC實現機制,我可能說的不是完全正確哈)

3、解決方法建議

看到上面的這些問題原因描述,有些同學可能覺得這個好辦啊,對 ibdata1 檔案大小進行收縮,回收表空間不就結了嗎。悲劇的是,截止目前,InnoDB 還沒有辦法對 ibdata1 檔案表空間進行回收/收縮,一旦 ibdata1 檔案的肚子被搞大了,只能把資料先備份後恢復再次重新初始化例項才能恢復原先的大小,或者把依次把各個獨立表空間檔案備份恢復到一個新例項中,具體做法如下

1、備份整個mysql資料庫:
mysqldump -uroot -p123 --extended-insert --all-databases --add-drop-database --disable-keys --flush-privileges --routines --events > all-databases.sql
2、備份完以後停止資料庫服務:
Service mysqld stop
3、修改資料庫配置:/etc/mysql/my.cnf
[mysqld]下增加下面配置
innodb_file_per_table=1
驗證配置是否生效:重啟mysql服務,檢視innodb_file_per_table值是否為ON。
#service mysqld restart
#mysql -uroot -ppassword
mysql> show variables like '%per_table%';
4、變數為ON之後,再停止資料庫服務,然後刪除原來的資料檔案:資料庫檔案一般存放路徑/var/lib/mysql,刪除原來的ibdata1檔案、日誌檔案ib_logfile*和原來的應用資料庫資料夾(mysql資料夾不要刪)。
5、刪完之後啟動資料庫服務,從之前備份中還原資料庫。
service mysqld start
mysql -uroot -p123 < all-databases.sql
還原以後發現/var/lib/mysql下,新的ibdata1檔案就只有幾十M了,資料及索引都變成了針對單個表的小ibd檔案,分別在對應的資料庫資料夾下面。

當然了,這個問題也並不是不能防範,根據上面提到的原因,相應的建議對策是:

  • 升級到5.6及以上(64-bit),採用獨立undo表空間,5.6版本開始就支援獨立的undo表空間了,再也不用擔心會把 ibdata1 檔案搞大;
  • 初始化設定時,把 ibdata1 檔案至少設定為1GB以上;
  • 增加purge執行緒數,比如設定 innodb_purge_threads = 8;
  • 提高file i/o能力,該上SSD的趕緊上;
  • 事務及時提交,不要積壓;
  • 預設開啟autocommit = 1,避免忘了某個事務長時間未提交;
  • 檢查開發框架,確認是否設定了 autocommit=0,記得在事務結束後都有顯式提交或回滾。