概述
MySQL中臨時表主要有兩類,包括外部臨時表和內部臨時表。
臨時表
- 內部臨時表:內部臨時表主要有兩類
- 一類是information_schema中臨時表
- 另一類是會話執行查詢時,如果執行計划中包含有“Using temporary”時,會產生臨時表
- 外部臨時表:外部臨時表是通過語句create temporary table...創建的臨時表,臨時表只在本會話有效,會話斷開后,臨時表數據會自動清理。
區別:
內部臨時表與外部臨時表的一個區別在於,我們看不到內部臨時表的表結構定義文件frm。
而外部臨時表的表定義文件frm,一般是以#sql{進程id}_{線程id}_序列號組成,因此不同會話可以創建同名的臨時表。
臨時表的特點
臨時表VS普通表
臨時表與普通表的主要區別在於是否在實例,會話,或語句結束后,自動清理數據。比如,內部臨時表,我們在一個查詢中,如果要存儲中間結果集,而查詢結束后,臨時表就會自動回收,不會影響用戶表結構和數據。
另外就是,不同會話的臨時表可以重名,所有多個會話執行查詢時,如果要使用臨時表,不會有重名的擔憂。5.7引入了臨時表空間后,所有臨時表都存儲在臨時表空間(非壓縮)中,臨時表空間的數據可以復用。
臨時表並非只支持Innodb引擎,還支持myisam引擎,memory引擎等。因此,臨時表我們看不到實體(idb文件),但其實不一定是內存表,也可能存儲在臨時表空間中。
臨時表 VS 內存表
臨時表既可以innodb引擎表,也可以是memory引擎表。這里所謂的內存表,是說memory引擎表,通過建表語句create table ...engine=memory,數據全部在內存,表結構通過frm管理,同樣的內部的memory引擎表,也是看不到frm文件中,甚至看不到information_schema在磁盤上的目錄。
在MySQL內部,information_schema里面的臨時表就包含兩類:innodb引擎的臨時表和memory引擎的臨時表。比如TABLES表屬於memory臨時表,而columns,processlist,屬於innodb引擎臨時表。
內存表所有數據都在內存中,在內存中數據結構是一個數組(堆表),所有數據操作都在內存中完成,對於小數據量場景,速度比較快(不涉及物理IO操作)。
但內存畢竟是有限的資源,因此,如果數據量比較大,則不適合用內存表,而是選擇用磁盤臨時表(innodb引擎),這種臨時表采用B+樹存儲結構(innodb引擎),innodb的bufferpool資源是共享的,臨時表的數據可能會對bufferpool的熱數據有一定的影響,另外,操作可能涉及到物理IO。
memory引擎表實際上也是可以創建索引的,包括Btree索引和Hash索引,所以查詢速度很快,主要缺陷是內存資源有限。
使用內部臨時表
場景
我們先看一下什么時候會使用到內部臨時表?
Using temporary
測試表結構如下:
CREATE TABLE `test1` ( `sms_id` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `app_id` int(1) NOT NULL DEFAULT '0', `payment` double NOT NULL DEFAULT '0', KEY `sms_id` (`sms_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
前面提到執行計划中包含有時,會使用臨時表,這里列兩個主要的場景。
場景1:union 場景
union操作的含義是,取兩個子查詢結果的並集,重復的數據只保留一行,通過建立一個帶主鍵的臨時表,就可以解決“去重”問題,通過臨時表存儲最終的結果集,所以能看到執行計划中Extra這一項里面有“Using temporary”。
與union相關的一個操作是union all,后者也是將兩個子查詢結果合並,但不解決重復問題。所以對於union all,沒有“去重”的含義,因此也就不需要臨時表了。
場景2:group by
group by的含義是按指定列分組,並默認按照指定列有序。上面的SQL語句含義是將test1中的數據按app_id列分組,統計每種app_id列值的記錄數目。
從執行計划中看到了"Using temporary;Using filesort":
- 對於group by而言,我們首先需要統計每個值出現的數目,這就需要借助臨時表來快速定位,如果不存在,則插入一條記錄,如果存在,並累加計數,所以看到了"Using temporary";
- 然后又因為group by隱含了排序含義,所以還需要按照app_id列進行對記錄排序,所以看到了"Using filesort"。
1).消除filesort
實際上,group by也可以顯示消除“排序含義”。
可以看到,語句中加上“order by null”后,執行計划中,不再出現“Using filesort”。
2).消除臨時表
可以看到執行計划中已經沒有了“Using temporary”,所以group by並非一定依賴臨時表,臨時表在group by中的作用主要是“去重”。
所以,實際上有另外一種方式,不使用臨時表,直接利用sort_buffer排序(sort_buffer不夠時,進行文件排序,具體而言是每一個有序數組作為一個單獨文件,然后進行外排歸並),然后再掃描得到聚合后的結果集。
3).SQL_BIG_RESULT
同時我們語句中用到了“SQL_BIG_RESULT”這個hint,正是因為這個hint導致了我們沒有使用臨時表,先說說SQL_BIG_RESULT和SQL_SMALL_RESULT的含義。
-
SQL_SMALL_RESULT:顯示指定用內存表(memory引擎)
-
SQL_BIG_RESULT:顯示指定用磁盤臨時表(myisam引擎或innodb引擎)
兩者區別在於,使用磁盤臨時表可以借助主鍵做去重排序,適合大數據量;使用內存表寫入更快,然后在內存中排序,適合小數據量。下面是從MySQL手冊中摘錄的說明。
SQL_BIG_RESULT or SQL_SMALL_RESULT can be used with GROUP BY or DISTINCT to tell the optimizer that the result set has many rows or is small, respectively. For SQL_BIG_RESULT, MySQL directly uses disk-based temporary tables if needed, and prefers sorting to using a temporary table with a key on the GROUP BY elements. For SQL_SMALL_RESULT, MySQL uses fast temporary tables to store the resulting table instead of using sorting. This should not normally be needed.
使用外部臨時表
create temporary t1(...............)engine=innodb;
MySQL 要給這個 InnoDB 表創建一個 frm 文件保存表結構定義,還要有地方保存表數據。
這個 frm 文件放在臨時文件目錄下,文件名的后綴是.frm,前綴是“#sql{進程 id}_{線程 id}_序列號”。可以使用 select @@tmpdir 命令,來顯示實例的臨時文件目錄。
而關於表中數據的存放方式,在不同的 MySQL 版本中有着不同的處理方式:
- 在 5.6 以及之前的版本里,MySQL 會在臨時文件目錄下創建一個相同前綴、以.ibd 為后綴的文件,用來存放數據文件;
- 而從 5.7 版本開始,MySQL 引入了一個臨時文件表空間,專門用來存放臨時文件的數據。因此,就不需要再創建 ibd 文件了。
從文件名的前綴規則,可以看到,其實創建一個叫作 t1 的 InnoDB 臨時表,MySQL 在存儲上認為創建的表名跟普通表 t1 是不同的,因此同一個庫下面已經有普通表 t1 的情況下,還是可以再創建一個臨時表 t1 的。
舉例:

這個進程的進程號是 1234,session A 的線程 id 是 4,session B 的線程 id 是 5。所以你看到了,session A 和 session B 創建的臨時表,在磁盤上的文件不會重名。
MySQL 維護數據表,除了物理上要有文件外,內存里面也有一套機制區別不同的表,每個表都對應一個 table_def_key。
- 一個普通表的 table_def_key 的值是由“庫名 + 表名”得到的,所以如果你要在同一個庫下創建兩個同名的普通表,創建第二個表的過程中就會發現 table_def_key 已經存在了。
- 而對於臨時表,table_def_key 在“庫名 + 表名”基礎上,又加入了“server_id+thread_id”。
也就是說,session A 和 sessionB 創建的兩個臨時表 t1,它們的 table_def_key 不同,磁盤文件名也不同,因此可以並存。
在實現上,每個線程都維護了自己的臨時表鏈表。這樣每次 session 內操作表的時候,先遍歷鏈表,檢查是否有這個名字的臨時表,如果有就優先操作臨時表,如果沒有再操作普通表;在 session 結束的時候,對鏈表里的每個臨時表,執行 “DROP TEMPORARY TABLE + 表名”操作。
這時候你會發現,binlog 中也記錄了 DROP TEMPORARY TABLE 這條命令。你一定會覺得奇怪,臨時表只在線程內自己可以訪問,為什么需要寫到 binlog 里面?