Java編程——MySQL 簡介及索引性能分析概要

Java編程——MySQL 簡介及索引性能分析概要

MySQL簡介

MySQL在過去由於性能高、成本低、可靠性好,已經成為最流行的開源數據庫,因此被廣泛地應用在Internet上的中小型網站中。隨著MySQL的不斷成熟,它也逐漸用於更多大規模網站和應用,比如維基百科、Google和Facebook等網站。

但被甲骨文公司收購後,Oracle大幅調漲MySQL商業版的售價,且甲骨文公司不再支持另一個自由軟件項目OpenSolaris的發展,因此導致自由軟件社區們對於Oracle是否還會持續支持MySQL社區版(MySQL之中唯一的免費版本)有所隱憂,因此原先一些使用MySQL的開源軟件逐漸轉向其它的數據庫。例如維基百科已於2013年正式宣佈將從MySQL遷移到MariaDB數據庫。MySQL的創始人麥克爾·維德紐斯以MySQL為基礎,成立分支計劃MariaDB。


分析索引性能的方法:基本問題法(BQ)和快速估算上限法(QUBE),這兩種方法能夠幫助我們快速分析、估算索引的性能,及時發現問題。

基本問題法

當我們需要考慮對現有的 SELECT 查詢進行分析時,哪怕沒有足夠的時間,也應該使用基本問題法對查詢進行評估,評估的內容非常簡單:現有的索引或者即將添加的索引是否包含了 WHERE 中使用的全部列,也就是對於當前查詢來說,是否有一個索引是半寬索引。

Java編程——MySQL 簡介及索引性能分析概要

在上一篇文章中,我們介紹過寬索引和窄索引,窄索引 (username) 其實就叫做半寬索引,其中包含了 WHERE 中的全部的列 username,當前索引的對於該查詢只有一顆星,它雖然避免了無效的回表查詢造成的隨機 IO,但是如果當前的索引的性能仍然無法滿足需要,就可以添加 age 將該索引變成寬索引 (username, age) 以此來避免回表訪問造成的性能影響;對於上圖中的簡單查詢,索引 (username, age) 其實已經是一個三星索引了,但是對於包含 ORDER BY 或者更加複雜的查詢,(username, age) 可能就只是二星索引:

Java編程——MySQL 簡介及索引性能分析概要

在這時如果該索引仍然不能滿足性能的需要,就可以考慮按照上一篇文章 MySQL 索引設計概要 中提供的索引設計方法重新設計了。

雖然基本問題法能夠快速解決一些由於索引造成的問題,但是它並不能保證足夠的性能,當表中有 (city, username, age) 索引,謂詞為 WHERE username="draveness" AND age="21"時,使用基本問題法並不能得出正確的結果。

快速估算上限法

基本問題法非常簡單,它能夠最短的時間內幫助我們評估一個查詢的性能,但是它並不能準確地反映一個索引相關的性能問題,而快速估算上限法就是一種更加準確、複雜的方法了;其目的在於在程序開發期間就能將訪問路徑緩慢的問題暴露出來,這個估算方法的輸出就是本地響應時間(Local Response Time):

Java編程——MySQL 簡介及索引性能分析概要

本地響應時間就是查詢在數據庫服務器中的耗時,不包括任何的網絡延遲和多層環境的通信時間,僅包括執行查詢任務的耗時。

響應時間

本地響應時間等於服務時間和排隊時間的總和,一次查詢請求需要在數據庫中等待 CPU 以及磁盤的響應,也可能會因為其他事務正在對同樣的數據進行讀寫,導致當前查詢需要等待鎖的獲取,不過組成響應時間中的主要部分還是磁盤的服務時間:

Java編程——MySQL 簡介及索引性能分析概要

QUBE 在計算的過程中會忽略除了磁盤排隊時間的其他排隊時間,這樣能夠簡化整個評估流程,而磁盤的服務時間主要還是包括同步讀寫以及異步讀幾個部分:

Java編程——MySQL 簡介及索引性能分析概要

在排除了上述多個部分的內容,我們得到了一個非常簡單的估算過程,整個估算時間的輸入僅為隨機讀和順序讀以及數據獲取的三個輸入,而它們也是影響查詢的主要因素:

Java編程——MySQL 簡介及索引性能分析概要

其中數據獲取的過程在比較不同的索引對同一查詢的影響是不需要考慮的,因為同一查詢使用不同的索引也會得到相同的結果集,獲取的數據也是完全相同的。

訪問

當 MySQL 讀取一個索引行或者一個錶行時,就會發生一次訪問,當使用全表掃描或者掃描索引片時,讀取的第一個行就是隨機訪問,隨機訪問需要磁盤進行尋道和旋轉,所以其代價巨大,而接下來順序讀取的所有行都是通過順序訪問讀取的,代價只有隨機訪問的千分之一。

如果大量的順序讀取索引行和錶行,在原理上可能會造成一些額外的零星的隨機訪問,不過這對於整個查詢的估算來說其實並不重要;在計算本地響應時間時,仍然會把它們當做順序訪問進行估算。

示例

在這裡,我們簡單地舉一個例子來展示如何計算查詢在使用某個索引時所需要的本地響應時間,假設我們有一張 users 表,其中有一千萬條數據:

Java編程——MySQL 簡介及索引性能分析概要

在該 users 表中除了主鍵索引之外,還具有以下 (username, city)、(username, age) 和 (username) 幾個輔助索引,當我們使用如下所示的查詢時:

Java編程——MySQL 簡介及索引性能分析概要

兩個查詢條件分別有著 0.05% 和 12% 的過濾因子,該查詢可以直接使用已有的輔助索引 (username, city),接下來我們根據表中的總行數和過濾因子開始估算這一步驟 SQL 的執行時間:

Java編程——MySQL 簡介及索引性能分析概要

該查詢在開始時會命中 (username, city) 索引,掃描符合條件的索引片,該索引總共會訪問 10,000,000 * 0.05% * 12% = 600 條數據,其中包括 1 次的隨機訪問和 599 次的順序訪問,因為該索引中的列並不能滿足查詢的需要,所以對於每一個索引行都會產生一次表的隨機訪問,以獲取剩餘列 age 的信息:

Java編程——MySQL 簡介及索引性能分析概要

在這個過程中總共產生了 600 次隨機訪問,最後取回結果集的過程中也會有 600 次 FETCH 操作,從總體上來看這一次 SQL 查詢共進行了 601 次隨機訪問、599 次順序訪問和 600 次 FETCH,根據上一節中的公式我們可以得到這個查詢的用時約為 6075.99ms 也就是 6s 左右,這個時間對於絕大多數應用都是無法接受的。

Java編程——MySQL 簡介及索引性能分析概要

在整個查詢的過程中,回表查詢的 600 次隨機訪問成為了這個超級慢的查詢的主要貢獻,為了解決這個問題,我們只需要添加一個 (username, city, age) 索引或者在已有的 (username, city) 後添加新的 age 列就可以避免 600 次的隨機訪問:

Java編程——MySQL 簡介及索引性能分析概要

(username, city, age) 索引對於該查詢其實就是一個三星索引了

總結

MySQL和存儲引擎訪問數據的方式,加上索引的特性,使得索引成為一個影響數據訪問的有利而靈活的工作(無論數據實在磁盤還是在內存中)

大多數情況下都會使用B-Tree索引,其他類型的索引大多隻適用於特殊目的。

選擇索引和編寫利用這些索引的查詢時,有如下三個原則始終需要記住:

  • 單行訪問是很慢的。如果服務器從存儲中讀取一個數據塊只是為了獲取其中的一行,那麼就浪費了很多工作。最好的讀取的塊中能包含儘可能多需要的行。使用索引可以創建位置引用提高效率。
  • 按順序訪問範圍數據是很快的。有兩個原因:順序IO不需要多次磁盤尋道而比隨機IO快很多;如果服務器能夠按需要的順序讀取數據,就不再需要額外的排序操作,並且GROUP BY查詢也無須再做排序和將行按組進行聚合計算。
  • 索引覆蓋查詢是很快的。如果一個索引包含了查詢需要的所有列,那麼存儲引擎就不需要再回表查找行,避免了大量的單行訪問。
Java編程——MySQL 簡介及索引性能分析概要


分享到:


相關文章: