>$ mysql --help | grep my.cnf
?參數文件:告訴MySQL實例啟動時在哪里可以找到數據庫文件,并且指定某些初始化參數,這些參數定義了某種內存結構的大小等設置,還會介紹各種參數的類型。
?日志文件:用來記錄MySQL實例對某種條件做出響應時寫入的文件,如錯誤日志文件、二進制日志文件、慢查詢日志文件、查詢日志文件等。
?socket文件:當用UNIX域套接字方式進行連接時需要的文件。
?pid文件:MySQL實例的進程ID文件。
?MySQL表結構文件:用來存放MySQL表結構定義文件。
?存儲引擎文件:因為MySQL表存儲引擎的關系,每個存儲引擎都會有自己的文件來保存各種數據。這些存儲引擎真正存儲了記錄和索引等數據。
參數文件
MySQL數據庫中的參數可以分為兩類:
?動態(dynamic)參數
?靜態(static)參數
動態參數意味著可以在MySQL實例運行中進行更改,靜態參數說明在整個實例生命周期內都不得進行更改,就好像是只讀(read only)的。
日志文件
?錯誤日志(error log)
?二進制日志(binlog)
?慢查詢日志(slow query log)
?查詢日志(log)
錯誤日志
>$ show variables like "log_error"
慢查詢日志
>$ show variables like "long_query_time"
>$ show variables like "slow_query_log"
查詢日志
查詢日志甚至記錄了對Access denied的請求,即對于未能正確執行的SQL語句,查詢日志也會進行記錄。
二進制日志
二進制日志(binary log)記錄了對MySQL數據庫執行更改的所有操作,但是不包括SELECT和SHOW這類操作,因為這類操作對數據本身并沒有修改。然而,若操作本身并沒有導致數據庫發生變化,那么該操作可能也會寫入二進制日志。
二進制日志主要有以下幾種作用:
?恢復(recovery):某些數據的恢復需要二進制日志,例如,在一個數據庫全備文件恢復后,用戶可以通過二進制日志進行point-in-time的恢復。
?復制(replication):其原理與恢復類似,通過復制和執行二進制日志使一臺遠程的MySQL數據庫(一般稱為slave或standby)與一臺MySQL數據庫(一般稱為master或primary)進行實時同步。
?審計(audit):用戶可以通過二進制日志中的信息來進行審計,判斷是否有對數據庫進行注入的攻擊。
套接字文件
在UNIX系統下本地連接MySQL可以采用UNIX域套接字方式,這種方式需要一個套接字(socket)文件。套接字文件可由參數socket控制。一般在/tmp目錄下,名為mysql.sock:
(很少看到解釋)
pid文件
當MySQL實例啟動時,會將自己的進程ID寫入一個文件中——該文件即為pid文件。該文件可由參數pid_file控制,默認位于數據庫目錄下,文件名為主機名.pid
表結構定義文件
不論表采用何種存儲引擎,MySQL都有一個以frm為后綴名的文件,這個文件記錄了該表的表結構定義。
InnoDB存儲引擎文件
InnoDB采用將存儲的數據按表空間(tablespace)進行存放的設計。在默認配置下會有一個初始大小為10MB,名為ibdata1的文件。該文件就是默認的表空間文件(tablespace file),用戶可以通過參數innodb_data_file_path對其進行設置。
若設置了參數innodb_file_per_table,則用戶可以將每個基于InnoDB存儲引擎的表產生一個獨立表空間。獨立表空間的命名規則為:表名.ibd。通過這樣的方式,用戶不用將所有數據都存放于默認的表空間中。
單獨的表空間文件僅存儲該表的數據、索引和插入緩沖BITMAP等信息,其余信息還是存放在默認的表空間中。
重做日志文件
在默認情況下,在InnoDB存儲引擎的數據目錄下會有兩個名為ib_logfile0和ib_logfile1的文件。在MySQL官方手冊中將其稱為InnoDB存儲引擎的日志文件,不過更準確的定義應該是重做日志文件(redo log file)。為什么強調是重做日志文件呢?因為重做日志文件對于InnoDB存儲引擎至關重要,它們記錄了對于InnoDB存儲引擎的事務日志。
每個InnoDB存儲引擎至少有1個重做日志文件組(group),每個文件組下至少有2個重做日志文件,如默認的ib_logfile0和ib_logfile1。為了得到更高的可靠性,用戶可以設置多個的鏡像日志組(mirrored log groups),將不同的文件組放在不同的磁盤上,以此提高重做日志的高可用性。
重做日志文件的大小設置對于InnoDB存儲引擎的性能有著非常大的影響。一方面重做日志文件不能設置得太大,如果設置得很大,在恢復時可能需要很長的時間;另一方面又不能設置得太小了,否則可能導致一個事務的日志需要多次切換重做日志文件。此外,重做日志文件太小會導致頻繁地發生async checkpoint,導致性能的抖動。
重做日志有一個capacity變量,該值代表了最后的檢查點不能超過這個閾值,如果超過則必須將緩沖池(innodb buffer pool)中臟頁列表(flush list)中的部分臟數據頁寫回磁盤,這時會導致用戶線程的阻塞。
首先,二進制日志會記錄所有與MySQL數據庫有關的日志記錄,包括InnoDB、MyISAM、Heap等其他存儲引擎的日志。而InnoDB存儲引擎的重做日志只記錄有關該存儲引擎本身的事務日志。
其次,記錄的內容不同,無論用戶將二進制日志文件記錄的格式設為STATEMENT還是ROW,又或者是MIXED,其記錄的都是關于一個事務的具體操作內容,即該日志是邏輯日志。而InnoDB存儲引擎的重做日志文件記錄的是關于每個頁(Page)的更改的物理情況。
此外,寫入的時間也不同,二進制日志文件僅在事務提交前進行提交,即只寫磁盤一次,不論這時該事務多大。而在事務進行的過程中,卻不斷有重做日志條目(redo entry)被寫入到重做日志文件中。
重做日志條目是由4個部分組成:
?redo_log_type占用1字節,表示重做日志的類型
?space表示表空間的ID,但采用壓縮的方式,因此占用的空間可能小于4字節
?page_no表示頁的偏移量,同樣采用壓縮的方式
?redo_log_body表示每個重做日志的數據部分,恢復時需要調用相應的函數進行解析
二進制日志的作用非常關鍵,可以用來進行point in time的恢復以及復制(replication)環境的搭建。
問題
二進制文件、undo、redo,這些文件有什么關系
- 首先,范圍不同。二進制文件記錄所有與MySQL相關的日志記錄,包括InnoDB,MyISAM,Heap等存儲引擎的日志。而InnoDB的重做日志只記錄InnoDB相關的事務日志。
- 其次,內容不同。二進制文件記錄的是關于一個事務的具體操作內容,而InnoDB的重做日志記錄每個數據頁(page)更改的物理情況。
- 寫入的時間不同。二進制文件在事務提交之前記錄,在事務進行過程中,不斷有重做日志條目寫入重做日志文件中。
表空間文件
.frm是描述了表的結構,.MYD保存了表的數據記錄,*.MYI則是表的索引
假設我們使用系統默認的參數啟動,則會有兩個tablespace,第一個tablespace包括一個文件ibdata1,這個也可以稱為系統表空間,
另外一個space包括兩個日志文件(iblogfile0, iblogfile1)
ibdata1文件是InnoDB存儲引擎的共享表空間文件,該文件中主要存儲著下面這些數據:
- data dictionary
- double write buffer
- insert buffer/change buffer
- rollback segments
- undo space
- Foreign key constraint system tables
無論是否啟用了 innodb_file_per_table = 1,ibdata1文件都必須存在,因為它必須存儲上述 InnoDB 引擎所依賴&必須的數據,尤其是上面加粗標識的 rollback segments 和 undo space,它倆是引起 ibdata1 文件大小增加的最大原因
一些表空間的基礎知識
共享表空間ibdata和用戶表空間是不一樣的,因為它需要存儲更多全局的一些信息,例如doublewrite,undo等等,所以共享表空間擁有更多的段
每個表空間都有一個唯一space id,因為很多地方都需要使用到這個id,例如內存數據刷到磁盤時,需要使用這個space id來尋找表空間文件.InnoDB總有一個"系統空間",即共享表空間,這個表系統表空間的space id始終為0.
頁空間結構
一個表空間文件被分為一個個16kb的頁,每個頁都有一個32位序號(page number),通常稱為偏移量,即離表空間初始位置的偏移量.因為每個頁大小為16kb,所以第0個頁的偏移量為0,第一個頁的偏移量為16384等等.因為32位的最大值為232,所以一個表空間的最大值為232*16kb=64TB.
共享表空間
共享表空間space id為0,包含了很多分配在固定偏移量上的頁,用來存儲和InnoDB操作相關的大量信息.系統表空間和其他空間一樣,也有FSP_HDR,IBUF_BITMAP和INODE頁,并且分配在前三個頁,但是第四個頁之后,和獨立表空間就不太一樣了
下面是分配的頁:
Page 3,type SYS:保存和插入緩存相關的信息
Page 4,type INDEX:插入緩存的索引結構的root頁,插入緩存也是一個B+樹;
Page 5,type TRX_SYS: 記錄和InnoDB事務操作相關的信息,例如最近一次的事務id,MySQL二進制日志信息以及兩次寫緩沖區的位置信息;
Page 6,type SYS: 第一個回滾段(rollback segment)頁,回滾段例外的頁或者整個區當需要的時候才會被分配出來存儲回滾段信息;
Page 7,type SYS: 存儲和數據字典相關的信息,包含了數據字典中所有表格root頁面的個數.當我們要訪問數據字典表頁時,就需要這些信息.
Pages 64-127: 第一個double write buffer頁塊(64塊,即一個extent).double write buffer是InnoDB的一個恢復機制;
Pages 128-191:double write buffer的第二個頁塊.
系統表空間其他頁則在索引,回滾段和undo logs等等需要時分配.
單獨表空間
從MySQL5.6開始,默認就是一個表,一個表空間文件.以.ibd后綴的文件就是單獨表空間文件,有如下結構
單獨表空間文件結構很簡單,沒有共享表空間存儲數據類型種類多;前三個頁分別是FSP_HDR,IBUF_BITMAP和INODE頁,從第四個頁開始存儲索引頁.在InnoDB中,B+樹內部節點頁和葉子節點頁統稱為索引頁(index pages).第四個頁固定為主鍵的root頁,其他頁則可能是內部節點頁,主鍵葉子節點,輔助索引root頁,輔助索引內部節點頁和輔助索引葉子節點頁.
因為大部分InnoDB系統信息都保存在共享表空間中,所以單獨表空間(per-table space)大部分都是type INDEX頁保存表數據.
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。
InnoDB 還沒有辦法對 ibdata1 文件表空間進行回收/收縮,一旦 ibdata1 文件的肚子被搞大了,只能把數據先備份后恢復再次重新初始化實例才能恢復原先的大小,或者把依次把各個獨立表空間文件備份恢復到一個新實例中,除此外,沒什么更好的辦法了。
相應的建議對策是:
- 升級到5.6及以上(64-bit),采用獨立undo表空間,5.6版本開始就支持獨立的undo表空間了,再也不用擔心會把 ibdata1 文件搞大;
- 初始化設置時,把 ibdata1 文件至少設置為1GB以上;
- 增加purge線程數,比如設置 innodb_purge_threads = 8;
- 提高file i/o能力,該上SSD的趕緊上;
- 事務及時提交,不要積壓;
- 默認打開autocommit = 1,避免忘了某個事務長時間未提交;
- 檢查開發框架,確認是否設置了 autocommit=0,記得在事務結束后都有顯式提交或回滾。
該FLUSH TABLES … FOR EXPORT語句確保對命名表的更改已刷新到磁盤,以便在實例運行時可以進行二進制表副本。當FLUSH TABLES … FOR EXPORT運行時, InnoDB產生了.cfg在同一個數據庫的目錄表文件。該.cfg文件包含導入表空間文件時用于模式驗證的元數據。
這個命令值得一提的是,保持當前的窗口,不要關閉,如果關閉,cfg文件就會自動刪除,可以看到命令運行后生成了cfg文件。
InnoDB表空間
MySQL · 引擎特性 · InnoDB 文件系統之文件物理結構
MySQL日志——Undo | Redo