我是如何在實際項目中解決MySQL性能問題

多是本性不肯隨衆的緣由,我對於程序員面試中動輒就是考察併發上千萬級別的QPS向來嗤之以鼻,好像國內的應用都是那麼多用戶量同樣,其實併發達到千萬,百萬以上的應用能有幾個?html

絕大多數的程序員面臨的只是解決百級、千計、萬級的併發量,與其純粹爲了面試學一些空中樓閣,不如腳踏實地的學習如何解決眼前實際問題。mysql

而對於我,最能激發我學習動力的不是什麼造火箭面試,而是可以解決實際的問題。linux

哪怕看起來沒那麼高大上的場景,只要能解決產品給客戶帶來的痛點,就能給作技術的人帶來很大的成就感!程序員

 

言歸正傳,公司最近開發的一個用於學生在線考試的產品正好面臨這樣的問題。面試

技術框架

  • 服務器:Windows Server 2008(64位虛擬機,內存16G)
  • DB:MySQL 5.7.27
  • 後端:Spring+Mybatis
  • 教師端:VueJS
  • 學生端(平板電腦):安卓原生 + VueJS

 

事件通過

  • 9.17 模擬考試的學生數量大約500,前方反饋頁面出現卡頓現象,有些學生的答題未提交上來
  • 9.18 將widnows自帶的性能監控開起來,主要監控CPU、內存、磁盤IO的狀況,並將晚自習模擬考試的兩個小時監控保存起來
  • 9.19 通過分析監控日誌,發現瓶頸在磁盤IO上,讀取的壓力很大,由於是機械鍵盤,因此聯繫學校能否將硬盤換爲固態硬盤
        (過後想這樣雖然是也是一個解決途徑,但不該該是程序員的第一選擇,由於硬件的優化應該是軟件優化作到極致以後的舉措,而不是第一時間把問題拋給硬件,這是很不負責的態度)
          還好學校沒有答應,因而開始分析爲什麼磁盤讀取壓力這麼大,涉及到磁盤讀寫,最大可能應該是數據庫的讀寫,也就是咱們用的MySQL,那麼爲何MySQL壓力這麼大呢?
          因而分析學生參與考試的整個過程:
    1. 打開應用:學生使用平板電腦打開app,並進入考試列表頁面(原生),這裏後臺讀取操做是查詢本課程目前的考試
    2. 開始考試:當某一考試開始時間到達,考生能夠點擊開始答題按鈕進入考試,此時,會將整個試卷的內容都拉取下來,並存儲到安卓本地的DB中(這裏當讀取的併發很大)
    3. 作題:學生開始答題,當學生每點擊下一題時,會將本題的答案保存到後臺,成功後會跳轉到下一題(寫入的併發壓力大)
    4. 提交:學生答完全部題目後,會在最後一題點擊提交按鈕,保存最後一題,並自動計算得分

         因此讀取的問題鎖定在第2步,也就是拉取試卷的過程,那麼首先考慮能否使用緩存,而不是每次都去DB存儲讀取,由於試卷一旦創建,數據是固定的,而不會去更新,契合緩存使用的場景。進一步思考爲何MySQL沒用使用緩存呢,原諒個人無知,查閱了一下才知道,項目使用的MySQL5.7.27中默認參數以下sql

    innodb_buffer_pool_size=8M
    innodb_buffer_pool_instances=8
    innodb_log_file_size=48M

     調整後參數以下(關於這幾個參數,請參考後續的原理解析):數據庫

    innodb_buffer_pool_size=5G
    innodb_buffer_pool_instances=8
    innodb_log_file_size=256M

    磁盤讀的壓力問題解決。windows

  • 10.29 查看日誌,發現主鍵重複問題(UUID),此問題是代碼問題。
  • 10.30 添加後臺自動提交的功能(防止學生經過平板鎖屏後的倒計時暫停做弊),發現DB鏈接沒法獲取的異常,將my.ini中max_connections由默認的156改成800解決
  • 10.31 發現日誌中偶爾存在死鎖問題,系統拋出MySQLTransactionRollbackException,代碼邏輯問題。
  • 11.6  1200+人的考試,順利完成,整個過程服務器壓力不大,最後學校滿意讀很高。

學習反思

雖然這裏的解決方案都不是那麼「高大上」的技術手段,但正是由於這樣的契機,激發了我係統學習MySQL的興趣後端

在試圖解決以及避免考試中出現性能問題總,總結了幾個比較重要的優化措施緩存

  • 充分利用緩存,特別是數據不頻繁更新的數據,好比本項目中的試卷內容拉取,緩存中有幾個比較重要的參數須要瞭解
    緩存相關
    1. innodb_buffer_pool_size = 下面兩個參數乘積的整數倍,InnoDB引擎下能夠緩存索引和數據塊,若是是專用DB服務器,能夠最大設置到OS內存的80%。5.7.5以後能夠動態調整
    此參數也不宜過大,由於過大會致使髒頁太多,DB關閉時會比較慢,開啓的預熱也比較慢,因此上面的設置實際上是有點大的 2. innodb_buffer_pool_chunk_size (5.7.5以後引入,默認128M) 3. innodb_buffer_pool_instances (默認8)

    日誌相關
    1. innodb_log_buffer_size 當存在較大數據量的事務提交的時候,修改此參數會下降磁盤寫
    2. innodb_flush_logs_at_trx_commit (默認爲1,對寫性能要求高而對數據不是特別敏感時能夠改成0或者2)

     參考:https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool-resize.html
     當執行一條查詢SQL時,步驟以下(如下圖表參考極客,若有侵權,請聯繫
     

     今後圖中瞭解數據查詢爲什麼走緩存快的多了,由於省去了分析、優化、執行等過程,不與存儲引擎交互,固然也就沒有磁盤讀了。 
     固然,這也是項目中增大innodb_buffer_pool_size的緣由

  • 查詢的其它優化方法
    > 提早終止查詢(使用limit避免掃描全表)
    > 不須要去重,則使用union all,不然中間的臨時表會由於加上distinct而致使效率低下
  • 充分利用MySQL影響性能的參數

    這裏也有必要提下MySQL的日誌機制(分爲redoLog和binLog),如下是SQL更新的過程

     
    >項目中沒有調整innodb_flush_logs_at_trx_commit默認值1(表示每次事務提交都會持久化到磁盤中),由於咱們的數據用於學生考試,對精確性有必定的要求。

    >若是磁盤寫壓力比較大,而平板端提交等待時間長的話(好比最後提交試卷),應該調大innodb_log_buffer_size,這個參數其實只是對於單次commit比較大的數據有用,表示在真正commit前若是達到了這個參數的峯值,會先寫入磁盤中,極大下降單次提交的效率。這個參數在版本更新中多次增大,在5.7.6以後默認爲16M,足以應對絕大多數狀況。

    >項目增大innodb_log_file_size(默認48M),是爲了減小磁盤的IO,這個參數控制的是redoLog的文件大小,參考此圖()
    圖中innodb_log_files_in_group設置爲4(默認2)
    每個log文件的大小爲innodb_log_file_size,若是設置太小,checkpoint到達(覆蓋以前的日誌)後,就會先擦除掉以前的redoLog,而後再往前寫。
    因此若是這個參數太小,就會常常下降磁盤IO,推薦設置爲innodb_buffer_pool_size的1/4

     

  • 充分利用索引
    由於當時項目中已經解決磁盤讀的問題,因此沒有在索引上進行優化。實際上,索引優化是很是值得學習的手段(特別是執行慢的代碼段)
    而對於執行慢的的SQL,可使用相似show variables like '%slow%' 方法來尋找,方法以下:
    慢查詢:查詢時間長於long_query_time參數的設置(默認10秒),查詢方法:show variables like 'long_query_time';
    查看慢查詢日誌(slow_query_log):show variables like 'slow_query%'; (默認不開啓)
    慢日誌開啓方法:my.cnf中設置slow_query_log =1 以及slow_query_log_file的路徑

    而後經過mysqldumpslow來找到執行慢的SQL

    下面總結幾條創建索引的通常規則
    > 利用主鍵索引來避免使用非主鍵索引(二次查詢)
    > 利用覆蓋索引來避免回表
    > 利用前綴索來避免索引的字段過長
    > 使用短字段創建索引
    > 避免使用使索引失效的語句,好比now()函數、用戶自定義函數、存儲函數、用戶變量、臨時表、mysql庫中的系統表

    至於什麼B樹、MySQL的B+樹等裝逼的知識,不在本文討論範圍內,感興趣的本身去查

  • 監控MySQL的運行狀態(找到須要優化的地方)

    show global status; // 顯示全部狀態,下面列出幾個跟性能關係比較大的status

    show status like 'Threads%'; // 顯示的Threads_connected可用來標示併發數
    show status like '%conn%' // 顯示的Connections是全部嘗試鏈接的數量
    show processlist 顯示正在執行的MySQL鏈接,記錄數與上面的Threads-connected相等

    > 對應的配置項能夠在配置文件中修改(windows的my.ini,linux的my.cnf),好比max_connections等

  • 查看執行計劃 (explain SQL)
  • 關於事務,儘可能避免死鎖和減小鎖數據的時間
    須要瞭解如下內容
    > MySQL默認都是行鎖
    > 行鎖都是對索引的鎖
    > 不要把查詢語句放在事務開啓和提交之間
    > 默認鎖級別爲REPEATABLE READ(可重讀),可經過更改TRANSACTION ISOLATION LEVEL,下面的四種級別的鎖,就再也不贅述了

        SERIALIZABLE(序列化)、REPEATABLE READ(可重讀)、READ COMMITTED(提交後讀)、READ UNCOMMITTED(未提交讀)

     

MySQL的其餘基本原理

  • MySQL是半雙工
  • 基本架構以下(老經典圖)

     

      

此文會持續更新,直到把全部我認爲對實際項目有幫助的地方總結完畢!

 

參考:

https://dev.mysql.com/doc/refman/8.0/en/server-status-variables.html#statvar_Connections 

http://www.javashuo.com/article/p-azpvgegy-k.html

https://zhuanlan.zhihu.com/p/59818056

http://www.javashuo.com/article/p-ypmqzjrb-gp.html

相關文章
相關標籤/搜索