1、事件背景
咱們的合做客戶,駐場人員報告說一個 RDS 實例出現磁盤不足的告警,須要排查。html
告警信息:mysql
告警內容:
數據庫 data 磁盤不足,磁盤占用 80% 以上
數據庫 binlog 磁盤不足,磁盤占用 80% 以上sql
2、排查過程
登錄告警的服務器,查看磁盤空間,並尋找大容量文件后,發現端口號為 4675 的實例臨時表空間 ibtmp1 的大小有 955G,致使磁盤被使用了 86%;數據庫
猜想和庫里執行長 SQL 有關系,產生了不少臨時數據,並寫入到臨時表空間。 服務器
看到有這樣一條 SQL,繼續分析它的執行計划;less
很明顯看到圖中標記的這一點為使用了臨時計算,說明臨時表空間的快速增加和它有關系。這條 SQL 進行了三表關聯,每一個表都有幾十萬行數據,三表關聯並無在 where 條件中設置關聯字段,造成了笛卡爾積,因此會產生大量臨時數據;並且都是全表掃描,加載的臨時數據過多;還涉及到排序產生了臨時數據;這幾方面致使 ibtmp1 空間快速爆滿。運維
3、解決辦法
和項目組溝通后,殺掉這個會話解決問題;優化
可是這個 SQL 停下來了,臨時表空間中的臨時數據沒有釋放;spa
最后經過重啟 mysql 數據庫,釋放了臨時表空間中的臨時數據,這個只能經過重啟釋放。操作系統
4、分析原理
經過查看官方文檔,官方是這么解釋的:
翻譯:
根據官網文檔的解釋,在正常關閉或初始化停止時,將刪除臨時表空間,並在每次啟動服務器時從新建立。重啟可以釋放空間的緣由在於正常關閉數據庫,臨時表空間就被刪除了,從新啟動后從新建立,也就是重啟引起了臨時表空間的重建,從新初始化,因此,重建后的大小為 12M。
從錯誤日志里能夠驗證上面的觀點:
5、官網對於 ibtmp1 大小的說明
6、如何避免
1. 對臨時表空間的大小進行限制,容許自動增加,但最大容量有上限,本例中因為 innodb_temp_data_file_path 設置的自動增加,但未設上限,因此致使 ibtmp1 有 955G。正確方法配置參數 innodb_temp_data_file_path:[mysqld]innodb_temp_data_file_path=ibtmp1:12M:autoextend:max:500M參考官方文檔:
設置了上限的大小,當數據文件達到最大大小時,查詢將失敗,並顯示一條錯誤消息,代表表已滿,查詢不能往下執行,避免 ibtmp1 過大。
2. 在發送例如本例中的多表關聯 SQL 時應確保有關聯字段並且有索引,避免笛卡爾積式的全表掃描,對存在 group by、order by、多表關聯的 SQL 要評估臨時數據量,對 SQL 進行審核,沒有審核不容許上線執行。
3. 在執行前經過 explain 查看執行計划,對 Using temporary 須要格外關注。
7、其余補充
1> 經過字典表查看執行的 SQL 產生臨時表、使用臨時表空間的狀況:查詢字典表:sys.x$statements_with_temp_tablesselect * from sys.x$statements_with_temp_tables where query like 'select%' and db='test' order by tmp_tables_to_disk_pct,disk_tmp_tables descG;
查詢字典表:sys.statements_with_temp_tablesselect * from sys.statements_with_temp_tables where query like 'select%' and db='test' order by tmp_tables_to_disk_pct,disk_tmp_tables descG;
這兩個表查詢的結果是同樣的,各列含義以下:
query:規范化的語句字符串。
db:語句的默認數據庫, NULL 若是沒有。
exec_count:語句已執行的總次數。
total_latency:定時出現的語句的總等待時間。
memory_tmp_tables:由該語句的出現建立的內部內存臨時表的總數。disk_tmp_tables:由該語句的出現建立的內部磁盤臨時表的總數。
avg_tmp_tables_per_query:每次出現該語句建立的內部臨時表的平均數量。
tmp_tables_to_disk_pct:內部內存臨時表已轉換為磁盤表的百分比。
first_seen:第一次看到該聲明的時間。
last_seen:最近一次發表該聲明的時間。
digest:語句摘要。
參考連接:https://dev.mysql.com/doc/ref...
經過字典表 tmp_tables_to_disk_pct 這一列結果可知,內存臨時表已轉換為磁盤表的比例是 100%,說明經過復現這個查詢,它的臨時計算結果已經都放到磁盤上了,進一步證實這個查詢和臨時表空間容量的快速增加有關系。
2> 對於 mysql5.7 中 kill 掉運行長 SQL 的會話,ibtmp1 容量卻沒有收縮問題的調研;來源連接:http://mysql.taobao.org/month...
從文章中的解釋看,會話被殺掉后,臨時表是釋放的,只是在 ibtmp1 中打了刪除標記,空間並無還給操做系統,只有重啟才能夠釋放空間。
3> 下面,進一步用 mysql8.0 一樣跑一下這個查詢,看是否有什么不一樣;mysql 版本:8.0.18
當這個 sql 將磁盤跑滿以后,發現與 5.7 不一樣的是這個 SQL 產生的臨時數據保存到了 tmpdir,mysql5.7 是保存在 ibtmp1 中,並且因為磁盤滿,SQL 執行失敗,很快磁盤空間就釋放了;
問題:如何使用到 8.0 版本的臨時表空間?
經過查看 8.0 的官方文檔得知,8.0 的臨時表空間分為會話臨時表空間和全局臨時表空間,會話臨時表空間存儲用戶建立的臨時表和當 InnoDB 配置為磁盤內部臨時表的存儲引擎時由優化器建立的內部臨時表,當會話斷開鏈接時,其臨時表空間將被截斷並釋放回池中;也就是說,在 8.0 中有一個專門的會話臨時表空間,當會話被殺掉后,能夠回收磁盤空間;而原來的 ibtmp1 是如今的全局臨時表空間,存放的是對用戶建立的臨時表進行更改的回滾段,在 5.7 中 ibtmp1 存放的是用戶建立的臨時表和磁盤內部臨時表;
也就是在 8.0 和 5.7 中 ibtmp1 的用途發生了變化,5.7 版本臨時表的數據存放在 ibtmp1 中,在 8.0 版本中臨時表的數據存放在會話臨時表空間,若是臨時表發生更改,更改的 undo 數據存放在 ibtmp1 中;
實驗驗證:將以前的查詢結果保存成臨時表,對應會話是 45 號,經過查看對應字典表,可知 45 號會話使用了 temp_8.ibt 這個表空間,經過把查詢保存成臨時表,能夠用到會話臨時表空間,以下圖:
下一步殺掉 45 號會話,發現 temp_8.ibt 空間釋放了,變為了初始大小,狀態為非活動的,證實在 mysql8.0 中能夠經過殺掉會話來釋放臨時表空間。
總結:在 mysql5.7 時,殺掉會話,臨時表會釋放,可是僅僅是在 ibtmp 文件里標記一下,空間是不會釋放回操做系統的。若是要釋放空間,須要重啟數據庫;在 mysql8.0 中能夠經過殺掉會話來釋放臨時表空間。
8、參考文檔
https://dev.mysql.com/doc/ref...://dev.mysql.com/doc/refman/8.0/en/innodb-temporary-tablespace.html http://mysql.taobao.org/month...