讀書-高性能MySQL 第三章

服務器性能剖析

最經常碰到的三個性能相關的服務請求是:如何確認服務器是否達到了性能最佳的狀態,找出某條語句爲什麼執行不夠快,以及診斷被用戶描述成“停頓”、“堆積”或者“卡死”的某些間歇性陰暗故障。核心方法是專注於服務器的時間花費在哪裏,使用的 技術則是性能剖析工具(profiling)。本章,將展示如何測量系統並生成剖析報告,以及如何分析系統的整個對渣(stack),包括從應用程序到數據庫服務器到單個查詢。
首先我們要保持空誒精神,拋棄掉一些關於性能的常見誤解。

3.1 性能優化簡介

我們將性能定義爲完成某件任務所需要的時間度量,性能即響應時間,這是一個非常重要的原則。我們通過任務和時間而不是資源來衡量性能。數據庫服務器的目的是執行SQL語句,所以他關注的任務是查詢語句。數據庫服務器的性能用查詢的相應時間來度量,單位是每個查詢花費的時間。
很多人對此很迷戀。假如你認爲性能優化是降低CPU利用率,那麼可以減少對資源的使用,但是這是一個陷阱,資源是用來消耗並用來工作的,所以有時候消耗更多的資源能夠加快查詢速度。很多時候使用老版本InnoDB引擎的MySQL升級到新版本後,CPU利用率上升的厲害,這並不代表性能出現了問題,反而說明了新版本的InnoDB對資源的利用率上升了。查詢的相應時間則更能體現升級後的性能是不是變得更好。版本升級有時候會帶來一些bug,比如不能利用某些索引從而導致CPU利用率上升,CPU利用率只是一種現象,而不是很好的可度量目標。
同樣,如果把性能優化僅僅看成提高每秒查詢量,這其實只是吞吐量優化。吞吐量的提升可以看做性能優化的副產品、對查詢的優化可以讓服務器每秒執行更多的查詢,因爲每條查詢執行的時間更短了。
所以如果目標是減低相應時間,那麼就要理解爲什麼服務器執行查詢需要這麼多時間,然後減去或者消除那些獲得查詢結果來說不必要的工作。也就是說,先要搞清楚時間花在哪裏。這就引申出優化的第二個原則:無法測量就無法有效地優化。所以第一步應該測量時間花在什麼地方。
我們觀察到,很多人在優化時,都將精力放在修改一些東西上,卻很少去進行精確的測量。我們的做法完全相反,將花費非常多,甚至90%的時間來測量響應時間花在哪裏。如果通過測量沒有找到答案,那要麼是測量的方式錯了,要麼是測量的不夠完整。如果測量了系統中完整而且正確的數據性能問題一般都能暴露出來,對症下藥的解決方案也就比較明瞭。測量是一項很有挑戰性的工作,並且分析結果也同樣具有挑戰性,測出時間花費在哪裏,和知道爲什麼花在那裏,是兩碼事。
前面提到需要合適的測量範圍,這是什麼意思能?合適的測量範圍是說還有測量需要優化的活動。有兩種比較常見的情況會導致不合適的測量:

  • 在錯誤的時間啓動和停止測量
  • 測量的是聚合後的信息,餓不死目標活動本身

例如,一個常見的錯誤是先查看慢查詢,然後又去排查整改服務器情況來判斷問題在哪裏。如果確認有慢查詢,那麼就應該測量慢查詢,而不是測量整改服務器。測量的應該是從慢查詢的開始時間到結束時間,而不是查詢之前或者查詢之後的時間。
完成一項任務所需要的時間可以分爲兩部分:執行時間和等待時間。如果要優化任務的執行時間,最好的辦法是通過測量定位不同的子任務花費的時間,然後優化去掉一下子任務、降低子任務自信頻率或者提升子任務的效率。而優化任務的等待時間相對要複雜一些,以爲等待有可能是由其他系統間接影響導致,任務之間也可能由於爭用次哦按或者CPU資源而相互影響。根據時間是花在執行還是等待上的不同,診斷也需要不同技術和工具。
剛擦索道需要定位和優化子任務,但只是一筆帶過。一些運行不頻繁或者很短的子任務對整體相應時間的影響很小,通常可以忽略不計。那麼如何億哪些子任務是優化的目標呢?這個時候性能剖析就可以派山用場了。

3.1.1 通過性能剖析進行優化

一旦掌握並時間面向響應時間的優化方法,就會發現需要不斷地對系統進行性能剖析(profiling)。
性能剖析是測量和分析時間花費在哪裏的主要方法。性能剖析一般有兩個步驟:測量任務所花費的時間;然後 對結果進行統計和排序,將重要的任務排到前面。
性能剖析工具的工作方式基本相同。在任務開始時啓動計時器,在任務技術時停止計時器,然後用結束時間減去啓動時間得到響應時間。也有些工具會記錄任務的父任務。這些數據結果可以用來繪製調用關係圖,但對於我們的目標來說更重要的是,可以將相似的任務分組並進行彙總。對相似的任務分組並進行彙總可以幫助最哪些分發哦一組的任務做更復雜的統計分必須,但至少需要知道沒一組有多少任務,並計算出總的響應時間。通過性能剖析報告(profile report)可以獲得需要的結果。性能剖析報告會列出所有任務列表。每行記錄一個任務,包括任務名、任務執行時間、任務笑傲時間、任務平均執行時間,以及該任務執行時間佔全部時間的百分比。性能剖析報告會安裝任務的消耗時間進行降序排序。
下面舉例說明,這是從整體的角度分析相應時間,後面會演示其他角度的分析結果。下面輸出的是用Percona ToolKit中的pt-query-digest(實際上是Maatkit工具中的mk-query-digest)分析得到的記過。爲了顯示方便,對結果做了一些微調,並且只截取了前幾行的結果
在這裏插入圖片描述
上面只是性能剖析結果的前幾行,根據總相應時間進行排名,只包括剖析所需要的最小列組合。沒一行都包括了查詢的相應時間和佔總時間的百分比、查詢的執行次數、單純執行的平均時間,以及該查詢的摘要。通過這個性能剖析可以清楚第看到每個查詢之間的成本比較,以及每個查詢佔總成本的比較。在這個例子中,任務指的就是查詢,實際上在分析MySQL的時候經常都指的是查詢。
將實際討論的兩種類型的性能剖析:基於執行時間的分析和基於等待的分析。基於執行時間的分析研究的是什麼任務的執行時間最長,而基於等待的分析是判斷任務在上面地方被阻塞時間最長。
如果任務執行時間長是因爲消耗了太多的資源並且大部分時間花費在執行上,等待的時間不多,這種情況基於等待的分析作用就不大。反之亦然,如果任務一直在等待,沒有消耗什麼資源,去分析執行時間就不會有什麼結果。如果不能確認問題是出在執行還是等待上,那麼這兩種方式都要試試。
事實上,當基於執行時間的分析發現一個任務需要花費太多時間的時候,應該深入去分析一下,可能會發現某些“執行時間”實際上是在等待。例如上面簡單的性能剖析顯示錶InvitesNew上的SELECT查詢花費了大量時間,如果深入研究,則可能發現時間都花費在等待I/O完成上。
在對系統進行性能剖析前,陛下先要能夠進行測量,這需要系統可測量化的支持。刻測量的系統一般會有多個測量點可以捕獲並蒐集數據,但實際系統很少可以做到可測量化。大部分系統沒有多少克測量點,即使有也只是提供一些活動的計數,而沒有活動花費的時間統計。MySQL就是一個典型的例子,知道版本5.5才第一次提供了Performance Schema,其中有一些基於某種活動發生的次數。這也是我們最終決定穿件Percons Server的主要原因,Percona Sever從版本5.0開始提供很多更詳細的查詢級別的測量點。
雖然理想的性能優化急速依賴於更多的測量點,但幸運的是,即使系統沒有提供測量點,也還有其他辦法可以開展優化工作。因爲可以從外部去測量系統,如果測量失敗,也可以根據對系統的瞭解做出一些靠譜的猜測。但這麼做的時候一定要記住,不管是外部測量還是猜測,數據都不是百分之百準確的,這是系統不透明所帶來的風險。
舉個例子,在Percona Server 5.0中,慢查詢日誌揭露了一些性能地下的原因,如磁盤I/O等待或者行級鎖等待。如果日誌顯示一條查詢花費了10秒,其中9.6秒在的辦法帶磁盤I/O,那麼追究其他的4%的事故華北花費在哪裏就沒有意義,磁盤I/O纔是最重要的原因。

3.1.2理解性能剖析

MySQL的性能剖析(profile)將最重要的任務展示在前面,但有時候沒顯示出來的信息也很重要。不幸的是,儘管性能剖析輸出了排名,總計和平均值,但還是有很多需要的信息缺失,如下所示。

值得優化的查詢(worthwhile query)

性能剖析不會自動給出哪些查詢值得花時間去優化。這把我們帶回到優化的本意。強調以下兩點:第一,一些只佔總響應時間比重很小的查詢是不值得優化的。根據阿姆達爾定律,對於一個佔總響應時間不超過5%的查詢進行優化,無論如何努力,收益也不會超過5%。第二,如花費了1000美元去優化一個任務,但業務的收入沒有任何增加,那麼可以鎖反而導致業務被逆優化了1000美元。如果優化的成本大於收益,就應當停止優化。

異常情況

某些任務即使沒有出現在性能剖析輸出的前面也需要優化。比如某些任務執行此時很少,但每次執行都非常慢,嚴重影響用戶體驗。因爲其執行頻率低,所以總的響應時間佔比並不突出。

未知的未知

一款好的性能剖析工具會顯示可能的“丟失的時間”。丟失的時間是指任務的總時間和實際測量到的時間之間的差。例如,如果處理器的CPU時間是秒,而剖析到任務的總時間是9.7秒,那麼就有300毫秒的丟失時間。這可能是有些任務沒有測量到,也有肯能是由於測量的誤差和精度問題的緣故。如果工具發現了這類問題,則要引起重視,因爲有肯能錯過了某些重要的事情。即使性能剖析沒有發現丟失時間,也需要考慮這類問題存在的可能性,這樣纔不會錯過重要的信息。

被掩藏的細節

性能剖析無法顯示所有響應時間的分佈。只相信平均值是非常危險的,他會隱藏很多信息,而且無法表達全部情況。Peter經常舉例說明醫院所有病人的平均體溫沒有任何意義。假如在前面的性能剖析的例子的第一項中,如果有兩次查詢的響應時間是1秒,而另外的12771此查詢響應時間是幾十微秒,結果會怎樣?從平均值裏面無法發現兩次1秒的查詢。要做出最好的決策,需要爲性能剖析裏輸入的這一行中包含的12773次查詢提供更多信息,尤其是更多響應時間的信息,比如直方圖、百分比、標準差、偏差指數等。
好的工具可以自動地獲取這些信息。實際上,pt-query-digest就在剖析的結果裏包含了很多這類細節信息,並且輸出在剖析報告中。對此我們做了簡化,可以將經集中在重要而基礎的例子上:通過排序將最昂貴的任務排在前面。本章後面會展示更多豐富而有用的性能剖析的例子。
前面的例子中,有一個重要的缺失,就是無法再更高層次的堆棧中進行交互式的分析。當我們僅僅着眼於服務器中的單個查詢時,就無法將相關的查詢聯繫起來,也無法理解這些查詢是否是在同一個用戶交互的一部分。性能剖析只能管中窺豹,而無法將剖析從任務擴展事務或者頁面查看(page view)的級別。也有一些版本可以解決這個問題,比如給查詢加上特殊註釋作爲標籤,可以標明其來源並據此做聚合,也可以在應用層面增加更多的測量點,這是下一節的主題。

3.2 對應用程序進行性能剖析

對任何需要消耗時間的任務都可以做性能剖析,當然也包括應用程序。實際上,剖析應用程序一般比剖析數據庫服務器容易,而且彙報更多。雖然前面顏色的例子都是針對MySQL服務器的剖析,但對系統性能剖析還是建議自上而下地進行,這樣可以追蹤用戶發起到服務器相應的整個流程。雖然性能問題大多數情況下都和數據庫有關,但應用導致性能問題也不少。性能瓶頸可能有很多影響因素:

  • 外部資源,比如調用了外部的Web服務器或者搜索引擎。
  • 應用需要處理大量的數據,比如分析一個超大的XML文件。
  • 在循環執行昂貴的操作,比如濫用正則表達式
  • 使用了低效的算法,比如使用暴力搜索算法(naive search algorithm)來查找列表中的項。

幸運的是,確定MySQL的問題沒有這麼複雜,只需要一款應用程序的剖析工具即可。建議在所有的新項目中都考慮包含性能剖析代碼。往已有的項目中假如性能剖析代碼也許很難,新項目就簡單一些。
一款工具New Relic的軟件即服務(software-as-service)產品,可以測量很多用戶體驗相關的點,涵蓋從Web瀏覽器到應用代碼,再到數據庫及其他外部調用。
像New Relic這類工具的好處是可以全天候地測量生產環境的代碼-既不限於測試環境,也不限於某個時間段。、

3.2.1 測量PHP應用程序

如果不使用New Relic,也有其他選擇。尤其是對PHP,有好幾款工具可以幫助進行性能剖析。其中一款xhprof(http://www.php.net/xhprof),xhprof有很多高級特性,易於安裝使用,輕量級,可擴展性好,可以在生產環境大量部署全天候使用,還能針對函數調用進行剖析,並根據耗費時間排序。相比xhprof,還有一些更底層的工具,比如xdebug,Valgrind和cachegrind,可以從多個角度對代碼進行檢測。有些工具會產生大量輸出,並且開銷很大,並不適合在生產環境運行,但開發環境卻可以發揮很大優勢。

  • MySQL企業監控器的查詢和分析功能
    MySQL的企業監控器(Enterprise Monitor)也是值得考慮的工具之一。這是Oracle提供的MySQL商業服務支持的一部分。他可以捕獲發送給服務器的查詢。有良好的用戶界面,可以直觀顯示查詢的剖析結果,並且根據時間段進行縮放,例如可以選擇某個異常的性能尖峯時間來查看狀態圖。也可以查看EXplAIN出來的執行計劃,在故障診斷的時候非常有用。

3.3 剖析MySQL查詢

對查詢進行性能剖析有兩種方式。可以剖析整個數據庫服務器,這樣可以分析出主要的壓力來源。定位到具體需要優化的查詢後,也可以鑽取下去對這些查詢進行單獨的剖析,分析哪些子任務是相應時間的主要消耗者。

3.3.1 剖析服務器負載

服務器端的剖析很有價值,因爲在服務器可以有效地審計效率低下的查詢。定位和優化“壞”查詢能夠顯著地提升應用的性能,也可以解決某些特定的難題。還可以減低服務器的整體壓力,這樣所有的查詢都將因爲減少了對共享資源爭用而受益。降低服務器的負載也可以推遲或者避免升級更昂貴的硬件的需求,還可以發現和定位糟糕的用戶體驗,比如一些極端的情況。
MySQL的沒一個新版本都會增加更多的可測量點。如果當前的趨勢可靠的話,那麼在性能方面比較重要的測量需求很快能夠子啊全球範圍內得到支持。但如果只是需要剖析並找出代價高的查詢,就不用如此複雜。可以使用慢查詢日誌。

捕獲MySQL的查詢到日誌文件中

在MySQL中,慢查詢日誌最初只是捕獲比較慢的查詢,而性能剖析卻需要針對所有的查詢。可以通過log_query_time爲0來捕獲所有傳銷,而且查詢的響應時間單位已經可以做到微秒級。如果使用的是Percons Server,提供了對日誌內容和查詢捕獲更多的控制能力。
在MySQL的當前版本中,慢查詢日誌是開銷最低、精度最高的測量查詢時間的工具。在I/O密集型場景做過基準測試,慢查詢日誌帶來的開銷可以忽略不計(實際上CPU密集型的影響稍微大一些)。更需要擔心的是日誌可能消耗大量的磁盤空間。如果長期開啓慢查詢日誌,注意要部署日誌輪轉工具(log rotation)。或者不要長時間啓用慢查詢日誌,只在需要蒐集負載樣本期間開啓即可。
MySQL還有另外一種查詢日誌,被稱爲“通用日誌”。但很少用於分析和剖析服務器的性能。通用日誌在查詢請求到服務器時進行記錄,所以不包含相應時間和執行計劃等重要信息。
Percona Server的慢查詢日誌比MySQL官方版本記錄了更多細節且有價值的信息,如查詢執行計劃、鎖、I/O活動等。這些特性都是隨着處理各種不同的優化場景需求而慢慢加進來的。另外在可管理性上也進行了增強。比如全局修改針對每個連接的log_query_time的閾值,這樣當應用連接池或者持久連接的時候,可以不用重置會話級別的變量而啓動或者停止連接的查詢日誌。總的來說,慢查詢日誌是一種 輕量而且功能全面的性能剖析工具,是優化服務器查詢的利器。
有時候因爲某些原因如權限不足等,無法再服務器上記錄查詢,這樣的限制也會經常遇見。因此可以使用Percona Toolkit中的pt-query-digest中。第一種是通過–processlist選項不斷查看SHOW FULL PROCESSLIST的輸出,記錄查詢第一次出現和小時的時間。某些情況下這樣的精度也足夠發現問題,但卻無法捕獲所有的查詢。一些執行比較快的查詢可能在量子執行時間的間隙執行完成了,就無法捕獲到。
第二種技術是通過抓取TCP網絡包,然後根據MySQL的客戶端/服務端通信協議進行機械。可以先通過tcpdump將網絡包數據保存到磁盤,然後使用pt-query-digest的–type=tcpdump選項來進行解析並進行分析查詢。此方法精度比較高,並且可以捕獲所有查詢。還可以機械更高級的協議特性,比如可以解析二進制協議,從而創建並執行服務端預解析的語句(prepared statement)及壓縮協議。另外還有一種方法,就是通過MySQL Proxy代理層的腳本來記錄所有查詢,但實際中很少這樣做。

分析查詢日誌

建議從現在開始利用慢查詢日誌捕獲服務器上所有查詢,並進行分析。可以在一些典型的是啊見窗口如業務高峯期的一個小記錄查詢。如果業務趨勢比較均衡,那麼在一分鐘甚至更短的時間內捕獲需要優化的低效查詢也是可行的。
不要直接打開整個慢查詢日誌進行分析,這樣只會浪費時間和金錢。首先應該生成一個剖析報告,如果需要,則可以在查看日誌中需要特別關注的部分。自頂向下是比較好的方式,否則可能像前面提到的,反而導致業務的逆優化。
從慢查詢日誌生成剖析報告可以使用pt-query-digest,功能強大,可以將報告保存到數據庫中,以及追蹤工作負載歲時間的變化。
一般情況下,只需要將慢查詢日誌文件作爲參數傳遞給pt-query-digest,就可以正確地工作了。下面是一分pt-query-digest輸出報告的例子,作爲性能剖析的開始,這是前面提到過的一個未修改的剖析報告:
在這裏插入圖片描述
ID,對查詢語句計算出的Hash指紋,計算式去掉了查詢條件中的文本值和所有空格,並且全部轉換爲小寫字母(注意,第三條和第四條的摘要看起來是一樣,但是Hash指紋是不一樣的)。表名InvitesNew後面的問號意味着這是一個分片(shard)的表,表名後面分片的標識被問號替代,這樣就可以將同一組分片表作爲一個整體做彙總統計 。
報告中V/M列提供了方差均值比(variance-to-mean ratio)的詳細數據,方差均值比也就是常說的離差指數(index of dispersion)。離差指數搞的查詢對應的執行時間變化加到,而這類查詢通常都值得去優化,如果指定了–explain選項,輸出結果中會正價一列簡要描述查詢的執行計劃。通過聯合觀察執行計劃列和V/M列,可以更容易識別出性能低下需要優化的查詢。
最後,在尾部也增加了一行輸出,顯示其他17個佔用比價低,而不值得單獨顯示的查詢的統計信息。可以通過–limit和–outliers選項指定工具顯示更多查詢的詳細信息,我不是講義寫不重要的查詢彙總在最後一行,默認只會打印時間消耗前10位的查詢,彙總執行時間超過1秒閾值很多倍的查詢,這兩個限制都是可以配置的。
剖析報告的後面包含了每種查詢的詳細報告。可以通過和查詢ID或者排名來匹配前面的剖析統計和查詢的詳細報告。下面排名第一也就是“最慢”的查詢的詳細報告:
在這裏插入圖片描述
查詢報告的頂部包含一些元數據,包含查詢執行的頻率,平均併發度,以及該查詢性能最長的一次執行在日誌文件中的字節偏移值,接下來還有一個表格格式的元數據,包括諸如諸如標準差一類的統計信息。
接下里的部分就是響應時間的直方圖。可以看到上面這個查詢在Query_timedistribution部分的直方圖上有兩個明顯的高峯,大部分情況下執行都需要幾百毫秒,但在快三個數據級的部分也有一個明顯的尖峯,大部分情況下執行都需要幾百毫秒,但在快三個數量級的部分也有一個明顯的尖峯,幾百微秒就能執行完成。如果這是Percona Server的記錄,那麼在查詢日誌中還會有更多豐富的屬性,可以對查詢進行切片分析到底發生了什麼。比如可能是因爲查詢條件傳遞了不同的值,而這些值的分佈很不均衡,導致服務器選擇了不同的索引;或者是由於查詢緩存命中等。在實際系統中,這種有兩個尖峯的直方圖情況很少見,尤其的對於簡單的查詢,查詢越簡單執行計劃也越穩定。
在細節報告的最後部分是方便複製,粘貼到終端去檢查表的模式和狀態的語句,以及完整的可以用explain分析執行計劃的語句。explain分析的語句要求所有的條件是文本值而不是“指紋”替代符,所以是真正可直接執行的語句。在本利中執行時間最長的一條實際的查詢。
確定需要優化的查詢後,可以利用這個報告迅速檢查查詢的執行情況。這個工具我們經常使用,並且會根據根據的情況不斷進行修正以幫助提升工具的可用性和效率,強烈建議大家都能熟練使用它。MySQL本身在未來或許會有更復雜的測量點和剖析工具,通過慢查詢日誌或者使用pt-query-digest分析tcpdump的結果,是可以找到最好的兩種方式。

3.3.2 剖析單條查詢

在丁外到需要優化的單條查詢後,可以針對此查詢“鑽取”更多的信息,確認爲什麼會花費這麼長時間執行,以及需要如何去優化。關於如何優化查詢的技術將在後面章節介紹。本章節的主要目的介紹如何方便地測量查詢執行的各部分花費多少時間,有了這些數據才能決定採用何種優化的技術。
在實際應用中,除了SHOW STATUS、SHOW PROFILE、檢查查詢日誌的條目,下面逐條介紹。

使用SHOW PROFILE

SHOW PROFILE命令是在MySQL5.1版本引進的。這也是在本書中唯一一個GA版本包含真正的查詢剖析工具。默認是禁用,但可以通過服務器變量在會話(連接)級別動態的修改。

mysql> SET profiling=1;

然後,在服務器上執行所有的語句,都會測量其消耗的時間和其他一些查詢執行狀態變更相關的數據。這個功能有一定作用,但是在未來版本會被Performance Schema所取代。儘管如此,這個工具最有用的最用還是在語句執行期間剖析服務器的具體工作。
當一條查詢提交給服務器時,此工具會聚類剖析信息到一張臨時表,並且給查詢賦予一個從1開始的整數標識符。下面是對Sakila樣本數據庫的一個視圖的剖析結果:
在這裏插入圖片描述
該查詢返回了997行記錄,花費大概1/6秒。下面看一下SHOW PROFILES有什麼結果:
在這裏插入圖片描述
首先可以看到的是以很高的精度顯示了查詢的相應時間,這很好。MySQL客戶端顯示的時間只有兩位小數,對於一些執行的很快的查詢這樣的精度是不夠的,下面繼續看接下來的輸出:在這裏插入圖片描述
在這裏插入圖片描述
剖析報告給出了每個步驟及其花費的時間,看結果很那迅速地確定那個步驟花費的時間最多。因爲輸出是按照習性順序排序的,而不是按花費的時間排序的–而實際上我們更關心花費了多少時間,這樣才能知道那些開銷比較大。到哪不行的是不能通過ORDER BY之類的命令進行重新排序。假如不使用SHOW PROFILE命令而是直接查詢INFORMATION_SCHEMA對應的表,則可以按照需要格式輸出:

mysql>  set @query_id = 1;
mysql>  SELECT STATE, SUM(DURATION) AS Total_R,
	ROUND(100 * SUM(DURATION)/
		(SELECT SUM(DURATION)
			FROM information_schema.PROFILING
			WHERE QUERY_ID=@query_id
		), 2) AS Pct_R,
		COUNT(*) AS Calls,
		SUM(DURATION)/COUNT(*) AS "R/Call"
FROM information_schema.PROFILING
WHERE QUERY_ID=@query_id
GROUP BY STATE
ORDER BY Total_R DESC

在這裏插入圖片描述
通過這個結果可以很容易查詢時間太長主要是因爲花了一大半的時間在將數據複製到臨時表這一步。那麼優化就要考慮如何改寫查詢避免使用臨時表,或者提升臨時表的使用效率。第二個消耗時間最多的是“發送數據(Sending data)”,這個狀態代表的原因非常多,可能是各種不同的服務器活動,包括在關聯時搜索匹配的行記錄等,這部分很難說優化節省多少消耗的時間。另外也要注意“結果排序(Sortingresult)”花費的時間佔比非常低,所以這部分是不值得去優化的。這是一個比較典型的問題,所以一般我們都不建議用戶在“優化排序緩存區(tuning sort buffer)”或者類似的活動上花時間。
儘管剖析報告能幫助我們丁外到哪些活動花費了最多的時間,但並不會告訴我們爲什麼會這樣。要弄清楚爲什麼複製數據到臨時表需要花費這麼多的時間,就需要深入下去,進一步剖析這一步的子任務。

SHOW STATUS

MySQL的SHOW STATUS命令返回來一些計數器。既有服務器級別的全局計數器,也有基於某個連接的會話級別的計數器。例如其中Queries在會話開始時爲0,每次提交一條查詢就會增加1。如果執行SHOW GLOBAL STATUS,則可以查看服務器級別的從服務器啓動時開始計算查詢次數統計。不同的計數器的課件範圍不一樣,不過全局的計數器也會出現在SHOW STATUS的結果中,容易被誤認爲是會話級別的,千萬不要搞錯。在使用這個命令的時候需要注意幾點,蒐集合適級別的數據是很關鍵的。如果打算優化某些特定連接觀察到的東西,測量的取捨全局級別的數據就會導致混亂。MySQL官方手冊中對所有變量是會話級還是全局級做了詳細的說明.
SHOW STATUS是一個有用的工具,但並不算一款剖析工具。SHOW STATUS的大部分結果都只是一個計數器,可以顯示某些活動如讀索引的頻繁程度,但無法給出消耗多少時間。SHOW STATUS的結構中只有一條指的是操作的時間(Innodb_row_lock_time),而且只能是全局級的,所以還是無法測量會話級別的工作。
儘管SHOW STATUS無法提供基於時間的統計但對於執行完查詢後觀察某些計數器的值還是有幫助的。最有用的計數器包括句柄計數器(handler counter)、臨時文件和表計數器等。在附錄B中有更加詳細的解釋。下面的例子演示了講會話級別的計數器重置爲0,然後查詢前面提到的視圖,在檢查計數器的結果:

mysql> FLUSH STATUS;
mysql> SELECT * FROM	user ;

在這裏插入圖片描述
從結果可以看到該查詢使用了三個臨時表,其中兩個是磁盤臨時表,並且很多的沒有用到索引的讀操作(Handler_read_rnd_next)。假設我們不知道這個視圖的具體定義,僅從結果來推測,這個查詢可能做了多表關聯(join)查詢,並且沒有合適的索引,可能其中一個子查詢簡歷了臨時表,然後和其他表做了聯合查詢。而用於保存子查詢結果的臨時表沒有索引,如此大致可以解釋這一的結果。
注意:使用SHOW STATUS本身也會創建一個臨時表,而且也會通過句柄操作訪問此臨時表,這會影響SHOW STATUS結果中對應的數字,而且不同的版本可能行爲也不盡相同。比較簽名通過SHOW PROFILES獲得到的查詢的執行計劃的結果來看,臨時表的計數器多加了2。
EXPLAIN查看查詢的執行計劃也可以獲得大部分相同的信息,但是EXPLAIN是通過估計得到的結果,而通過計數器則是實際的測量結果。例如,EXPLAIN無法告訴你臨時表是否是磁盤表,這和內存臨時表的性能差別是很大的。

使用慢查詢日誌

下面是“使用SHOW PROFILE”一節演示過的相同的查詢後抓取到的結果:
在這裏插入圖片描述
從這裏可以看到查詢確實一共創建了三個臨時表,其中兩個是磁盤臨時表。而SHOW PROFILE看起來則隱藏了信息(可能是由於服務器的執行查詢方式有不一樣的地方造成的)。淡最後對該查詢執行SHOW PROFILE的數據也會寫入到日誌中,所以在Percona Server中甚至可以記錄SHOW PROFILE的細節信息。
慢查詢日誌中記錄的條目包含了SHOW PROFILE和SHOW STATUS所有的輸出,並且還有更多的信息。所以通過pt-query-digest發現“壞”查詢後,在慢查詢日誌中可以獲得足夠有用的信息。查看pt-query-digest的報告中,其標題部分一般會有如下輸出:
在這裏插入圖片描述
這樣就可以直接跳轉到細節部分了。另外,pt-query-digest能夠出息Procona Server在慢查詢日誌中正價的所有鍵值對,並且會自動在報告中打印更多的細節信息。

使用Performance Schema

mysql> SELECT EVENT_NAME,COUNT_STAR,SUM_TIMER_WAIT FROM events_waits_summary_global_by_event_name ORDER BY SUM_TIMER_WAIT DESC LIMIT 5;

在這裏插入圖片描述
對於大多數用戶來講,直接通過Performance Schema的裸數據獲得有用的結果相對過於複雜和底層。主要是爲了測量當爲提升服務器性能而修改MySQL源代碼是使用,包括等待和互斥鎖。MySQL5.5中的特性對於高級用戶也很有價值,而不僅僅爲開發者使用,但還是需要開發一些前端工具以方便用戶使用和分析結果。

3.3.3 使用性能剖析

當獲得服務器或者查詢的剖析報告後,怎麼使用?好的剖析工具將潛在的問題顯示出來,淡最終的解決方案還需要用戶來決定。優化查詢時,用戶需要對服務器如何執行查詢有比較深的瞭解。剖析報告儘可能多地收集需要的信息、給出診斷問題的正確方向,以及爲其他諸如EXPLAIN等公交提供基礎信息。後續章節繼續討論。
儘管一個擁有完整測量向歐盟向的剖析報告可以讓事情變得簡單,但現有系統通常都沒有完美的測量支持。從前面的例子來說,雖然推斷出是臨時表和沒有索引的讀導致查詢響應時間過長,但卻沒有明確的證據。因爲無法測量所有需要的信息,或者測量的範圍不明確,有些問題就很難解決。例如,可能沒有集中在需要優化的地方測量,而是測量的服務器層面的活動;或者測量的是查詢開始之前的計數器,而不是查詢開始後的數據。
也有其他的可能性。設想一下正在分析慢查詢,發現一個很簡單的查詢正常的情況下非常快,卻有幾次不合理地執行了很長時間。手工執行一遍,發現也非常快,然後使用EXPLAIN查詢其執行計劃,也正確的使用了索引。然後嘗試修改WHERE條件中使用不同的值,以排除緩存命中的可能,也沒發現什麼問題,這可能是什麼原因呢?
如果使用官方版本的MySQL,慢查詢日誌中沒有執行計劃或者詳細細心,對於偶爾記錄到的這幾次查詢異常慢的問,很難知道其原因在哪,因爲心虛有限。可能是系統中其他東西消耗了資源,比如正在備份,也可能是某種類型的鎖或者爭用阻塞了查詢的進度。這種間歇性的問題將在下一章討論。

3.4 診斷間歇性問題

間歇性的問題比如系統偶爾停頓或者慢查詢,很難診斷。有些幻影問題只在沒有注意到的時候才發生,而且無法確認如何重現,診斷這樣的問題往往要花費很多時間,有時候甚至需要幾個月。在這個過程中,有些人會嘗試不斷試錯的方式來診斷,有時候甚至會想要通過隨機地改變一些服務器的設置來僥倖找到問題。
儘量不要使用試錯的方式來解決問題,低效,並且有可能導致結果變得更壞。如果一時無法定位問題,可能是測量的方式不正確,或者是車上的點選擇有誤,或者使用的工具不合適。
爲了演示爲什麼要避免試錯的診斷方式,下面列舉了我們認爲已經解決的一些間歇性數據庫性能問題的實際案例:

  • 應用通過curl從一個運行很慢的外部服務來獲取匯率報價的數據。
  • memcached緩存中的一些重要條目過期,導致大量請求落到MySQL以重新生成緩存條目。
  • DNS查詢偶爾有超時的現象
  • 可能由於互斥鎖爭用,或者內部刪除查詢緩存的算法效率太低的緣故,MySQL的查詢緩存有時候會導致服務有短暫的停頓。
  • 當併發度超過某個閾值時,InnoDB的擴展性限制導致查詢計劃的優化需要很長時間。
    從上面可以看到有些問題確實是數據庫的原因,也有些不是。只有在問題發生的地方通過觀察資源的使用情況,並儘可能滴測量出數據,才能避免在沒有問題的耗費精力。

3.4.1 單條查詢問題還是服務器問題

首先需要確認這是單條查詢的問題,還是服務器的問題。這將爲解決問題指出正確的方向。如果服務器上所有的程序都突然變慢,有突然編號,每一條查詢都變慢了,那麼慢查詢就可能不一定是原因,而是由於其他問題導致的結果。反過來說,如果服務器整體運行沒有問題,只有某條查詢偶爾變慢,就需要將注意力放在這條特定的查詢上面。
那麼如何判定是服務器問題還是單條查詢的問題能?如果問題不停地週期性出現,那麼可以通過三種技術來解決,下面將意義描述。

使用SHOW GLOBAL STATUS

這個方法實際上就是比較高的頻率比如一秒執行一次SHOW GLOBAL STATUS命令捕捉數據,問題出現是,則可以通過某些計數器(比如Threads_running、Threads_conndected、Questions和Queries)的“尖刺”或者“凹陷”來發現。這個方法比較簡單,對服務器也很小,所以是一個花費時間不多卻能很火地瞭解問題的好方法。下面是示例命令及其輸出;
在這裏插入圖片描述
這個明明每秒就捕獲一次SHOW GLOBAL STATUS的數據,輸出給awk計算並輸出每秒的查詢數、Threads_connected和Threads_running(表示當前正在執行查詢的線程數)。這三個數據的趨勢對於服務器級別偶爾停頓的敏感性很高。一般發生此類問題時,根據原因的不同和應用鏈接數據庫方式的不同,每秒的查詢數一般會下跌,而其他兩個則至少有一個會出現尖刺。在這個例子中,應用使用了連接池,所以Threads_connected沒有變化。淡正在執行查詢的線程數明顯上升,同時每秒的查詢數相比正常的數據有嚴重的下跌。
如何解決這個現象呢?憑猜測有一定的風險。但在實際中有兩個原因的可能性比較大。其中之一是服務器內部碰到了某種瓶頸,導致新查詢在開始執行前因爲需要獲取老查詢正在等待的鎖而造成堆積。另外一個常見的原因是服務區突然遇到大量查詢請求的衝擊,比如前端的memcached突然失效導致的查詢風暴。
這個命令每秒輸出一行數據,可以運行幾個小時或者幾天,然後將結果繪製成圖形,這樣就可以方便地發現是否有趨勢的突變。如果問題確實是間歇性的,發送頻率又較低,也可以根據需要儘可能長時間的運行此命令,知道發現問題再回頭看輸出結果。大多數情況下,通過輸出結果都可以明確地定位問題。

使用SHOW PROCESSLIST

這個方法是通過不停捕獲SHOW PROCESSLIST的輸出,來觀察是否有大量線程處於不正常的狀態獲取有其他不正常的特徵。例如查詢很少會長時間處於“statistics”的狀態,這個狀態一般是指服務器在查詢優化階段如何確定表關聯的順序–通常是非常快的。
另外,也會很少見到大量線程報告是當前用戶是“未經驗證的用戶(Unauthenticated user)”,這只是在鏈接握手的中間過程的狀態,當客戶端等待輸入用於登錄的用戶信息的時候在會出現。
在這裏插入圖片描述
如果要查看不同的列,只需要修改grep的格式即可,。在大多數情況下,State列都非常有用。例子中可以看到,有很多線程處於查詢執行的記過部分的狀態,包括“freeing items”、“end”、"cleaning up"和“Logging slow query”。事實上,在案例中的這臺服務器上,同樣模式或類似的輸出採樣出現了很多次。大量的線程處於“freeing items”狀態是出現大量問題查詢很明顯的特徵和指示。
用這種技術查找問題,上面的命令行不是唯一的方法。如果MySQL服務器的版本較新,也可以直接查詢INFOFMATION_SCHEMA中的PROCESSLIST表;或者使用innotop工具以比較高的頻率刷新,以觀察屏幕上出現的不正常查詢堆積。上面演示的這個例子是由於InnoDB內部的爭用和髒塊刷新所致,但有時候原因可能比這個要簡單的多。一個景點的例子是很多處查詢處於“Locked”的狀態,這是MyISAM的一個典型問題,他的表級別鎖定,在寫請求較多時,可能迅速導致服務器級別的線程堆積。

使用查詢日誌

如果要通過查詢日誌發現問題,需要開啓慢查詢日誌並在全局級別設置long_query_time爲0,並且要確認所有的鏈接都採用新的設置。這可能需要重置所有鏈接以使新的全局設置生效;或者使用Percona Server的一個特性,可以在不斷開現有鏈接的情況下動態地設置強制生效。
如果因爲某些原因,不能設置慢查詢日誌記錄所有的查詢,也可以通過tcpdump和pt-query-digest工具來模擬替代。要注意找吞吐量突然下降的時間段日誌。查詢是在完成階段才寫入到慢查詢日誌的,所以堆積會造成大量查詢處於完成階段,知道阻塞其他查詢的資源佔用者才能執行完成。這種行爲特徵的一個好處是,當遇到聽聽量突然下降時,可以歸咎於吞吐量下降後完成的第一個查詢(有時候也不一定是第一個查詢,其他查詢可以不受影響繼續運行。所以不能完全依賴這個經驗)。
好的工具可以幫助診斷這類問題。下面的例子只有一行代碼,卻可以根據MySQL每秒將當前時間寫入日誌中的模式統計每秒的查詢數量:
在這裏插入圖片描述
從上面的輸出可以看到有吞吐量突然下降的情況發生,而且在下降之前還有一個突然的高峯,僅從這個輸出而不去查詢當時的詳細 情況很難確定發生了什麼,但應該可以說這個突然的高峯和隨後的下降有一定的關聯。這種現象很奇怪,值得去日誌中挖掘該時間段的詳細信息(實際上通過日誌的詳細信息,可以發現圖突然地高峯時間段有很多鏈接被斷開的現象,可能是有一臺應用服務器重啓導致。所以並不是所有的問題的都是MySQL的問題)。

理解發現的問題(Making sense of the findings)

可視化的數據最具有說服力。上面只演示了很少的幾個例子,但在實際情況中,利用上面的工具診斷時可能產生大量的輸出結果。可以選擇用gnuplot或者R,或者其他繪圖工具將結果繪製成圖形。這些繪圖工具速度很快,比電子表格要快得多,而且可以對圖上的一些異常的地方進行縮放,這比在終端中通過滾輪翻看文字要好用的多。
建議診斷問題時優先使用前兩種方法:SHOW STATUS和SHOW PROCESSLIST。這兩種方法的開銷很低,而且可以通過簡單的shell攪拌或者反覆執行的查詢來交互式地收集數據。分析慢查詢日誌則相對他要困難一些,經常會發現一些蛛絲馬跡,淡仔細去研究的時候可能又消失了。這樣我們很容易會任務其實沒有問題。

3.4.2 捕獲診斷數據

當出現間歇性問題時,需要儘可能地多收集所有數據,而不只是問題出現時的數據。雖然這樣會收集大量的診斷數據,但總比真正能夠診斷問題的數據沒有被收集到的情況要好。
1.一個可靠且實時的“觸發器”,也就是是能夠區分什麼時候問題出現的方法。
2.一個收集診斷數據的工具。

診斷觸發器

觸發器很重要。這是在問題出現時能夠捕獲數據的基礎。有兩個常見的問題可能導致無法達到預期的結果:誤報(false positive)或者漏檢(false negative)。誤報是指收集了很多診斷數據,但期間沒有發生問題。而漏檢則指在問題時沒有捕獲到數據,錯失機會。所以在開始收集數據之前多花一些時間來確認觸發器能夠真正的識別問題是划算的。
那摩好的觸發器的標準是什麼呢?比如前面的Threads_running的趨勢在出現問題時會比較敏感,而沒有問題的時候比較平穩。另外SHOW PROCESSLIST中線程中的異常狀態尖峯也是一個不錯的指標。當然還有很多其他的方法,包括SHOW INNODB STATUS的特定輸出、服務器的平均負載尖峯等。關鍵是找到一些能和正常時的閾值進行比較的指標。通常情況下是一個技術,比如正在運行的線程的數量、處於“freeing items”狀態的線程的數量等。當要計算線程某個狀態的數量時,grep的-c選項非常有用:
在這裏插入圖片描述
選擇一個合適的閾值很重要,既要足夠高,以確保正常時不會觸發;又不能太高,要確保問題發生時不會錯過。另外要注意,要在問題開始時就捕獲數據,及更不能將閾值設置得太高。問題持續上升的趨勢一般會導致更多問題的發生,如果在問題導致系統快要崩潰時纔開始捕獲數據,就很難診斷到最初的根本原因。如果可能,在問題還是涓涓細流的時候就要開始收集數據,而不要等到波濤洶涌的時候纔開始。舉個例子,Threads_connected偶爾出現非常高的尖峯值,在幾分鐘時間內會從100衝到5000或者更高,所以閾值設置到4999也可以捕獲到問題,但爲什麼要等到那麼高的時候才收集數據呢?如果正常該值一般不超過150,將閾值設置爲200或者300會更好些。
回到前面關於Threads_running的例子,正常情況下的併發度不超過10.但是閾值設置爲10並不是一個好主意,很可能會導致很多誤報。即使設置爲15也不夠,還是會有很多正常的波動會到這個範圍。當併發運行線程到15的時候可能也會有少量的堆積的情況,但可能還沒有到問題的引爆點。淡也應該在糟糕到一眼就能看出問題前就清晰地識別出來,對於這個例子,我們建議的閾值設置爲20.
我們當然希望在問題確實發生的時候能夠捕獲到數據,到哪有時候也需要稍微等待一下以確保不是誤報或者短暫的尖峯。所以,最後的觸發條件可以這樣設置:每秒監控狀態值,如果Threads_running連續5秒超過20,就開始蒐集診斷數據。
所以我們需要利用一種工具來監控服務器,當達到觸發條件時能蒐集數據。當然可以自己編寫腳本實現,不過不用那麼麻煩,Percona Tookit中的pt-stalk就是爲這種情況設計的,這個工具有很多有用的特性,只要碰到過類似的問題就能明白這些特性的必要性。例如,監控磁盤的可用空間,所以不會因爲蒐集太多數據將空間耗盡而導致服務器崩潰。如果之前碰到過這種情形,就會理解這一點。
pt-stalk是用法很簡單。可以配置需要監控的變量、閾值、檢查的頻率等。還支持一些比較時間需要更多花哨特性,但在這個例子中有這些已經足夠了。在使用之前建議先閱讀說明文檔。

需要收集什麼樣的數據

現在已經確定了診斷觸發器,可以開始啓動一些進程來蒐集數據了。但需要蒐集什麼樣的數據呢?就像前面說的,但是是儘可能蒐集所有能蒐集的數據,淡只在需要的時間段內蒐集。包括系統的狀態、CPU利用率、磁盤使用率和可用空間、ps的輸出採樣、內存利用率,以及可以從MySQL獲得的信息,如SHOW STATUS、SHOW PROCESSLIST和SHOW INNODB STATUS。這些在診斷問題時都需要用到。
執行時間包括用於工作時間和等待時間。當一個未知問題發生時,一般來說有兩種可能:服務器需要做大量工作,從而導致大量消耗CPU;或者在等待某些資源被釋放。所以需要用不同的方法蒐集診斷數據,來確認是何種原因;剖析報告用於確認是否有太多的工作,而等待分析則用於確認是否存在大量等待。如果是未知的問題,怎麼知道將精力集中在那個方面呢?只能將兩種數據都儘量收集。
在GNU/Linux平臺,可用於服務器內部診斷的一個重要工具oprofile。後面會展示一些例子。也可以使用strace剖析服務器的系統調用,但在生產環境中使用它有一定的風險。後面還會繼續討論。如果要剖析查詢,可以使用tcpdump。大多數MySQL版本無法方便地打開和關閉慢查詢日誌,此時可以通過監聽TCP流量來模擬。此外,網絡流量在其他一些分析中也非常有用。
對於等待分析,常用的方法是GDB跟蹤。MySQL內的.MySQL內的線程如果卡在一個特定的地方很長時間,往往都有相同的堆棧跟蹤信息。跟蹤的過程是先啓動gdb,然後附加(attach)到mysql進程,將所有的線程的堆棧都轉儲出來,然後可利用一些簡短的腳本,將類似時的堆棧跟蹤信息做彙總,再利用sort|uniq|sort的方式排序出總計最多的堆棧信息。稍後將演示如何用pt-pmp工具來完成。
也可以使用SHOW PROCESSLIST和SHOW INNODBSTATUS的快照信息觀察線程的事務的狀態來進行等待分析。
蒐集所有的數據聽起來工作量很大。但是pt-collect會提供一些幫助,也是Percona Toolkit中的一員。pt-collect一般使用pt-stalk來調用。因爲涉及很多重要數據的收集,所以需要用root權限來運行。默認情況下,啓動後會收集30秒的數據,然後退出。對於大多數問題的診斷來說這已經足夠,但是如果有誤報(false positive)的問題出現,則可能收集的信息就不夠。工具不需要任何配置,配置都是通過pt-stalk進行的。系統中最好安裝gdb和oprofile,然後在pt-stalk中配置使用。另外mysqld也需要有調試符號信息。當觸發條件滿足是,pt-collect會很好地收集完整的數據。它會在目錄中創建時間戳文件。

解釋結果數據

如果已經正確地設置好觸發條件,並且長時間運行pt-stalk,則只需要等待豬狗長的時間捕獲幾次問題,就能夠得到大量的數據來進行篩選。可以先根據兩個目的來查看一些東西。第一,檢查問題是否真的發生了,因爲很多的樣本數據需要檢查。如果是誤報就會浪費大量的時間。第二,是否有非常明顯的跳躍性變化。
查看異常的查詢或事務的行爲,以及異常的服務器內部行爲通常是最有收穫的。查詢或事務的行爲可以顯示否由於使用服務器的方式導致的問題:性能低下的SQL查詢、使用不當的索引、設計糟糕的數據庫邏輯架構等。通過抓取TCP流量或者SHOW PROCESSLIST輸出,可以獲得查詢的事務出現的地方,從而知道用戶對數據庫進行了什麼操作。通過對服務器內部行爲則可以清楚服務器是否有bug,或者內部的性能和擴展性是否有問題。這些信息在類似的地方都可以看到,包括在oprofile或者gdb的輸出中,但要理解則需要更多的經驗。
如果遇到無法解釋的錯誤,則最好將收集到的所有數據大把,提交給技術支持人員進行分析。詳細的數據對於支持人員非常重要。另外也可以將Percona Toolkit中另外兩款pt-mysql-summary和pt-summary的輸出結果打包,這兩個工具會輸出MySQL的狀態和配置信息,以及操作系統和硬件的信息。
Percona Toolkit還提供了一款快速檢查收集到數據樣本的工具:pt-sift。這個工具會輪流導航到所有的樣本數據,得到每個樣本的彙總信息。如果需要也可以鑽取到詳細信息。可以少打很多字。
前面演示了狀態計數器和線程狀態的例子。在本章結束之前,將再給出一些operfile和gdb的輸出例子。下面是一個問題服務器上的operfile輸出,你能找到問題嗎?
在這裏插入圖片描述
如果你大 答案是“查詢緩存”,那麼恭喜你答對了。在這裏查詢緩存導致了大量的工作,並拖慢了整個服務器。這個問題是一夜之間突然發生的,系統變慢了50倍,淡這期間系統並沒有做過任何其他變更。關閉查詢緩存後系統性能恢復了正常。這個例子比較簡單地解釋了服務器內部行爲對性能的影響。
另外一個重要的關於等待分析的性能瓶頸分析工具便是gdb的堆棧跟蹤。下面是Udine一個線程的堆棧跟蹤的輸出結果,爲了便於印刷做了一些格式化:
在這裏插入圖片描述
堆棧信息需要自下而上來看。也就是說,線程當前正在執行的是pthread_cond_wait函數,這是由os_event_wait_low來調用的。繼續往下,看起來線程試圖進入到InnoDB內核(srv_conc_enter_innodb0),但被放入了一個內部隊列中(os_event_wait_low),原因應該是內核中線程數已經操作innodb_thread_concurrency的限制。當然,要真正發揮堆棧跟蹤的價值需要將很多信息聚合在一起看。這種技術是有Domas Mituzas推廣的,他以前的MySQL的支持工程師,開發了情人剖析器“poor man’s profiler”,和其他人開發了更多的收集和分析堆棧跟蹤的工具,可以查看https://poormansprofiler.org/
在Percona Toolkit中我們也開了了有、一個類似的啓窮人剖析器,叫做pt-pmp。這是一個用shell和awk腳本編寫的工具,可以將類似的堆棧跟蹤輸出合併到一起,然後通過sort|uniq|sort將常見的條目在最前面輸出。下面是一個堆棧跟蹤的完整例子,用過此工具將重要的信息展示出來。使用-l 5的選項指定了堆棧跟蹤不超過5層,以免因太多前面部分想用而後面部分不同的跟蹤信息而導致無法聚合在一起的情況,這樣才能更好地顯示到底在哪裏產生了等待:
在這裏插入圖片描述
第一行是MySQL中非常典型的空閒線程的一種特徵,所以可以忽略。第二行纔是最有意思的地方,看起來大量線程正準備進入到InnoDB的內核中,淡都被阻塞了。從第三行可以看出許多線程都帶等待某些互斥鎖,但具體是什麼鎖,不清楚,因爲更深城池的被截斷了,如果需要確切知道什麼是互斥鎖,則需要更大的-l選項重跑一次。一般來說這個堆棧顯示很多線程都在等待進入InnoDB,這是爲什麼呢?這個工具並不清楚,需要從其他的地方來入手。
從前面的堆棧跟蹤和oprofile報表來看,如果不是MySQL和InnoDB源碼方面的專家,這種類型的分析很難進行。如果用戶在進行此類分析遇見問題,通常需要請教這樣的專家。

3.4.3 一個診斷案例

本章節中逐步演示一個客戶時間碰到的間歇性性能問題的診斷過程。這個案例是診斷需要具備MySQL、InnoDB和GUN/Linux的相關知識。要嘗試從瘋狂中找到條理:閱讀本章節並保持對之前的假設和猜測的關注,保持對之前基於合理性和基於可度量的方式的關注。

  1. 首先,問題是什麼?一定要清晰地描述出來,費力去解決一個錯誤的問題是常有的事。在這個案例中,用戶抱怨說每隔一兩天,服務器就會拒絕鏈接,報max_connections錯誤。這種情況一般會持續幾秒到幾分鐘,發生的會就愛你非常隨機。
  2. 其次,爲解決問題已經做過什麼操作?在這個案例中,用戶沒有爲這個問題做個任何操作。這個信息非常有幫助,因爲很少有其他事情會像另外一個人來描述一件事情發生的確切順序和曾經做過的改變及其後果一樣難以理解。如果一臺服務器遭受過未知的變更,產生了未知的結果,問題就更難解決了,尤其是時間有非常有限的時候。

搞清楚這個兩個問題後,就可以開始了。不僅需要了解服務器的行爲,也需要花點時間去梳理一下服務器的狀態、參數配置,以及軟硬件環境。使用pt-summary和pt-mysql-summary工具可以獲得這些信息。這個例子的服務器有16個CPU核心,12GB內存,數據量有900MB,且全部採用InnoDB存儲引擎,存儲在一塊SSD固態硬盤上。服務器操作系統GBU/Linux,MySQL版本5.1.37,存儲引擎版本InnoDB plugin 1.0.4。過去的數據庫沒有出現問題,大多數問題都是由應用程序的不良行爲導致的。初步建成服務器也沒有發現明顯的問題。查詢 一些優化的空間,但大多數相應時間都不到10毫秒。所以認爲正常情況下數據庫服務器運行良好(這一點比較重要,因爲很多問題一開始只是零星出現,慢慢地積累成大問題。比如RAID陣列中壞了一塊硬盤案例情況)。
我們安裝好診斷工具,在Thread_connected上設置觸發條件,正常情況下Threads_connected的值一般都少於15,但在發生問題時該值Kenneth飆升到幾百。下面我們會先給出一個樣本數據的收集結果,後續再來評論。首先試試看,能否從大量輸出中找出問題的重點在哪裏:

  • 查詢活動從1000到10000的QPS,其中有很多是“垃圾”命令,比如ping一下看是否存活。其餘大部分是SELECT命令,大約每秒300~2000次,只很少的update命令(大約每秒5次):
  • 在SHOW PROESSLIST中主要有兩種類型的查詢,只是在WHERE條件中的值不一樣。下面是查詢狀態的彙總數據:
    在這裏插入圖片描述
  • 大部分查詢都是索引掃描或者範圍掃描,很少有全表掃描或者關聯掃描的情況。
  • 每秒大約有20-100次排序,需要排序的行大約有1000-12000行。
  • 每秒大約創建12-90個臨時表,其中有3-5個磁盤臨時表。
  • 沒有鎖或者查詢緩存的問題
  • 在SHOW INNODB STATUS中可以觀察到主要線程的狀態是“flushing buffer pool pages”,但只有很少的髒頁需要刷新(Innodb_buffer_pool_pages_dirty),innodb_biffer_pool_pages_flushed也沒有太大變化,日誌順序號(log sequence number)和最後檢查點(last checkpoint)之間的差距也很少。InnoDB緩存池還遠麼有用滿;緩存池比數據集還要大很多。大多數現場在等待InnoDB隊列;“12 queries inside InnoDB,495 queries in queue”(12個查詢在InnoDB內部執行,495個在隊列中)。
  • 每秒捕獲一次iostat輸出,持續30秒。從輸出可以發現沒有磁盤讀,而寫操作則接近了“天花板”,所以I/O平均等待時間和隊列 長度都非常高。下面是部分輸出結果,爲便於打印輸出,這裏截取了部分字段:
    在這裏插入圖片描述
  • vmstat的輸出也驗證了iostat的結果,並且CPU的大部分時間是空閒的,只是偶爾在寫尖峯時有一些I/O等待時間(最高約佔9%的CPU)。
    是不是感覺腦袋裏塞滿了東西?當你深入一個系統的細節並且沒有任何先入爲主的觀念時,很容易碰到這種情況,最終只能檢查所有的情況。很多倍檢查的地方最終要嗎是完全正常的,妖魔發現是問題導致的結果而不是問題產生的原因。儘管此時我們會有很多關於問題原因的猜測,但還是需要繼續檢查下面給出的oprofile報表,並且在給出更多數據的時候添加一些評論和解釋:
    在這裏插入圖片描述
    這裏大多數符號(symbol)代表的意義並不是那麼明顯,而大部分的時間都消耗在內核符號(no-vmlinux)和一個通用的mysqld符號中,這兩個符號無法告訴我們更多的細節。不要被多個ha_innodb.so符號分散了注意力,看一下他們佔用的百分比就知道了,不管它們在做什麼,其佔用時間都很少,所以應該不是問題所在。這個例子說明,僅僅從剖析報表觸發是無法得到解決問題的結果的。我們追蹤的數據是錯誤的。如果遇到上述例子這樣的情況,需要繼續檢查其他的數據,尋找問題根源更爲明顯的證據。
    到這裏,如果希望從gdb的堆棧跟蹤進行等待分析,可以參考3.4.2節最後的部分內容。那個案例就是我們當前正在診斷的這個問題。回想一下,當時堆棧跟蹤分析的記過是等待進入到INNODB內核,所以SHOW INNODB STATUS的輸出解僱喲中有“12 queries inside InnoDB, 495 queries in queue”.
    從上面的分析發現問題的關鍵點了嗎?沒有。我們看到了許多不同問題可能的症狀,根據經驗和直覺可以推測至少有兩個可能的原因。淡也有一些沒有意義的地方。如果再次檢查一下iostat輸出,可以發現wsec/s列顯示了至少在6秒內,服務器寫入幾百MB的數據到磁盤。每個磁盤扇區是512B,所以這裏採樣的結果顯示每秒最多寫入了150MB的數據。然而整個數據庫也只有900MB大小,系統的壓力有主要是SELECT查詢,怎麼會出現這種情況呢?
    對一個系統進行檢查的時候,應該先問一下自己,是否也碰到過上面這種明顯不合理的問題,如果有就需要深入調查。應該儘量跟進每一個可能的問題直到發現結果,而不要被離題太多的各種情況分散了注意力,以至於最後忘記了最初要調查的問題。可以把問題先寫到小紙條上,檢查一個劃掉一個,最後再確認問題都已將完成了調查。
    在這一點上,我們可以直接得到一個結論,但卻有可能是錯誤的。可以看到主線程的狀態是InnoDB正在刷新的髒頁。在狀態輸出中出現這樣的狀況,一般都意味着刷新已經延遲了。我們知道這部版本的InnoDB存在“瘋狂刷新”的問題(檢查點停頓)。發生這樣的情況是因爲InnoDB沒有按照時間均勻分佈刷新請求,而是隔一段時間突然請求一次強制檢查點導致大量刷新的操作。這種機制可能會導致InnoDB內部發生嚴重的阻塞,導致所有的操作需要排隊等待進入內核,從而引發InnoDB上一層的服務器產生堆積。在第二章中演示的例子就是一個因爲“瘋狂刷新”而導致性能週期性下跌的問題。很多類似的問題都是由於強制檢查點導致的,但這個案例中卻不是這個問題。有很多方法可以證明,最簡單的方法就是查詢SHOW STATUS的計數器,最終一下Innod_buffer_pool_pages_flushed的變化,之前已經提到了,這個值並沒有怎麼正價。另外,注意到InnoDB緩衝池中也沒有大量的髒頁需要刷新,肯定不到幾百MB。這並不值得驚訝,因爲這個服務器的工作壓力幾乎都是SELECT查詢。所以可以得到一個初步的結論,我們要關注的不是InnoDB刷新的問題,而應該是刷新延遲的問題,但這只是一個現象,而不是原因。根本的原因是磁盤的I/O已經包含,InnoDB無法完成其I/O操作。至此我們消除了 一個可能的原因,可以從基於直覺的原因列表中將其劃掉了。
    從結果中將原因區別出來有時候會很困難。當一個問題看起來很眼熟的時候,可以跳過檢查階段直接診斷。當然最好不要走這樣的捷徑,但有時候依賴直接也非常重要。如果有什麼地方看起來眼熟,明智之舉還是需要花費一點時間去測量一下充分必要條件,以證明是否就是問題所在。這樣可以節省大量時間,避免查看大量其他的系統和性能數據。不過也不要過於相信直覺而直接下結論.應該去收集相關的證據,尤其是能證明直覺的證據。
    下一步是嘗試找出什麼導致服務器的I/O利用率異常的高。首先應該注意到前面已經提到的“服務器有連續幾秒內每秒寫入了幾百MB的數據到磁盤,而數據庫一共只有900MB的大小,怎麼會發送這樣的情況?”,注意到這裏已經隱式地假設是數據庫導致了磁盤的寫入。那麼有什麼證據表明是數據庫導致的呢?當你有未證實的相符、或者覺得不可思議時,如果可能 的話就應該進行測量,然後排除掉一些懷疑。
    我們看到了兩種的可能性;要麼是數據庫導致了I/O(如果能找到源頭,那摩就可以找到問題的原因);要麼不是數據庫導致了所有的I/O而是其他什麼導致的,而系統因爲缺少了I/O資源影響了數據庫的性能。我們也很消息地盡力避免引入另外一個隱式的假設:磁盤忙併不一定意味着MySQL存在問題。要記住,這個服務器的主要壓力是內核的讀取,所以也很可能出現磁盤長時間無法響應但沒造成嚴重問題的現象。
    如果你一直跟隨我們的推理邏輯,就可以發現還需要回頭檢查一下另外一個假設。我們已經知道了磁盤設備很忙,因爲其等待時間很高。對於固態硬盤來說,其I/O平均等待時間一般不會超過1/.4秒。實際上,從iostat的輸出結果也可以發現磁盤本身的響應還是很快的,但請求在塊設備隊列中等待了很長時間才能進入到磁盤設備。但要記住的試試,這是iostat的輸出結果,也可能是錯誤的信息。

究竟是什麼導致了性能低下?
1.資源被過度使用,餘量已經不足以正常工作。
2.資源沒有被正確的配置。
3.資源已經損壞或者失靈。

在檢查過所有的診斷數據之後,接下來的任務就很明顯了:測量出什麼導致了I/O的消耗。不幸的是,客戶當前使用到GNU/Linux版本對此支持不力。通過一些工作我們可以做一些相對準確的猜測,但首先還是需要探索一下其他的可能性。我們可以測量有多少I/O來自於MySQL,但客戶使用的MySQL版本較低以至於缺乏一些診斷功能,所以也無法提供確切 和有利的支持。
作爲替代,基於我們已經知道MySQL如何使用磁盤,我們來觀察MySQL的I/.O情況。通常來說,MySQL只會寫數據、日誌、排序文件和臨時表到磁盤。從前面的狀態計數器和其他信息來看,首先可以排除數據和日誌的寫入問題。那麼,只能假設MySQL突然寫入大量數據到臨時表或者排序文件,如何來觀察這種情況能?有兩個簡單的方法;一是觀察磁盤的可用空間,二是通過lsof命令觀察服務器打開的文件句柄。這兩個方法我們都採用了,結果也足以滿足我們的需求。下面是問題期間每秒運行df -h的結果:
在這裏插入圖片描述
下面則是lsof的數據,因爲某些原因每5秒才收集一次。我們簡單地將mysqld在/tmp中打開的文件大小做了加總,並且把總大下和採樣的時間戳一起輸出到結果文件中:
在這裏插入圖片描述
從這個結果可以看出,在問題之初MySQL大約寫了1.5G的數據到臨時表,這和之前在SHOW PROCESSLIST中有大量的“Copying to tmp table”相吻合。這個數據表明可能是某些效率低下的查詢風暴耗盡了磁盤資源。根據我們的工作直覺,出現這種情況比較普遍的原因是緩存失效。當memcached中所有緩存的條目同時失效,而又有很多應用需要同時訪問的時候就會出現這種情況。我們給開發人員出示了部分採樣的查詢,並討論 了這些查詢的作用。實際情況是,緩存同時失效就是罪魁禍首。一方面開發人員在應用層面解決緩存失效的問題,另一方面我們也修改了查詢,避免使用磁盤李世彪。這兩個方法的任何一個都可以解決問題,當然最後是兩個都是實施。
如果讀者一直順着前面的思路,可能還會有一些疑問。在這裏我們可以稍微解釋一下:
Q:爲什麼我麼不一開始就優化慢查詢?
A:因爲問題不在於慢查詢,而是“太多連接”的錯誤。當然,因爲慢查詢,太多查詢的時間過長而導致堆積在邏輯上也是成立的。淡也有可能是其他原因導致的鏈接過多。如果沒有找到問題的真正原因,那麼回頭查看慢查詢或其他可能的原因,看是否能夠改善是很自然的事情。淡這樣做很多情況下會讓問題變得更糟。
Q:但是查詢由於糟糕的執行計劃而執行緩慢不是一種警告嗎?
A:在事故中確實如此。但慢查詢到底是原因還是結果?在深入調查之前是無法知曉的。在正常的時候這個查詢也是正常運行的。一個查詢需要執行filesort和創建臨時表並不一定意味着就是有問題的。儘管消除filesort和臨時白哦通常來說是“最佳實踐”。
通常的“最佳實踐”自然有他的道理,淡不一定是解決某些特殊問題的“靈丹妙藥”。比如說問題可能是很簡單的配置錯誤。我們碰到很多這樣的案例,問題本來是由於錯誤的配置導致的,卻去優化查詢,這不但浪費了時間,也使得真正的問題被解決的時間被拖延了。
Q:如果緩存項被從先生成了很多次,是不是會導致很多同樣的查詢呢?
A:這個問題確實沒有調查到。如果是多線程重新生成同樣的緩存項,那麼確實有可能導致產生很多同樣的查詢(這和很多同類型的查詢不同,比如WHERE字句中的參數可能不一樣)。注意要這樣會刺激我們的直覺,並更快地帶我們找到問題的解決方案。
Q:每秒有幾百次SELECT查詢,但只有五次UPDATE。怎麼能確定這五次UPDATE的壓力不會導致問題呢?
A:這些UPDATE有可能對服務器造成很大的壓力。我們沒有將真正的查詢展示出來,因爲這樣可能會將事情搞得更混亂。淡有一點可以明確,某種查詢的絕對數量不一定有意義。
Q:I/O風暴最初的證據看起來不是很充分?
A:是的,確實是這樣。有很多種解釋可以說明爲什麼一個這麼小的數據庫可以產生這麼大量的寫入磁盤,或者說爲什麼磁盤的可用空間下降的這麼快。這個問題中使用MySQL和GNU/Linux版本都很難對一些問題進行測量。
儘管在很多時候我們可能扮演“魔鬼代言人”的角色,淡我們還是儘量平衡成本和潛在的利益爲第一優先級。越是難以測量的時候,成本/收益比越攀升,我們也更原因接收不確定性。
Q:之前說“數據庫過去從來沒有出現問題”是一種偏見嗎??
A:是的,這是一種偏見。如果抓住問題很好;如果沒有,也可以證明我們都有偏見很好的例子。
至此我麼要結束這個案例的學習了。需要指出的是,如果使用了諸如New Relic這樣的剖析工具,即使沒有我們的參與,也可能解決這個問題。

3.5 其他剖析工具

3.5.1 使用USER_STATISTICS表

Percona Server和MeriaDB都引入了一些額外的對象級別使用統計的INFOMATION_SCHEMA表,這些最初是由Google開發的。這些表對於查找服務器各部分的實際使用情況非常有幫助。下面就是這些表:
在這裏插入圖片描述
這裏我麼不詳細演示對這些表的所有有用的查詢,下面幾個要點說明一下:

  • 可以查找使用最多或者使用最少的表和索引,通過讀取次數或者更新次數,或者兩者一起排序。
  • 可以找出從未使用的索引,可以考慮刪除。
  • 可以看看複製用戶的CONNECTED_TIME和BUSY_TIME,以確認是否會很難跟上主庫的進度。

3.5.2 使用strace

strace工具可以調查系統調用的情況。有好幾種可以使用的方法,其中一種是計算系統調用的時間並打印出來:
在這裏插入圖片描述
這種用法和oprofile有點像。但是oprofile還可以剖析程序的內部符號,而不僅僅是系統調用。另外,strace攔截系統調用使用的是不同於oprofile的技術,這會有一些不可預期性,開銷也更大些。strace度量是使用的是實際時間,而oprofile使用的是花費CPU的週期。舉個例子,當I/O等待出現問題的時候,strace能將他們顯示出來,因爲它從諸如read或者pread64這樣的系統調用開始計時知道調用結束。但oprofile不會這樣,因爲I/O系統調用並不會真正消耗CPU週期,而只是等待I/O完成而已。
我們會在需要的時候使用oprofile,因爲strace對像mysqld這樣有大量線程的場景會產生一些副作用。當strace附加上去後,mysqld的運行會變得很慢,因此不適合在產品環境中使用。但在某些場景中strace還是相當有用的,Percona Toolkit中有一個叫pt-ioprofile的工具就是使用strace來生成I/O活動的剖析報告的。這個工具很有幫助,可以證明或者駁斥某些難以測量情況下的一些觀點,此時其他方法很難達到目的(MySQL的Performance Schema也可以)。

3.6 總結

本章給出了一些進本的思路和技術,有利於你成功的進行性能優化。正確的事務方式是開啓系統的全部潛力和應用本書其他章節提供的知識的關鍵。西面是一些基本的知識點:

  • 我們認爲定義性能最有效的方法是相應時間。
  • 如果無法測量就無法有效地優化,所以性能優化工作需要機遇搞質量、全方位及完整的相應時間測量。
  • 測量的最佳開始時間是應用程序我,而不是數據庫,即使問題出現在底層的數據庫,藉助良好的測試也可以很容易地發現問題。
  • 大多數系統無法完整地測量,測量有時候也會有錯誤的結果。但也可以想辦法繞過一些限制,並的得到好的結果(大梅沙要能意思到鎖使用的方法的缺陷和不確定性在哪裏)。
  • 完成的測量會產生大量需要分析的數據,所以需要用到剖析器。這是最佳的工具,可以幫助將重要的問題冒泡到前面,這樣就可以決定從哪裏開始分析比較好。
  • 剖析報告是一種彙總信息,掩蓋和丟棄了太多細節。而且它不會告訴你缺少了什麼,所以完- 全依賴剖析報告也是不明智的。
  • 有兩種消耗時間的操作:工作或等待。大多數剖析器只能測量因爲工作而消耗的時間,所以等待分析有時候是很有用的補充,尤其是當CPU利用率很低但工作卻一直無法完成的時候。
    優化和提升是兩回事。當繼續提升的成本高於收益的時候,應當停止優化。
  • 注意你的直覺,但應該只根據直覺來指導解決問題的思路,而不是用於確定系統的問題。決策應當儘量基於數據而不是感覺。
    總體來說,解決性能問題的方法,首先是要澄清問題,然後選擇合適的技術來解答這些問題。如果想嘗試提示服務器的總體性能,那麼一個比較好的起點就是將所有查詢記錄到日誌中,然後利用pt-query-digest工具生成系統基本的剖析報告。如果是要追查某些性能低下的查詢,記錄和剖析的方法也會有幫助。可以把經理放在尋找那些消耗時間最多的、導致了糟糕的用戶體驗的,或者那些高度變化的,亦或有奇怪響應時間直方圖的查詢。當找到了這些“壞“查詢時,要鑽取pt-query-digest報告中包含該查詢的詳細信息,或者使用SHOW PROFILE及其他諸如EXPLAIN這樣的工具。
    如果找不到這些查詢性能低下的原因,那麼也可能遇到了服務器級別的性能問題。這時,可以較高精度測量繪製服務器狀態計數器的細節信息。如果通過這樣的分析重現了問題,則應該通過同樣的數據制定一個可靠的觸發條件,來蒐集跟多診斷數據。多花費一點時間來確定可靠的觸發條件,儘量避免漏檢或者誤報。如果已經可以捕獲到故障活動期間的數據,還是無法找到根本原因,則要嗎嘗試捕獲更多的數據,要嗎嘗試尋求幫助。
    我們無法完整地測量工作系統,淡說到底他們都是某種狀態機,所以只要足夠西西,邏輯清晰並且堅持下去,通常來說都能得到想要的結果。要注意的是不要把原因和結果搞混了,而且在確認問題之前不要隨便針對系統做變動。
    理論上純粹的自頂向下的方法分析和詳盡的測量只是理想的情況,而我們常常需要處理的是真實系統。真實系統是複雜且無法充分測量的,所以我們只能根據情況盡力而爲。使用諸如pt-query-digest和MySQL企業監控器的查詢分析器這樣的工具並不完美,通常都不會給出問題根源的直接證據。但真正掌握了以後,已經足以完成大部分優化診斷的工作了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章