架構設計:系統存儲(5)——MySQL數據庫性能優化(1)

接上文:《架構設計:系統存儲(4)——塊存儲方案(4)mysql

一、MySQL概述

從本文開始咱們將討論創建在塊存儲方案之上的關係型數據庫的性能優化方案和集羣方案。關係型數據庫的選型將以創業公司、互聯網行業使用最普遍的MySQL數據爲目標,可是MySQL的安裝過程和基本使用方法等知識並不在咱們討論的範圍內。後續幾篇文章咱們首先討論影響單個MySQL節點性能的主要因素,而後介紹MySQL讀寫分離、數據表橫縱拆分的原理和技術方案。web

MySQL數據庫目前已被Oracle收購,並發展處多個版本。目前使用最普遍且免費的MySQL版本是MySQL Community(社區版),另外還有三個付費的MySQL版本MySQL Standard(MySQL標準版)、MySQL Enterprise(MySQL企業版)、MySQL Cluster(MySQL集羣版),這三個版本是按照CPU內核進行費用計算,而且價格由低到高。最後Oracle還提供了兩個微型的MySQL版本:MySQL Classic(經典版),這個版本的MySQL只提供了MyISAM存儲引擎可是安裝快速,佔用空間較少;MySQL Embedded(嵌入式版本),這個版本的競爭軟件是SQLite。雖然社區版本是免費的而且這個版本提供的功能也沒有企業級版本豐富,一樣的硬件條件下單節點性能也沒有企業基本版優秀。可是咱們能夠藉助社區版本自身提供的功能和一些第三方軟件配合使用,搭建起相對廉價且性能不俗的MySQL數據庫集羣。sql

二、數據庫引擎的選擇

MySQL數據庫中最重要的一個概念就是數據庫引擎,不一樣的數據庫引擎的工做原理存在很大差別最終形成MySQL數據庫服務的性能差別。例如若是數據庫引擎須要支持事務,就必須知足事務的基本特性——AICD特性(AICD:原子性、隔離性、一致性和永久性。屬於基礎知識因此不在這裏贅述),那麼天然就須要必定處理機制來實現這些特性。這樣作的現實效果就是致使寫入一樣數據量的狀況下,支持事務的數據庫引擎比不支持事務的數據庫引擎耗費更多的時間。這裏咱們首先爲讀者列舉MySQL數據庫社區版中支持的數據庫引擎(部分):數據庫

  • MEMORY:MEMORY存儲引擎將表的數據徹底存放在內存中。在MySQL數據庫的歷史版本中和該數據庫引擎相似的其它引擎是HEAP,後者曾是MySQL數據庫中訪問速度最快的數據庫引擎。但因爲這兩種數據庫引擎徹底工做在內存中,因此若是MySQL或者服務器從新啓動,數據庫引擎中保存的數據將會丟失。安全

  • BLACKHOLE:中文名「黑洞」,使用BLACKHOLE數據庫引擎的數據表不存儲任何數據,只根據數據庫操做過程記錄二進制日誌。它的主要做用是做爲MySQL主從複製的中繼器,而且能夠在其上面添加業務過濾機制。性能優化

  • MyISAM:MyISAM數據庫引擎是MySQL數據庫默認的數據庫引擎。MyISAM使用一種表格鎖定的機制,來優化多個併發的讀寫操做(實際上就是使用的一種避免數據髒讀的機制)。可是這種機制對存儲空間的使用有必定的浪費。MyISAM還有一些有用的擴展,例如用來修復數據庫文件的MYISAMCHK工具和用來恢復浪費空間的MYISAMPACK工具。本文所介紹的MySQL數據庫相關技術將不涉及到這種數據庫引擎。服務器

  • InnoDB:InnoDB數據庫引擎是在各類版本的MySQL數據庫中使用最普遍的一種數據庫引擎,本文後續的介紹中若是沒有特別說明都默認是在說InnoDB數據庫引擎。InnoDB數據庫引擎使用日誌機制提供事務的支持。架構

三、基本I/O性能

要了解MySQL數據庫中的性能問題,就首先要搞清楚在客戶端向MySQL數據庫提交一個事務操做時後者到底作了些什麼事情,以及主要是怎麼作的。本節所描述的工做過程主要圍繞InnoDB數據庫引擎進行:併發

這裏寫圖片描述

上圖中筆者只畫出了InnoDB數據庫引擎在insert/update一個事務的過程當中所涉及的重要工做區域,InnoDB的實際工做細節要比上圖所示的步驟複雜得多。上文已經說到InnoDB數據庫引擎是一個支持事務的數據庫引擎,那麼如何解決異常崩潰狀況下的數據一致性問題就是它的設計中最重要的任務之一。InnoDB數據庫引擎採用日誌來解決這個問題,請注意這裏說的InnoDB數據庫引擎日誌,並非MySQL數據庫全局的二進制日誌。InnoDB數據庫引擎日誌還有另一個名字:重作日誌(redo log),這是由於這部分日誌主要的做用就是在數據庫異常崩潰並重啓後進行InnoDB引擎中數據的恢復。svg

爲了提升MySQL數據庫的性能,InnoDB數據庫引擎的數據操做過程基本上都在內存中完成,而後經過必定的策略(後文會詳細介紹)將InnoDB Log Buffer內存區域中的日誌數據同步到磁盤上的InnoDB File Log Group區域。InnoDB File Log Group區域主要用於存儲InnoDB數據庫引擎的日誌文件,它由多個大小相同的日誌文件構成而且這些文件都採用順序讀寫。innodb_log_file_size參數將決定每一個文件的大小,而innodb_log_files_in_group參數將決定整個日誌組中有多少個日誌文件。

當MySQL數據庫完成初始化過程後這些日誌文件將會按照參數的設置值,在磁盤上預佔一個連續的磁盤空間。這樣作的現象就是雖然數據庫中尚未任何數據,可是日誌文件的總大小就已是 innodb_log_file_size * innodb_log_files_in_group所獲得的數值了:

# InnoDB數據庫引擎 日誌文件示例
.... total 1.0G -rw-rw---- 1 mysql mysql 500M May 4 06:09 ib_logfile0 -rw-rw---- 1 mysql mysql 500M May 4 06:09 ib_logfile1 ....

這樣作的目的是保證了後續同步日誌數據的操做都是順序寫,而不是隨機寫。當日志數據寫到最後一個文件的末尾時,下一條日誌數據又會從新從第一個日誌文件的開始位置進行寫入。

3-一、I/O 性能問題的產生

InnoDB Log Buffer內存空間中的四個標識指針是InnoDB數據庫引擎日誌處理部分最重要元素,它們分別是:Log sequence、Log flushed、Pages flushed和Last checkpoint,這四個標識涉及到InnoDB在崩潰重啓時不一樣的數據恢復策略,以及I/O性能優化中的關鍵原理。這四個標識其實是四個數值它們共享一個數值池(名叫LSN,日誌序列號,其總長度是64位無符號整數),表明當前InnoDB對事務操做的處理狀態。而且它們數值有如下特色:

Log sequence >= Log flushed >= Pages flushed >= Last checkpoint

  1. 每當InnoDB接收到一個完整數據庫insert/update請求事務後,就會建立一個新的LSN。新的LSN = 舊的LSN + 本次寫入的日誌大小。這條最新的日誌將會使用Log sequence進行標記,而且若是出現接收到多個事務請求的狀況下,InnoDB也會按照一個既定的順序對這些日誌進行排序,而後依次生成新的LSN。這一步驟是徹底在內存中進行的,因此不存在I/O性能問題

  2. 接下來Mysql就會開始執行這個事務中的各類細節操做。InnoDB數據庫引擎專門有一個InnoDB Buffer Pool內存空間用來進行數據更改或數據新增。其大小由innodb_buffer_pool_size參數控制,其數據來源於innoDB data file而且以Page的形式存在於InnoDB Buffer Pool中。當日志中有insert操做時則生成新的Page;當日志中有update操做時,InnoDB會檢查該數據是否已經存在於Page Cache中,若是存在(命中)就直接更新這個Page Cache中的內容,若是不存在(未命中)就會繼續從InnoDB data file中讀取原始數據到InnoDB Buffer Pool中而後再更新。這裏要注意幾個問題:

    還記得咱們在討論磁盤設備時提到的「預讀」技術嗎?這個技術的思路是,若是某個區域的數據被讀取和使用那麼在不久的未來與其相鄰的區域也將會被讀取和使用。因此爲了提升讀取效率,磁盤控制芯片會將磁盤上目標塊和其相鄰的若干塊一塊兒讀取出來。InnoDB數據庫引擎一樣使用了這個思路,即讀取某個Page時將會同時讀取臨近的Page,可是是否能起到提到I/O性能的目的仍是要分不一樣的運行環境(後文進行說明)。

    當InnoDB完成InnoDB Buffer Pool中的數據操做後,更改後數據所涉及到的Page將和此時存儲在磁盤上的數據不同,這樣的Page稱爲髒頁。如何控制髒頁將是保持數據一致性的關鍵,InnoDB數據庫引擎的作法是首先向InnoDB File Log Group日誌文件中寫入這個事務的日誌信息。這裏的寫入策略由三種,經過innodb_flush_log_at_trx_commit參數能夠進行控制

    • innodb_flush_log_at_trx_commit = 0時,InnoDB將按照1秒鐘爲單位向磁盤寫入這個階段全部已完成的事務日誌信息。這裏的寫入成功並非說寫入到Linux操做系統的Page Cache中就算成功,而是須要等待操做系統真正寫到了物理磁盤上的通知(具體請參見以前講解文件系統的文章)。這意味着即便InnoDB Buffer Pool中的數據操做是成功的,可是一旦數據庫系統異常崩潰,那麼業務系統將會丟失前1秒內寫入的數據:由於沒有磁盤介質上的日誌就沒法在異常重啓後恢復數據信息。

    • innodb_flush_log_at_trx_commit = 1時,InnoDB按照完成一個日誌操做就向磁盤寫入事務日誌信息的方式來工做(執行一個事務就寫入一個事務日誌)。一樣,這裏的寫入成功一樣是要等待操做系統返回真正寫入了物理磁盤的通知。

    • innodb_flush_log_at_trx_commit = 2時,InnoDB按照完成一個日誌操做就向磁盤寫入日誌信息的方式來工做。可是,這種工做模式下InnoDB不會等待操做系統返回物理磁盤上寫入成功的通知,就會繼續工做。實際上這個時候,數據通常還存在於Linux操做系統的cache memory區塊中,因此這種模式下最好使用帶有日誌功能的文件系統,而且確認開啓了文件系統的日誌功能

    InnoDB數據庫引擎在這一步驟的最後一個動做是更改Log flushed標識指針值爲當前最後完成刷新動做的事務日誌LSN值。實際上執行完這個步驟,一個事務處理操做纔算真正成功

  3. 可是涉及數據變更的髒頁尚未更新到磁盤上,爲何事物的處理就能夠算做成功了呢?這是由於即便這個時候數據庫異常崩潰了,就憑存儲在磁盤上的完整日誌咱們也能夠重作數據。好吧,最好仍是要同步髒頁是吧。在第三個步驟InnoDB數據庫引擎將會把最近Log flush時所涉及到的髒頁(最舊髒頁)更新到磁盤上。當完成髒頁向磁盤的同步操做後,InnoDB數據庫引擎將會更新Pages flushed標識點的LSN值,表示這個LSN值所表明的事務(以及以前的事務)都已經完成了內存和磁盤上的數據同步動做。當InnoDB數據庫引擎進行髒頁更新時,將會按照必定的週期策略批量提交髒頁到Linux操做系統的cache memory區塊中。每一次批量提交的髒頁數量由innodb_io_capacity參數決定

    不一樣版本InnoDB數據庫引擎支持的pages flush策略是不同的,但最基本的規則沒有變化,就是週期性刷新。從Mysql version 5.6開始InnoDB數據庫引擎向管理者提供了一個innodb_adaptive_flushing參數,當這個參數設置爲「no」時InnoDB數據庫引擎將檢測髒頁在InnoDB Buffer Pool中的比例,以及即時I/O狀態等狀況來決定pages flush的週期。若是髒頁在InnoDB Buffer Pool中的比例達到了由innodb_max_dirty_pages_pct(默認爲75)參數設置的百分比閥值,這時InnoDB數據庫引擎將按照innodb_io_capacity_max(默認值2000)參數設置的數量將這寫髒頁一塊兒同步到磁盤

    當磁盤I/O性能不足且innodb_io_capacity設置過大時,會致使產生較長的I/O隊列形成I/O請求阻塞,一旦累積到innodb_max_dirty_pages_pct閥值,又會產生更長的I/O阻塞隊列;反之則會形成物理服務器的I/O性能沒有被去徹底使用。因此innodb_io_capacity的設置很是重要,特別是當讀者在硬件層採用SSD固態硬盤和高速磁盤陣列時。

  4. Checkpoint是InnoDB數據庫引擎中最後一個標識點。這個標識點表明着當數據庫異常崩潰重啓後,小於或者等於這個標識點LSN值的全部日誌信息、數據信息都無需進行重作檢查。而LSN值大於Checkpoint的全部事務都須要重作,只是重作策略將視LSN值所在標識區域的不一樣而不一樣:

    這裏寫圖片描述

    • 當表明事務的LSN數值在Log sequence——Log flushed範圍內時(不包括Log flushed),說明在數據庫崩潰時內存中的事務並無處理完,這部分事務操做將在恢復時被丟棄。

    • 當表明事務的LSN數值在Log flushed——Pages flushed範圍內時(不包括Pages flushed),說明數據庫崩潰時磁盤上已經擁有這些事務完整的日誌記錄。InnoDB數據庫引擎將讀取這些日誌數據,並繼續執行下去,直到表明這些事務的LSN值被標記爲Checkpoint(或者小於Checkpoint標識的LSN值)。這裏要注意,在數據庫崩潰時處於這個範圍內的某些事務可能已經完成了一部分的數據同步動做,可是確定是不完整的。因此即便是這樣的事務也要從新進行磁盤同步,才能保證數據的一致性。

    • 實際上在MySQL version 5.5的早期版本,InnoDB數據庫引擎中只有三個標識:Log sequence、Log flushed和Checkpoint。也就是說當髒頁成功同步到磁盤後,就會直接更新Checkpoint標識的LSN值。後續版本的MySQL數據庫增長了Pages flushed標識點,這樣作的目的是保證Checkpoint和Pages flush的更新能夠擁有獨立的週期,從而下降其帶來的性能消耗。

3-二、I/O 性能問題要點

  1. Log flush和Pages flush

    從上一小節的描述中,咱們大體知道了在InnoDB數據庫引擎中一個事務的處理過程當中有兩個步驟存在I/O操做:Log flush和Pages flush。

    Log flush的過程是將完成的事務日誌寫入到日誌文件中,因爲InnoDB數據庫引擎中日誌文件的組織方式,因此Log flush中對磁盤的操做是順序寫。而且技術團隊還能夠經過innodb_flush_log_at_trx_commit參數來調整InnoDB Log Buffer到InnoDB File Log Group的同步策略,這有助於進一步提升Log flush性能。這也就是爲何實際環境中每每將MySQL數據庫(大部分關係型數據庫都適用)直接創建在塊存儲方案上,而不是創建在文件存儲方案或者對象存儲方案上的緣由。

    Pages flush的過程就沒有那麼幸運了,InnoDB數據庫引擎不可能事先知道數據庫會存放哪些數據,也不可能知道下次的update操做和select操做的目標數據存放在哪一個區域。因此InnoDB數據庫引擎針對Page的讀取和更新都只能基於隨機讀寫。那麼Pages flush過程就須要在如何保持I/O性能這問題上想更多的解決辦法。

    • 例如在讀取Page時,採用「預讀」思路將目標Page所臨近的Page一塊兒讀取出來;在寫入Page時將目標Page所臨近的Page一塊兒寫入(「臨近寫」)。「預讀」策略能夠經過innodb_read_ahead_threshold參數進行設置,並經過read thread完成,另外 innodb_flush_neighors參數能夠控制是否開啓「臨近寫」策略。總的來講「預讀」/「臨近寫」在默認狀況下都是開啓的,但「預讀」/「臨近寫」思路自己就須要必定的準確性,低命中率的「預讀」反而會下降InnoDB的I/O性能。還有一種「隨機預讀」,它在MySQL version 5.6版本中默認就是關閉的,而且在隨後的版本中將會慢慢廢除,因此這裏就再也不介紹了。

    • 例如將向磁盤提交Page的動做設計爲週期性且批量進行,而且始終保持InnoDB Buffer Pool內存區域的髒頁(Dirty Page)在必定的比例,這些策略主要由innodb_io_capacity、innodb_max_dirty_pages_pct、innodb_io_capacity_max等參數控制。

    • 例如經過調整Innodb_Buffer_Pool_size參數得到更大的InnoDB Buffer Pool內存區域,存儲更多的Page。實際上Innodb Buffer Pool區域不只包括咱們已經介紹的Page Cache數據部分,還包括其它的數據區塊。例如爲了快速定位B+樹索引的Hash Index結構。調整Innodb_Buffer_Pool_size參數將會使這些數據區域都享受到內存容量帶來的優點——至少不會頻繁地發生內容空間的強制清理。

  2. 基礎硬件條件

    按照本專題以前文章介紹的塊存儲方案來看(《架構設計:系統存儲(1)——塊存儲方案(1)》、《架構設計:系統存儲(2)——塊存儲方案(2)》),若是存儲MySQL數據的底層硬件介質就只是一塊機械磁盤,那麼不管怎樣優化MySQL的其它各參數,MySQL實際對磁盤的順序I/O速度理論上也只有100MB/S左右。這仍是不計算硬件層校驗、不計算不一樣文件系統處理耗時等等時間,因此實際I/O速度只會更慢。另外若是採用單塊機械磁盤存儲MySQL的數據,那麼磁盤空間的擴容也是一個問題。目前市場上能買到的容量最大的單塊機械磁盤,它的存儲空間也只有10TB。當這部分容量使用完後想要進行擴容就基本上是就一個不可能完成的任務了。最後這種存儲方式還有安全性的問題,單塊機械磁盤在持續的高I/O環境下是很容易損壞的,只要是有必定資金支持的公司,機械磁盤自己就看作耗材。

    因此即便是初創型公司的線上生產環境,本文也不推薦使用單塊機械磁盤存儲任何須要持久保存的業務數據。若是是由於資金問題,則推薦使用一些雲服務商提供的現成PaaS環境,緣由是這些PaaS
    環境自己就支持數據恢復功能,且利用雲服務商已經建設好的價格不菲的硬件/軟件環境,MySQL數據庫的I/O性能和計算性能暫時還不會成爲業務系統的瓶頸。

3-三、突破I/O性能

================================================= (接下文)

相關文章
相關標籤/搜索