MySQL · 引擎特性 · 臨時表那些事兒

前言

相比於普通的用戶數據表,MySQL/InnoDB中的臨時表,你們應該會陌生不少。再加上不一樣的臨時表建立的時機和建立的位置都不固定,這也進一步加大神祕感。最讓人捉摸不透的是,臨時表不少時候會先建立文件,而後什麼都不作,就把文件刪除,留一個句柄讀寫,給人的感受是神龍見首不見尾。本文分析了詳細MySQL各個版本臨時表的處理方式,但願對你們有所幫助。mysql

綜述

準確的說,咱們常說的臨時表分爲兩種,一種真的是表,用來存儲用戶發送的數,讀寫走的是表讀寫接口,讀寫的時候表必定在文件系統上存在,另一種,應該是一種臨時文件,用來存儲SQL計算中間過程的數據,讀寫走的是文件讀寫接口,讀寫的時候文件可能已經被刪除了,留一個文件句柄進行操做。算法

臨時表

臨時表能夠分爲磁盤臨時表和內存臨時表,而臨時文件,只會存在於磁盤上,不會存在於內存中。具體來講,臨時表的內存形態有Memory引擎和Temptable引擎,主要區別是對字符類型(varchar, blob,text類型)的存儲方式,前者無論實際字符多少,都是用定長的空間存儲,後者會用變長的空間存儲,這樣提升了內存中的存儲效率,有更多的數據能夠放在內存中處理而不是轉換成磁盤臨時表。Memory引擎從早期的5.6就可使用,Temptable是8.0引入的新的引擎。另一方面,磁盤臨時表也有三種形態,一種是MyISAM表,一種是InnoDB臨時表,另一種是Temptable的文件map表。其中最後一種方式,是8.0提供的。sql

在5.6以及之前的版本,磁盤臨時表都是放在數據庫配置的臨時目錄,磁盤臨時表的undolog都是與普通表的undo放在一塊兒(注意因爲磁盤臨時表在數據庫重啓後就被刪除了,不須要redolog經過奔潰恢復來保證事務的完整性,因此不須要寫redolog,可是undolog仍是須要的,由於須要支持回滾)。數據庫

在MySQL 5.7後,磁盤臨時表的數據和undo都被獨立出來,放在一個單獨的表空間ibtmp1裏面。之因此把臨時表獨立出來,主要是爲了減小建立刪除表時維護元數據的開銷。緩存

在MySQL 8.0後,磁盤臨時表的數據單獨放在Session臨時表空間池(#innodb_temp目錄下的ibt文件)裏面,臨時表的undo放在global的表空間ibtmp1裏面。另一個大的改進是,8.0的磁盤臨時表數據佔用的空間在鏈接斷開後,就能釋放給操做系統,而5.7的版本中須要重啓才能釋放。函數

目前有如下兩種狀況會用到臨時表:性能

用戶顯式建立臨時表

這種是用戶經過顯式的執行命令create temporary table建立的表,引擎的類型要麼顯式指定,要麼使用默認配置的值(default_tmp_storage_engine)。內存使用就遵循指定引擎的內存管理方式,好比InnoDB的表會先緩存在Buffer Pool中,而後經過刷髒線程寫回磁盤文件。測試

在5.6中,磁盤臨時表位於tmpdir下,文件名相似#sql4d2b_8_0.ibd,其中#sql是固定的前綴,4d2b是進程號的十六進制表示,8是MySQL線程號的十六進制表示(show processlist中的id),0是每一個鏈接從0開始的遞增值,ibd是innodb的磁盤臨時表(經過參數default_tmp_storage_engine控制)。在5.6中,磁盤臨時表建立好後,對應的frm以及引擎文件就在tmpdir下建立完畢,能夠經過文件系統ls命令查看到。在鏈接關閉後,相應文件自動刪除。所以,咱們若是在5.6的tmpdir裏面看到不少相似格式文件名,能夠經過文件名來判斷是哪一個進程,哪一個鏈接使用的臨時表,這個技巧在排查tmpdir目錄佔用過多空間的問題時,尤爲適用。用戶顯式建立的這種臨時表,在鏈接釋放的時候,會自動釋放並把空間釋放回操做系統。臨時表的undolog存在undo表空間中,與普通表的undo放在一塊兒。有了undo回滾段,用戶建立的這種臨時表也能支持回滾了。優化

在5.7中,臨時磁盤表位於ibtmp文件中,ibtmp文件位置及大小控制方式由參數innodb_temp_data_file_path控制。顯式建立的表的數據和undo都在ibtmp裏面。用戶鏈接斷開後,臨時表會釋放,可是僅僅是在ibtmp文件裏面標記一下,空間是不會釋放回操做系統的。若是要釋放空間,須要重啓數據庫。另外,須要注意的一點是,5.6能夠在tmpdir下直接看到建立的文件,可是5.7是建立在ibtmp這個表空間裏面,所以是看不到具體的表文件的。若是須要查看,則須要查看INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO這個表,裏面有一列name,這裏能夠看到表名。命名規格與5.6的相似,所以也能夠快速找到佔用空間大的鏈接。spa

在8.0中,臨時表的數據和undo被進一步分開,數據是存放在ibt文件中(由參數innodb_temp_tablespaces_dir控制),undo依然存放在ibtmp文件中(依然由參數innodb_temp_data_file_path控制)。存放ibt文件的叫作Session臨時表空間,存放undo的ibtmp叫作Global臨時表空間。這裏介紹一下這個存放數據的Session臨時表空間。Session臨時表空間,在磁盤上的表現是一組以ibt文件組成的文件池。啓動的時候,數據庫會在配置的目錄下從新建立,關閉數據庫的時候刪除。啓動的時候,默認會建立10個ibt文件,每一個鏈接最多使用兩個,一個給用戶建立的臨時表用,另一個給下文描述的優化器建立的隱式臨時表使用。固然只有在須要臨時表的時候,纔會建立,若是不須要,則不會佔用ibt文件。當10個ibt都被使用完後,數據庫會繼續建立,最多建立四十萬個。當鏈接釋放時候,會自動把這個鏈接使用的ibt文件給釋放,同時回收空間。若是要回收Global臨時表空間,依然須要重啓。可是因爲已經把存放數據的文件分離出來,且其支持動態回收(即鏈接斷開即釋放空間),因此5.7上困擾你們多時的空間佔用問題,已經獲得了很好的緩解。固然,仍是有優化空間的,例如,空間須要在鏈接斷開後,才能釋放,而理論上,不少空間在某些SQL(如用戶drop了某個顯式建立的臨時表)執行後,便可以釋放。另外,若是須要查看錶名,依然查看INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO這個表。須要注意的是,8.0上,顯式臨時表不能是壓縮表,而5.6和5.7能夠。

優化器隱式建立臨時表

這種臨時表,是數據庫爲了輔助某些複雜SQL的執行而建立的輔助表,是否須要臨時表,通常都是由優化器決定。與用戶顯式建立的臨時表直接建立磁盤文件不一樣,若是須要優化器以爲SQL須要臨時表輔助,會先使用內存臨時表,若是超過配置的內存(min(tmp_table_size, max_heap_table_siz)),就會轉化成磁盤臨時表,這種磁盤臨時表就相似用戶顯式建立的,引擎類型經過參數internal_tmp_disk_storage_engine控制。通常稍微複雜一點的查詢,包括且不限於order by, group by, distinct等,都會用到這種隱式建立的臨時表。用戶能夠經過explain命令,在Extra列中,看是否有Using temporary這樣的字樣,若是有,就確定要用臨時表。

在5.6中,隱式臨時表依然在tmpdir下,在複雜SQL執行的過程當中,就能看到這臨時表,一旦執行結束,就被刪除。值得注意的是,5.6中,這種隱式建立的臨時表,只能用MyISAM引擎,即沒有internal_tmp_disk_storage_engine這個參數能夠控制。因此,當咱們的系統中只有innodb表時,也會看到MyISAM的某些指標在變更,這種狀況下,通常都是隱式臨時表的緣由。

在5.7中,隱式臨時表是建立在ibtmp文件中的,SQL結束後,會標記刪除,可是空間依然不會返還給操做系統,若是須要返還,則須要重啓數據庫。另外,5.7支持參數internal_tmp_disk_storage_engine,用戶能夠選擇InnoDB或者MYISAM表做爲磁盤臨時表。

在8.0中,隱式臨時表是建立在Session臨時表空間中的,即與用戶顯式建立的臨時表的數據放在一塊兒。若是一個鏈接第一次須要隱式臨時表,那麼數據庫會從ibt文件構成的池子中取出一個給這個鏈接使用,直到鏈接釋放。上文中,咱們也提到過,在8.0中,用戶顯式建立的臨時表也會從池子中分配一個ibt來使用,每一個鏈接最多使用兩個ibt文件用來存儲臨時表。咱們能夠查詢INFORMATION_SCHEMA.INNODB_SESSION_TEMP_TABLESPACES來肯定ibt文件的去向。這個表中,每一個ibt文件是一行,當前系統中有幾個ibt文件就有幾行。有一列叫作ID,若是此列爲0,表示此ibt沒有被使用,若是非0,表示被此ID的鏈接在用,好比ID爲8,則表示process_id爲8的鏈接在用這個ibt文件。另外,還有一列purpose,值爲INTRINSIC表示是隱式臨時表在用這個ibt,USER則表示是顯示臨時表在用。此外,還有一列size,表示當前的大小。用戶能夠查詢這個表來肯定整個數據庫臨時表的使用狀況,十分方便。

在5.6和5.7中,內存臨時表只能使用Memory引擎,到了8.0,多了一種Temptable引擎的選擇。Temptable在存儲格式有采用了變長存儲,能夠節省存儲空間,進一步提升內存使用率,減小轉換成磁盤臨時表的次數。若是設置的磁盤臨時表是InnoDB或者MYISAM,則須要一個轉換拷貝的消耗。爲了儘量減小消耗,Temptable提出了一種overflow機制,即若是內存臨時表超過配置大小,則使用磁盤空間map的方式,即打開一個文件,而後刪除,留一個句柄進行讀寫操做。讀寫文件格式和內存中格式同樣,這樣就略過了轉換這一步,進一步提升性能。注意,這個功能是在還沒發佈的8.0.16版本中才有的,由於還看不到代碼,只能經過文檔猜想其實現。在8.0.16中,參數internal_tmp_disk_storage_engine已經被去掉,磁盤臨時表只能使用InnoDB形式或者TempTable的這種overflow形式。從文檔中,咱們彷佛看出官方比較推薦使用TempTable這個新的引擎。具體性能提高狀況,還須要等代碼發佈後,測試過才能得出結論。

臨時文件

相比臨時表,臨時文件對你們可能更加陌生,臨時文件更多的被使用在緩存數據,排序數據的場景中。通常狀況下,被緩存或者排序的數據,首先放在內存中,若是內存放不下,纔會使用磁盤臨時文件的方式。臨時文件的使用方式與通常的表也不太同樣,通常的表建立完後,就開始讀寫數據,使用完後,才把文件刪除,可是臨時文件的使用方式不同,在建立完後(使用mkstemp系統函數),立刻調用unlink刪除文件,可是不close文件,後續使用原來的句柄操做文件。這樣的好處是,當進程異常crash,不會有臨時文件由於沒被刪除而殘留,可是壞處也是明顯的,咱們在文件系統上使用ls命令就看不到這個文件,須要使用lsof +L1來查看這種deleted屬性的文件。

目前,咱們主要在一下場景使用臨時文件:

DDL中的臨時文件

在作online DDL的過程當中,不少操做須要對原表進行重建,對錶重建前,須要對各類二級索引排序,而大量數據的排序,不太可能在內存中完成,須要依賴外部排序算法,MySQL使用了歸併排序。這個過程當中就須要建立臨時文件。通常須要的空間大小與原表差很少。可是在使用完以後,會立刻清理,因此在作DDL的時候,須要保留出足夠的空間。用戶能夠經過指定innodb_tmpdir來指定這種排序文件的路徑。這個參數能夠動態修改,通常把他設置在有足夠磁盤空間的路徑上。臨時文件的名字通常是相似ibXXXXXX,其中ib是固定前綴,XXXXXX是大小寫字母以及數字的隨機組合。

在作online DDL中,咱們是容許用戶對原表作DML操做的,即增刪改查。咱們不能直接插入原表中,所以須要一個地方記錄對原表的修改操做,在DDL結束後,再應用在新表上。這個記錄的地方就是online log,固然若是改動少的話,直接存在內存裏(參數innodb_sort_buffer_size可控制,同時這個參數也控制online log每一個讀寫塊的大小)面便可。這個onlinelog也是用臨時文件存,建立在innodb_tmpdir,最大大小爲參數innodb_online_alter_log_max_size控制,若是超過這個大小了,DDL就會失敗。臨時文件的名字也相似上述的排序臨時文件的名字。

在online DDL的最後階段,須要把排序完的文件和中途產生的DML全都應用到一箇中間文件上,中間文件文件名相似#sql-ib53-522550444.ibd,其中#sql-ib是固定的前綴,53是InnoDB層的table id,522550444是隨機生成的數字。同時,在server層也會生成一個frm文件(8.0中沒有),文件名相似#sql-4d2b_2a.frm,其中#sql是固定前綴,4d2b是進程號的十六進制表示,2a是線程號的十六進制表示(show processlist中的id)。所以咱們也能夠經過這個命名規則來找到哪一個線程在作DDL。這裏須要注意一點,這裏說的中間文件,其實算是一個臨時表,並非上文說中臨時文件,這些中間文件能夠經過ls來查看。當在DDL中的最後一步,會把這兩個臨時文件命名回原來的表名。正由於這個特性,因此當數據庫中途crash的時候,可能會在磁盤上留下殘餘無用的文件。遇到這種狀況,能夠先把frm文件重命名成與ibd文件同樣的名字,而後使用DROP TABLE#mysql50##sql-ib53-522550444`來清理殘餘的文件。注意,若是不用drop命令,直接刪除ibd文件,可能會致使數據字典裏面依然有殘餘的信息,作法不太優雅。固然,在8.0中,因爲使用了原子的數據字典,就不會出現這種殘餘文件了。

BinLog中的緩存操做

BinLog只有在事務提交的時候纔會寫入到文件中,在沒提交前,會先放在內存中(由參數binlog_cache_size控制),若是內存放慢了,就會建立臨時文件,使用方法也是先經過mkstemp建立,而後直接unlink,留一個句柄讀寫。臨時文件名相似MLXXXXXX,其中ML是固定前綴,XXXXXX是大小寫字母以及數字的隨機組合。單個事務的BinLog太大,可能會致使整個BinLog的大小也過大,從而影響同步,所以咱們須要儘量控制事務大小。

優化建立的臨時文件

有些操做,除了在引擎層須要依賴隱式臨時表來輔助複雜SQL的計算,在Server層,也會建立臨時文件來輔助,好比order by操做,會調用filesort函數。這個函數也會先使用內存(sort_buffer_size)排序,若是不夠,就會建立一個臨時文件,輔助排序。文件名相似MYXXXXXX,其中MY是固定前綴,XXXXXX是大小寫字母以及數字的隨機組合。

Load data中用的臨時文件

在BinLog複製中,若是在主庫上使用了Load Data命令,即從文件中導數據,數據庫會把整個文件寫入到RelayLog中,而後傳到備庫,備庫解析RelayLog,從中抽取出對應的Load文件,而後在備庫上應用。備庫上這個文件存儲的位置由參數slave_load_tmpdir控制。文檔中建議這個目錄不要配置在物理機的內存目錄或者重啓後會刪除的目錄。由於複製依賴這個文件,若是意外被刪除,會致使複製中斷。

其餘

除了上文所述的幾個地方外,還有其餘幾個地方也會用到臨時文件:

  • 在InnoDB層,啓動的時候會建立多個臨時文件用來存儲:最後一次外鍵或者惟一鍵錯誤; 最後一次死鎖的信息; 最後的innodb狀態信息。用臨時文件而不用內存的緣由猜想是,內存使用率不會由於寫這些指標而波動。
  • 在Server層,分區表使用show create table時,會用到臨時文件。另外在MYISAM表內部排序的時候也會用到臨時文件。

相關參數

*** tmpdir: *** 這個參數是臨時目錄的配置,在5.6以及以前的版本,臨時表/文件默認都會放在這裏。這個參數能夠配置多個目錄,這樣就能夠輪流在不一樣的目錄上建立臨時表/文件,若是不一樣的目錄分別指向不一樣的磁盤,就能夠達到分流的目的。
*** innodb_tmpdir: *** 這個參數只要是被DDL中的排序臨時文件使用的。其佔用的空間會很大,建議單獨配置。這個參數能夠動態設置,也是一個Session變量。
*** slave_load_tmpdir: *** 這個參數主要是給BinLog複製中Load Data時,配置備庫存放臨時文件位置時使用。由於數據庫Crash後還須要依賴Load數據的文件,建議不要配置重啓後會刪除數據的目錄。
*** internal_tmp_disk_storage_engine: *** 當隱式臨時表被轉換成磁盤臨時表時,使用哪一種引擎,默認只有MyISAM和InnoDB。5.7及之後的版本才支持。8.0.16版本後取消的這個參數。
*** internal_tmp_mem_storage_engine: *** 隱式臨時表在內存時用的存儲引擎,能夠選擇Memory或者Temptable引擎。建議選擇新的Temptable引擎。
*** default_tmp_storage_engine: *** 默認的顯式臨時表的引擎,即用戶經過SQL語句建立的臨時表的引擎。
*** tmp_table_size: *** min(tmp_table_size,max_heap_table_size)是隱式臨時表的內存大小,超過這個值會轉換成磁盤臨時表。
*** max_heap_table_size: *** 用戶建立的Memory內存表的內存限制大小。
*** big_tables: *** 內存臨時錶轉換成磁盤臨時表須要有個轉化操做,須要在不一樣引擎格式中轉換,這個是須要消耗的。若是咱們能提早知道執行某個SQL須要用到磁盤臨時表,即內存確定不夠用,能夠設置這個參數,這樣優化器就跳過使用內存臨時表,直接使用磁盤臨時表,減小開銷。
*** temptable_max_ram: *** 這個參數是8.0後纔有的,主要是給Temptable引擎指定內存大小,超過這個後,要麼就轉換成磁盤臨時表,要麼就使用自帶的overflow機制。
*** temptable_use_mmap: *** 是否使用Temptable的overflow機制。

總結建議

MySQL的臨時表以及臨時文件實際上是一個比較複雜的話題,涉及的模塊比較多,出現的時機比較難把握,致使排查問題相比普通表也難很多。建議讀者結合代碼細細研究,這樣才能定位在線上可能出現的棘手問題。

相關文章
相關標籤/搜索