爲何咱們要儘可能避免FileSort(文件排序)

故事

如今,假設閱讀此文的你穿越回了小學二年級的時光,此時的你正在不斷的追求着隔壁班的班長小紅,巴不得把家裏全部東西都送給TA。那麼問題來了,若是你要把家裏東西都搬光送給小紅,你有幾種辦法?如下是我想到 mysql

  • 一件一件的搬,若是搬不動那就拆分(不排除你被你父母揍一頓的可能性)
  • 試圖經過吃藥讓本身變成大力士

上述例子看似滑稽,但其實這是一直以來人類解決大規模數量問題的解決方案,即要麼提高自身的能力以應付大規模的數量,要麼進行拆分,分而治之。git

對應到IT行業,因爲傳統小型機處理能力有限因而便有了大型機。若是不用大型機那咋辦嗎?只好拆分服務,因而便有了微服務。github

經典面試題

面試官:假設你只有100M的內存可用,如今有一個大小爲1G的文件,裏面存放着整數,每一個整數用4個字節來存儲,要你對這個這個文件中數據進行排序,你有什麼解決方案?web

:打電話找行政的妹子跟她要一條8G的DDR4內存條,爲了表示感謝順便約她去吃飯, 說不定還能順利脫單。面試

面試官:emmm….., 回去等通知吧算法

解決方案

:要解決這個問題,首先咱們須要分爲兩種狀況:sql

  • 數據不重複 若是數據不重複咱們可使用位圖來標記相應的數據,在須要輸出結果的時候遍歷位圖便可(此方案較爲簡單,不在本文的討論範圍內)數據庫

  • 數據重複 因爲只有100M的內存可用,徹底利用這100M內存的狀況下意味着咱們一次能夠對26214400個整數(100 * 1024 * 1024 / 4 ) 進行排序, 這意味着咱們要分次讀取文件並對讀取的內容進行排序,並將每一次排序的結果保存到文件系統中,以後再對這些文件進行合併。設計模式

面試官:能夠用畫圖表示一下嗎?數組

:過程以下圖所示

面試官:能夠,要不你現場寫一下代碼吧

解決方案的實現

解決方案的實現總的來講有如下幾步

  • 根據緩衝區的大小讀入相應的數據量,並把他們轉爲整數數組,進行排序,並寫入文件,重複這一步直到原始數據文件中沒有數據可讀。

  • 合併這些已排序的文件直到只剩一個文件

將問題拆分開來看的話,咱們須要解決如下子問題

  • 因爲咱們採用4個字節的數據來保存整數,所以咱們須要解決整數按字節存取的問題

你能夠考慮一下爲何咱們要用四個字節來存取整數?而不是將其轉爲字符串

  • 合併已排序的文件的算法

方案1、預讀取一部分的數據寫入緩存中,而後進行歸併排序(拆分以後的文件中的數據都是有序的),當數據用完時再去文件中讀取,重複此步驟直到沒有數據可讀

方案2、每次只從兩個文件中讀取一個整數,進行比較,而後將較大/較小(取決於你要增序仍是降序)的數據寫入文件中

方案一,相對來講比較簡單而且速度比較快留給你們實現。

對於方案二,因爲最近開發中有涉及狀態機,所以對於方案二我採用了狀態機的設計模式來實現。

該狀態機以下所示

外部排序的實現

給大夥提供個參考,我實現的方案還有進一步優化的空間😄

測試

爲了有一個直觀的印象,咱們對一個16MB的文件進行排序,緩衝區設置爲512kb.

如下爲測試結果

  • 文件分割階段,能夠看出文件分割的時候所用時間都是差很少的

  • 合併階段,能夠看出合併已排序的文件所用的耗時是不斷遞增的由於併合並的文件體積在不斷的遞增

若是咱們直接將緩衝區設置爲16MB呢?如下爲測試結果,連合並階段都不用了。

面試官: 很好,那你能說出應用場景嗎?

:利用文件(file)進行排序(sort)工做 = filesort,好像在哪裏見過…

面試官: 提示你一個單詞explain

FileSort

:想起來了,假設咱們有一張表

CREATE TABLE `users` (
  `id` int(11NOT NULL,
  `account` varchar(45COLLATE utf8mb4_bin DEFAULT NULL,
  `nickname` varchar(45COLLATE utf8mb4_bin DEFAULT NULL,
  `password` varchar(45COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
複製代碼

若是咱們須要根據某一個字段繼續排序而且沒有添加索引的話,那麼使用explain對該SQL進行查詢的話就會在Extra中看到filesort

以下圖所示

這意味着,MySQL沒法根據索引對數據進行排序(若是有索引的話直接取就行了,不須要排序操做)。

只好對要排序字段的進行排序了,可是生產環境中數據量可能會很是大,若是所有加載到內存中,必然會引發內存不足進而致使數據庫崩潰,所以必須劃出一塊專門的內存區域以供排序,而這塊內存區域極可能裝不下這巨大的數據量,必然要藉助外部文件系統進行排序,這就是filesort的由來

面試官:很好,那你知道怎麼看這塊內存的大小嗎?

: 緩衝區 = buffer,根據mysql的一向傳統,如下語句應該能夠查到

show variables like '%buffer%'
複製代碼

(圖中藍色標註的區域,即 sort_buffer_size)

面試官:很好,那你知道怎麼優化嗎?

: 加索引唄,還能咋樣,要不叫運維給服務器再加個內存條?或者把牙膏廠(Intel)的CPU換成農廠的CPU(AMD!YES)

面試官:只要你喜歡AMD,咱們就是異父異母的親兄弟。哦,不對我是想問該怎麼加索引

:咱們知道索引是有順序的,若是索引上信息已經知足了咱們的需求,那麼就不須要使用filsort了。

好比上文中所提到的users表
咱們建立了一個index

alter table users add index(nickname, account)
複製代碼

考慮如下語句是否須要filesort

select nickname,account from users order by nickname
複製代碼
select * from users order by nickname
複製代碼
select * from users order by account
複製代碼

答案是

  • 第一條語句不須要filesort,由於索引中已經包含了咱們所須要的信息
  • 第二條語句能夠直接使用索引(索引有序存儲),在讀取到索引對應的主鍵值後取相應的數據並直接返回給客戶端便可,不須要使用到sort_buffer
  • 第三條語句須要filesort, 但因爲account和nickname組合成了索引,每個nickname對應的account都是有序,所以不一樣的nickname對應的account能夠用來作歸併排序(如上文所提到的合併階段)

總結

今天的總結就三張圖

附錄

Q1: 爲何用4個字節來存整數

節省空間,用字符串來存的話,你整數多長就得多少個字節

Q2: 怎麼使用本文提供得外部排序DEMO


源碼中的三個文件分別是

  • 打印文件中的數據
  • 對指定文件進行排序
  • 生成隨機數文件

Q3: 爲何要使用狀態機來實現歸併排序

不得不說,用狀態機來梳理邏輯是比較清晰的,建議你也嘗試一下。但在本例中若是你使用緩衝區來保存整數數組的話性能會快不少。

參考資料

《高性能MySQL(第三版)》

索引相關的部分

《MySQL王者晉級之路》

3.4節

相關文章
相關標籤/搜索