MySQL索引優化詳解

MySQL存儲引擎簡介

  1. 查看命令html

    a. 查看所使用的MySQL如今已提供什麼存儲引擎:java

    mysql> show engines;mysql

    b. 查看所使用的MySQL當前默認的存儲引擎:ios

    mysql> show variables like '%storage_engine%';程序員

  2. MyISAM和InnoDB兩種存儲引擎的區別算法

    對比項 MyISAM InnoDB
    主外鍵 不支持 支持
    事務 不支持 支持
    行表鎖 表鎖,即便操縱一條記錄也會鎖住整個表,不適合高併發的操做 行鎖,操做時只鎖某一行,不對其餘行有影響,適合高併發的操做
    緩存 只緩存索引,不緩存真實數據 不只緩存索引還要緩存真實數據,對內存要求較高,並且內存大小對性能有決定性的影響
    表空間
    關注點 性能 事務
    默認安裝 Y Y
    • 事務屬性(既然提到,就順帶複習一下)
      事務是由一組SQL語句組成的邏輯處理單元,事務具備如下4個屬性,一般簡稱爲事務的ACID屬性。
      • 原子性(Atomicity):事務是一個原子操做單元,其對數據的修改,要麼全都執行,要麼全都不執行
      • 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態,這意味着全部相關的數據規則都必須應用於事務的修改,以保持數據的完整性。事務結束時,全部的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的
      • 隔離性(Isolation):數據庫系統提供必定的隔離機制,保證事務在不受外部併發操做影響的「獨立」環境執行,這意味着事務處理過程當中的中間狀態對外部是不可見的,反之亦然
      • 持久性(Durable):事務完成以後,它對於數據的修改是永久性的,即便出現系統故障也可以保持
    • 併發事務處理帶來的問題
      • 更新丟失(Lost Update)
        當兩個或多個事務選擇同一行,而後基於選定的值更新該行時,因爲每一個事務都不知道其餘事務的存在,就會發生丟失更新的問題——最後的更新覆蓋了由其餘事務所作的更新。
        例如,兩個程序員修改同一java文件。每一個程序員獨立地更改其副本,而後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改副本的編輯人員覆蓋前一個程序員所作的更改。
        若是在一個程序員完成並提交事務以前,另外一個程序員不能訪問同一文件,則可避免此問題。
      • 髒讀(Dirty Reads)
        一個事務正在對一條記錄作修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫作「髒讀」。
        一句話:事務A讀取到了事務B已修改但還沒有提交的數據,還在這個數據基礎上作了操做,此時,若是B事務回滾,A讀取的數據無效,不符合一致性要求。
      • 不可重複讀(Non-Repeatable Reads)
        一個事務在讀取某些數據後的某個時間,再次讀取之前讀過的數據,卻發現其讀出的數據已經發生了改變,或某些記錄已經被刪除了!這種現象就叫作「不可重複讀」。
        一句話:事務A讀取到了事務B已經提交的修改數據,不符合隔離性。
      • 幻讀(Phantom Reads)
        一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲「幻讀」。
        一句話:事務A讀取到了事務B已提交的新增數據,不符合隔離性。
        多說一句:幻讀和髒讀有點相似:髒讀是事務B裏面修改了數據,幻讀是事務B裏面新增了數據。
    • 事務隔離級別
      「髒讀」、「不可重複讀」和「幻讀」,其實都是數據庫讀一致性問題,必須由數據庫提供必定的事務隔離機制來解決。sql

      讀數據一致性及容許的併發反作用 讀數據一致性 髒讀 不可重複讀 幻讀
      Read uncommitted 最低級別,只能保證不讀取物理上損壞的數據
      Read committed 語句級
      Repeatable read 事務級
      Serializable 最高級別,事務級

      數據庫的事務隔離越嚴格,併發反作用越小,但付出的代價也就越大,由於事務隔離實質上就是使事務在必定程度上,「串行化」進行,這顯然與「併發」是矛盾的。同時,不一樣的應用對讀一致性和事務隔離程度的要求也是不一樣的,好比許多應用對「不可重複讀」和「幻讀」並不敏感,可能更關心數據併發訪問的能力。
      查看MySQL的事務隔離級別:show variables like 'tx_isolation';數據庫

性能降低的緣由

包括但不限於下列狀況:緩存

  1. 查詢語句寫的爛
  2. 索引失效
  3. 關聯查詢太多join(設計缺陷或不得已的需求)
  4. 服務器調優即各個參數設置(緩衝、線程數等)不合理

索引簡介

MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構。能夠獲得索引的本質:索引是數據結構。
索引的目的在於提升查詢效率,能夠類比字典,好比:若是要查「MySQL」這個單詞,咱們確定須要定爲到m字母,而後從上往下找到y字母,再找到剩下的s、q、l。若是沒有索引,那麼你可能須要a--z,若是我想找到Java開頭的單詞呢?或者Oracle開頭的單詞呢?是否是以爲若是沒有索引,這個事情根本沒法完成?
你能夠簡單理解爲「排好序的快速查找的數據結構」。服務器

  • 詳解
    在數據以外,數據庫系統還維護着知足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就能夠在這些數據結構上實現高級查找算法。這種數據結構,就是索引。下圖就是一種可能的索引方式示例:

    爲了加快Col2的查找,能夠維護一個右邊所示的二叉查找樹,每一個節點分別包含索引鍵值和一個指向對應數據的記錄物理地址的指針,這樣就能夠運用二叉查找在必定的複雜度內獲取到相應數據,從而快速的檢索出符合條件的記錄。

  • 結論
    數據自己以外,數據庫還維護着一個知足特定查找算法的數據結構,這些數據結構以某種方式指向數據,這樣就能夠在這些數據結構的基礎上實現高級算法,這種數據結構就是索引。
    通常來講,索引自己也很大,不可能所有存儲在內存中,所以索引每每以索引文件的形式存儲在磁盤上。
    咱們日常所說的索引,若是沒有特別指明,都是指B樹(多路搜索樹,並不必定是二叉樹)結構組織的索引。其中彙集索引,次要索引,覆蓋索引,複合索引,前綴索引,惟一索引默認都是使用B+數索引,統稱索引。固然,除了B+樹這種類型的索引以外,還有哈希索引(hash index)等。

  • 優點
    相似大學圖書館創建書目索引,提升數據檢索的效率,下降數據庫的IO成本。經過索引列對數據進行排序,下降數據排序的成本,下降了CPU的消耗。

  • 劣勢
    實際上索引也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄,因此索引列也是要佔用空間的。
    雖然索引大大提升了查詢速度,同時卻會下降更新表的速度,如對錶進行INSERTUPDATEDELETE。由於更新表時,MySQL不只要保存數據,還要保存一下索引文件每次更新添加了索引列的字段。
    索引只是提升效率的一個因素,若是你的MySQL有大數據量的表,就須要花時間創建最優秀的索引,或優化查詢。

  • MySQL索引分類
    創建複合索引優於創建單值索引。通常而言,一張表最多建5個索引

  1. 單值索引:即一個索引只包含單個列,一個表能夠有多個單列索引。
  2. 惟一索引:索引列的值必須惟一,但容許有空值。
  3. 複合索引:即一個索引包含多個列。
  • 基本語法

    • 建立
      CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));
      ALTER mytable ADD [UNIQUE] INDEX [indexName] ON (columnname(length));
    • 刪除
      DROP INDEX [indexName] ON mytable;
    • 查看
      SHOW INDEX FROM table_name;
    • 使用ALTER命令
      有四種方式來添加數據表的索引:
      • ALTER TABLE tbl_name ADD PRIMARY KEY(column_list);該語句添加一個主鍵,這意味着索引值必須是惟一的,且不能爲NULL;
      • ALTER TABLE tbl_name ADD UNIQUE index_name(column_list);這條語句建立索引的值必須是惟一的(除了NULL外,NULL可能會出現屢次);
      • ALTER TABLE tbl_name ADD INDEX index_name(column_list);添加普通索引,索引值可出現屢次;
      • ALTER TABLE tbl_name ADD FULLTEXT index_name(column_list);該語句指定了索引爲FULLTEXT,用於全文索引。

MySQL索引結構

  1. Btree
    儘可能增長檢索廣度,不增長深度。檢索原理:

    初始化介紹:一棵B+樹,淺藍色的塊,咱們稱之爲一個磁盤塊,能夠看到每一個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示)。
    如磁盤塊1包含數據項17和35,包含指針P一、P二、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。
    真實的數據存在於葉子節點,即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。
    非葉子節點不存儲真實的數據,只存儲指引搜索方向的數據項,如1七、35並不真實存在於數據表中。
    查找過程:若是要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找肯定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間由於很是短(相比磁盤的IO)能夠忽略不計,經過磁盤塊1的P2指針的磁盤地址把磁盤3由磁盤加載到內存,發生第二次IO,29在26到30之間,鎖定磁盤塊3的P2指針,經過指針加載磁盤塊8到內存,發生第三次IO,同時內存中作二分查找找到29,結束查詢,總計三次IO。
    真實的狀況是,3層的B+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。
  2. Hash
  3. full-text
  4. R-Tree

哪些狀況須要建立索引

  1. 主鍵自動創建惟一索引
  2. 頻繁做爲查詢條件的字段應該建立索引
  3. 查詢中與其餘表關聯的字段,外鍵關係創建索引
  4. 頻繁更新的字段不適合建立索引:由於每次更新不只僅是更新了記錄還會更新索引
  5. where條件裏用不到的字段不建立索引
  6. 單鍵/組合索引的選擇問題,which?(在高併發下傾向建立組合索引)
  7. 查詢中排序的字段,排序字段若經過索引去訪問,將大大提升排序速度
  8. 查詢中統計或者分組字段

哪些狀況不須要建立索引

  1. 表記錄太少(建議:一百萬條之內的數據不建議創建索引,由於足夠hold得住)
  2. 常常增刪改的表
    why:雖然提升了查詢速度,同時卻會下降更新表的速度,如對錶進行INSERTUPDATEDELETE。由於更新表時,MySQL不只要保存數據,還要保存一下索引文件。
  3. 數據重複且分佈平均的表字段,所以應該只爲最常常查詢和最常常排序的數據列創建索引。
    注意:若是某個數據列包含許多重複的內容,爲它創建索引就沒有太大的實際效果。
    假如一張表有10萬行記錄,有一個字段A只有T和F兩種值,且每一個值的分佈機率大約爲50%,那麼對這種表A字段建索引通常不會提升數據庫的查詢速度。
    索引的選擇性是指索引列中不一樣值的數目與表中記錄數的比。若是一張表中有2000條記錄,表索引列有1980個不一樣的值,那麼這個索引的選擇性就是1980/2000=0.99。一個索引的選擇性越接近於1,這個索引的效率就越高。

性能分析

MySQL Query Optimizer

  • MySQL中有專門負責優化SELECT語句的優化器模塊,主要功能:經過計算分析系統中收集到的統計信息,爲客戶端請求的Query提供它認爲最優的執行計劃(它認爲最優的數據檢索方式,但不見得是DBA認爲是最優的,這部分最耗費時間)。
  • 當客戶端向MySQL請求一條Query,命令解析器模塊完成請求分類,區別出SELECT並轉發給MySQL Query Optimizer時,MySQL Query Optimizer首先會對整條Query進行優化,處理掉一些常量表達式的預算,直接換算成常量值。並對Query中的查詢條件進行簡化和轉換,如去掉一些無用或顯而易見的條件、結構調整等。而後分析Query中的Hint信息(若是有),看現實Hint信息是否能夠徹底肯定該Query的執行計劃。若是沒有Hint或Hint信息還不足以徹底肯定執行計劃,則會讀取所涉及對象的統計信息,根據Query進行寫相應的計算分析,而後再得出最後的執行計劃。

MySQL常見瓶頸

  • CPU:CPU在飽和的時候通常發生在數據裝入內存或從磁盤上讀取數據的時候
  • IO:磁盤I/O瓶頸發生在裝入數據遠大於內存容量的時候
  • 服務器硬件的性能瓶頸:top, free, iostatvmstat來查看系統的性能狀態。

explain

What's this?

使用EXPLAIN關鍵字能夠模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。分析你的查詢語句或是表結構的性能瓶頸。
官網介紹

What can it do?
  • 表的讀取順序
  • 數據讀取操做的操做類型
  • 哪些索引可使用
  • 哪些索引被實際使用
  • 表之間的引用
  • 每張表有多少行被優化器查詢
How to play?
  • explain + SQL語句
  • 包含的信息

  • 各字段解釋
    • id
      select查詢的序列號,包含一組數字,表示查詢中執行select子句或操做表的順序。包含三種狀況:
      1. id相同,執行順序由上至下

      2. id不一樣,若是是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行

      3. id相同和不一樣,同時存在

      id若是相同,能夠認爲是一組,從上往下順序執行,在全部組中,id值越大,優先級越高,越先執行。
    • select_type
      有6種類型:SIMPLEPRIMARYSUBQUERYDERIVEDUNIONUNION RESULT,主要是用於區別普通查詢、聯合查詢、子查詢等複雜查詢。
      1. SIMPLE:簡單的select查詢,查詢中不包含子查詢或者UNION
      2. PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢則被標記爲PRIMARY
      3. SUBQUERY:SELECT或者WHERE列表中包含了子查詢
      4. DERIVED:FROM列表中包含的子查詢被標記爲DERIVED(衍生),MySQL會遞歸執行這些子查詢,把結果放在臨時表裏
      5. UNION:若第二個SELECT出如今UNION以後,則被標記爲UNION,若UNION包含在FROM子句的子查詢中,外層SELECT將被標記爲:DERIVED
      6. UNION RESULT:UNION表獲取結果的SELECT
    • table
      顯示這一行的數據是關於哪張表的。
    • type
      1. 主要有以下幾種類型:ALLindexrangerefeq_refconst,systemNULL
      2. 訪問類型排列:type顯示的是訪問類型,是較爲重要的一個指標,結果值從最好到最壞依次是:
        system > const > eq_ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
      3. 顯示查詢了顯示查詢使用了何種類型,常見的以下,從最好到最差依次是:
        system > const > eq_ref > ref > range > index > ALL
        • system:表只有一行記錄(等於系統表),這時const類型的特例,平時不會出現,這個也能夠忽略不計
        • const:表示經過索引一次就找到了,const用於比較primary key或者unique索引,由於只匹配一行數據,因此很快,如將主鍵置於where列表中,MySQL就能將該查詢轉換爲一個常量
        • eq_ref:惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或惟一索引掃描

        • ref:非惟一性索引掃描,返回匹配某個單獨值的全部行。
          本質上也是上也是一種索引訪問,它返回全部匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,因此它應該屬於查找和掃描的混合體

        • range:只檢索給定範圍的行,使用一個索引來選擇行。
          key列顯示使用了哪一個索引,通常就是在你的where語句中出現了between<>in等的查詢,這種範圍掃描索引掃描比全表掃描要好,由於它只須要開始於索引的某一點,而結束語另外一點,不用掃描所有索引。以下:

        • index:Full Index ScanindexALL的區別爲index類型只遍歷索引樹,這一般比ALL塊,由於索引文件一般比數據文件小。(也就是說雖然ALLindex都是讀全表,但index是從索引中讀取的,而ALL是從硬盤中讀的)
        • ALL:Full Table Scan,將遍歷全表以找到匹配的行
    • possible_keys
      顯示可能應用在這張表中的索引,一個或多個。查詢涉及到的字段上若存在索引,則該索引將被列出,但不必定被查詢實際使用。
    • key
      實際使用的索引。若是爲NULL,則沒有使用索引。查詢中若使用了覆蓋索引,則該索引僅出如今key列表中。以下:
      先創建索引:
      create index idx_col1_col2 on t2(col1, col2);
      而後使用explain語句分析下列SQL語句:

    • key_len
      表示索引中使用的字節數,可經過該列計算查詢中使用的索引的長度。在不損失精確性的狀況下,長度越短越好,key_len顯示的值爲索引字段的最大可能長度,並不是實際使用長度,即key_len是根據表定義計算而得,不是經過表內檢索出的。
    • ref
      顯示索引的哪一列被使用了,若是可能的話,是一個常數。哪些列或常量被用於查找索引列上的值。以下:

      上圖中,由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一個常量,即‘ac’。
    • rows
      根據表統計信息及索引選用狀況,大體估算出找到所需的記錄所須要讀取的行數。以下,觀察創建索引先後rows的變化:

    • Extra
      包含不適合在其餘列中顯示但十分重要的額外信息。id、type、key、rows、Extra是較爲重要的。
      1. Using filesort:說明MySQL會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL中沒法利用索引完成的排序操做稱爲「文件排序」。

        從上面兩個例子能夠看出,後者效率更高,若是出現Using filesort,該SQL語句會執行得比較慢,須要對其進行優化。
      2. Using temporary:使用了臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表。臨時表會很損耗性能。常見於排序order by和分組查詢group by

        若是出現了Using temporary,那麼該SQL語句會更慢,需對其進行優化。
      3. Using index:表示相應的select操做中使用了覆蓋索引(Covering Index),避免訪問了表的數據行,效率不錯!若是同時出現Using where,代表索引被用來執行索引鍵值的查找;若是沒有同時出現Using where,代表索引被用來讀取數據而非執行查找動做。

        若是同時出現Using where,代表索引被用來執行索引鍵值的查找;

        若是沒有同時出現Using where,代表索引用來讀取數據而非執行查找動做。
        覆蓋索引(Covering Index),也稱爲索引覆蓋。
        理解方式一:就是select的數據列只用從索引中就可以取得,沒必要讀取數據行,MySQL能夠利用索引返回select列表中的字段,而沒必要根據索引再次讀取數據文件,換句話說查詢列要被所建的索引覆蓋。
        理解方式二:索引是高效找到行的一個方法,可是通常數據庫也能使用索引找到一個列的數據,所以它沒必要讀取整個行。畢竟索引葉子節點存儲了它們索引的數據;當能經過讀取索引就能夠獲得想要的數據,那就不須要讀取行了。一個索引包含了(或覆蓋了)知足查詢結果的數據就叫作覆蓋索引。
        注意:若是要使用覆蓋索引,必定要注意select列表中只取出須要的列,不可select *,由於若是將全部字段一塊兒作索引會致使索引文件過大,查詢性能降低。
      4. Using where:代表使用了where過濾。
      5. Using join buffer:使用了鏈接緩存。
      6. impossible where:where子句的值老是false,不能用來獲取任何元組。如:

      7. select tables optimized away:(出現頻率很小)在沒有GROUP BY子句的狀況下,基於索引優化MIN/MAX操做或者對於MyISAM存儲引擎優化COUNT(*)操做,沒必要等到執行階段再進行計算,查詢執行計劃生成的階段即完成優化。
      8. distinct:(出現頻率很小)優化distinct操做,在找到第一匹配的元組後即中止找一樣的動做。

CASE


第一行(執行順序4):id列爲1,表示是union裏的第一個selectselect_type列的primary表示該查詢爲外層查詢,table列被標記爲<derived3>,表示查詢結果裏來自一個衍生表,其中derived3中3表明該查詢衍生自第三個select查詢,即id爲3的selectselect d1.name ......
第二行(執行順序2):id爲3,是整個查詢中第三個select的一部分,因查詢包含在from中,因此爲derivedselect id, name from t1 where other_column="
第三行(執行順序3):select列表中的子查詢select_typesubquery,爲整個查詢中的第二個selectselect id from t3
第四行(執行順序1):select_typeunion,說明第四個selectunion裏的第二個select,最早執行。select name, id from t2
第五行(執行順序5):表明從union的臨時表中讀取行的階段,table列的<union1,4>表示用第一個和第四個select的結果進行。

索引優化

索引分析

  1. 單表
    • 建表SQL

    • 案例
      查詢category_id爲1且comments大於1的狀況下,views最多的article_id。
      explain select id, author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
      結論:很顯然,typeALL,即最壞的狀況,Extra裏還出現了Using filesort,也是最壞的狀況。必需要進行優化。
      開始優化:
      • 新建索引
        alter table article add index idx_article_ccv(category_id, comments, views);create index idx_article_ccv on article(category_id, comments, views);
      • 第2次EXPLAIN
        explain select id, author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
      結論:typerange,這是能夠忍受的。可是extra裏使用Using filesort還是沒法接受的。
      可是咱們已經創建了索引,爲啥沒用呢?
      這是由於按照BTree索引的工做原理,先排序category_id,若是遇到相同的category_id,則再排序comments,若是遇到相同的comments則再排序views。
      comments字段在聯合索引裏處於中間位置時,因comments > 1 條件是一個範圍值(所謂range),MySQL沒法利用索引再對後面的views部分進行檢索,即range類型查詢字段後面的索引無效。
      • 刪除第一次創建的索引
        drop index idx_article_ccv on article;
      • 第2次新建索引
        alter table article add index idx_article_cv(category_id, views);
        create index idx_article_cv on article(category_id, views);
      • 第3次explain
        explain select id, author_id from article where category_id = 1 and comments > 1 order by views desc limit 1;
      結論:能夠看到type變爲了refExtra中的Using filesort也消失了,結果很是理想。
  2. 兩表
    • 建表SQL
    insert into class(card) values(floor(1 + (rand() * 20)));
    insert into book(card) values(floor(1 + (rand() * 20)));
    添加數據,分別執行20次

    • 案例

    explain select * from class left join book on class.card = book.card;
    結論:type中有ALL。
    添加索引進行優化:alter table book add index y(card); 再次進行explain分析:

    能夠看到第二行的type變爲了refrows優化也比較明顯。 這是由左鏈接特性決定的。left join條件用於肯定如何從右表搜索行,左邊必定都有,因此右邊是咱們的關鍵點,必定須要創建索引。
    刪除舊索引,而後創建新的索引,再進行第3次explain

  3. 三表
    • 建表SQL
    insert into phone(card) values(floor(1 + (rand() * 20)));
    添加數據,執行20次
    • 案例

      創建索引,進行優化:
      alter table phone add index z(card);
      alter table book add index y(card);
      再看效果:

    後兩行的type都是ref,且總的rows優化很好,效果不錯,所以索引最好設置在須要常常查詢的字段中。

Join語句的優化

  • 儘量減小Join語句中的NestedLoop的循環總次數:「永遠用小結果集驅動大的結果集」;
  • 優先優化NestedLoop的內層循環;
  • 保證Join語句中被驅動表上Join條件字段已經被索引;
  • 當沒法保證被驅動表的Join條件字段被索引且內存資源充足的前提下,不要太吝嗇JoinBuffer的設置。

索引失效(應當避免)

  • 建表SQL

    插入數據:
    insert into staffs(name, age, pos, add_time) values('z3', 22, 'manager', now());
    insert into staffs(name, age, pos, add_time) values('july', 23, 'dev', now());
    insert into staffs(name, age, pos, add_time) values('tom', 23, 'dev', now());
  • 案例(索引失效)
    創建索引:ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name,age,pos);
    1. 全值匹配

    2. 最佳左前綴法則
      若是索引了多列,要遵照最左前綴法則。指的是查詢從索引的最左前列開始而且不跳過索引中的列。(口訣:帶頭大哥不能死,中間兄弟不能斷)
    3. 不在索引列上作任何操做(計算、函數、(自動or手動)類型轉換),會致使索引失效而轉向全表掃描

    4. 存儲引擎不能使用索引中範圍條件右邊的列

    5. 儘可能使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減小select *
    6. MySQL在使用不等於(!=或者< >)的時候沒法使用索引會致使全表掃描
    7. is nullis not null也沒法使用索引
    8. like以通配符開頭(‘%abc…’)MySQL索引失效會變成全表掃描的操做
      問題:解決like '%字符串%'時索引不被使用的方法?

      在創建索引前,執行如下語句:
      explain select id from tbl_user where name like '%aa%;
      explain select name from tbl_user where name like '%aa%;
      explain select age from tbl_user where name like '%aa%;
      explain select id, name from tbl_user where name like '%aa%;
      explain select id, name, age from tbl_user where name like '%aa%;
      explain select name, age from tbl_user where name like '%aa%;
      explain select * from tbl_user where name like '%aa%;
      explain select id, name, age, email from tbl_user where name like '%aa%;
      創建索引:create index idx_user_nameAge on tbl_user(name, age);
      在創建索引後,再執行上述explain,除了最後兩條select *即查詢列多出了索引外的email字段,其他都利用了覆蓋索引或直接使用了主鍵索引,效果都會挺好。
    9. 字符串不加單引號索引失效
    10. 少用or,用它來鏈接時會索引失效
    11. 小總結
      全值匹配我最愛,最左前綴要遵照;
      帶頭大哥不能死,中間兄弟不能斷;
      索引列上少計算,範圍以後全失效;
      Like百分寫最右,覆蓋索引不寫星;
      不等空值還有or,索引失效要少用;
      VAR引號不可丟,SQL高級也不難。

      假設index(a, b, c):下表中的各類狀況,可自行試驗。

      where語句 索引是否被使用
      where a = 3 Y,使用到a
      where a = 3 and b = 5 Y,使用到a,b
      where a = 3 and b = 5 and c = 4 Y,使用到a,b,c
      where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N
      where a = 3 或者 c = 5 使用到a,可是c不能夠,b中間斷了
      where a = 3 and b > 4 and c = 5 使用到a和b,c不能用在範圍以後,b斷了
      where a = 3 and b like 'kk%' and c = 4 Y,使用到a,b,c
      where a = 3 and b like '%kk' and c = 4 Y,只用到a
      where a = 3 and b like '%kk%' and c = 4 Y,只用到a
      where a = 3 and b like 'k%kk%' and c = 4 Y,使用到a,b,c
  • 通常性建議
    • 對於單值索引,儘可能選擇針對當前query過濾性更好的索引
    • 在選擇組合索引的時候,當前query中過濾性最好的字段在索引字段順序中,位置越靠前越好。
    • 在選擇組合索引的時候,儘可能選擇能夠可以包含當前query中的where子句中更多字段的索引
    • 儘量經過分析統計信息和調整query的寫法來達到選擇合適索引的目的。

查詢優化

小表驅動大表

小表驅動大表,即小的數據集驅動大的數據集。

select * from A where id in (select id from B)
等價於
for select id from B
for select * from A where A.id = B.id

當B表的數據集必須小於A表的數據集時,用in優於exists。

select * from A where exists (select 1 from B where B.id = A.id)
等價於
for select * from A
for select * from B where B.id = A.id

當A表的數據集系小於B表的數據集時,用exists優於in。
注意:A表與B表的id字段應創建索引。

  • exists
    select ... from table where exists (subquery);
    該語法能夠理解爲:將主查詢的數據,放到子查詢中作條件驗證,根據驗證結果(true或false)來決定主查詢的結果數據是否得意保留。
  • 提示
    • exists(subquery)只返回true或false,所以子查詢中的select * 也能夠是select 1 或其餘,官方說法是實際執行時會忽略select清單,所以沒有區別
    • exists子查詢的實際執行過程可能通過了優化而不是咱們理解上的逐條對比,若是擔心效率問題,可進行實際檢驗以肯定是否有效率問題
    • exists子查詢每每也能夠用條件表達式、其餘子查詢或者join來替代,何種最優須要具體問題具體分析

order by關鍵字優化

  • order by子句,儘可能使用index方式排序,避免使用filesort方式排序
    1. 建表SQL + 添加數據 + 創建索引

    2. case

    3. MySQL支持兩種方式的排序,filesortindexindex效率高,它指MySQL掃描索引自己完成排序,filesort方式效率較低
    4. order by知足兩種狀況,會使用index方式排序:
      • order by語句使用索引最左前列
      • 使用where子句與order by子句條件列組合知足索引最左前列
  • 儘量在索引列上完成排序操做,遵守索引鍵的最佳左前綴
  • 若是不在索引列上,filesort有兩種算法,MySQL就要啓動雙路排序和單路排序
    1. 雙路排序
      MySQL4.1以前是使用雙路排序,字面意思就是兩次掃描磁盤,最終獲得數據,讀取行指針和order by列,對他們進行排序,而後掃描已經排序好的列表,按照列表中的值從新從列表中讀取對應的數據輸出。
      從磁盤取排序字段,在buffer進行排序,再從磁盤取其餘字段。
      取一批數據,要對磁盤進行了兩次掃描,總所周知,I/O是很耗時的,因此在MySQL4.1以後,出現了第二種改進的算法,就是單路排序。
    2. 單路排序
      從磁盤讀取查詢須要的全部列,按照order by列在buffer對它們進行排序,而後掃描排序後的列表進行輸出,它的效率更快一些,避免了第二次讀取數據。而且把隨機IO變成了順序IO,可是它會使用更多的空間,由於它把每一行都保存在內存中了。
    3. 結論及引伸出的問題
      因爲單路是後來出現的,整體而言好過雙路。可是用單路有問題:
      sort_buffer中,方法B比方法A要佔用不少空間,由於方法B是把全部字段都取出,因此有可能取出的數據的總大小超出了sort_buffer的容量,致使每次只能取sort_buffer容量大小的數據,進行排序(建立tmp文件,多路合併),排完再去取sort_buffer容量大小,再排......從而屢次I/O。
      原本想省一次I/O操做,反而致使了大量的I/O操做,得不償失。
  • 優化策略
    1. 增大sort_buffer_size參數的的設置
    2. 增大max_length_for_sort_data參數的設置
why?
  1. order by的時候,select *是一個大忌,所以只查詢須要的字段,這點很是重要,在這裏影響的是:
    • 當查詢的字段大小總和小於max_length_for_sort_data並且排序字段不是text|blob類型時,會用改進後的算法--單路排序,不然用老算法--多路排序
    • 兩種算法的數據都有可能超出sort_buffer的容量,超出以後,會建立tmp文件進行合併排序,致使屢次I/O,可是用單路排序算法的風險會更大一些,因此要提升sort_buffer_size
  2. 嘗試提升sort_buffer_size
    無論用哪一種算法,提升這個參數都會提升效率,固然,要根據系統的能力去提升,由於這個參數是針對每一個進程的
  3. 嘗試提升max_length_for_sort_data
    提升這個參數,會增長用改進算法的效率,可是若是設的過高,數據總容量超出sort_buffer_size的機率就增大,明顯症狀是高的磁盤I/O活動和低的處理器使用率
  • 小總結
    key:a_b_c(a, b, c)

    order by能使用索引最左前綴
    order by a
    order by a,b
    order by a,b,c
    order by a desc,b desc,c desc #這個同時都爲降序因此也能夠

    若是where使用索引的最左前綴定義爲常量,則order by能使用索引
    where a=const order by b,c
    where a=const and b=const order by c
    where a=const order by b,c
    where a=const and b>const order by b,c

    不能使用索引進行排序
    order by a asc,b desc,c desc #排序不一致,有升序有降序
    where g=const order by b,c #丟失a索引,沒有帶頭大哥
    where a=const order by c #丟失b索引
    where a in(...) order by b,c #對於排序來講,多個相等條件也是範圍查詢

group by關鍵字優化

  • group by實質是先排序後進行分組,遵守索引建的最佳左前綴
  • 當沒法使用索引列,增大max_length_for_sort_data參數的設置加增大sort_buffer_size參數的設置
  • where高於having,能寫在where限定的條件就不要去having限定了

慢查詢日誌

What's this?

MySQL的慢查詢日誌是MySQL提供的一種日誌記錄,它用來記錄在MySQL中響應時間超過閥值的語句,具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日誌中。long_query_time的默認值爲10,意思是運行10秒以上的語句。
用它來查看哪些SQL超出了咱們的最大忍耐時間值,好比一條SQL執行超過5秒鐘,咱們就把它定爲慢SQL,但願能收集超過5秒鐘的SQL,結合以前的explain進行全面分析。

How to play?

  • 說明
    默認狀況下,MySQL數據庫沒有開啓慢查詢日誌,須要咱們手動來設置這個參數。
    固然,若是不是調優須要的話,通常不建議啓動該參數,由於開啓慢查詢日誌會或多或少帶來必定的性能影響。慢查詢日誌支持將日誌記錄寫入文件。
  • 查看是否開啓及如何開啓
    1. 默認
      show varialbes like '%slow_query_log%';
      經過執行上條語句,能夠看到,默認狀況下,slow_query_log的值爲OFF,表示慢查詢日誌是禁用的。能夠經過設置slow_query_log的值來開啓。

    2. 開啓
      set global slow_query_log=1;
      使用set global slow_query_log=1開啓了慢查詢日誌,只對當前數據庫生效,而且若是MySQL重啓後則會失效。
      若是要永久生效,就必須修改配置文件my.cnf(其它系統變量也是如此)
      修改my.cnf文件,[mysqld]下增長或修改參數slow_query_logslow_query_log_file後,而後重啓MySQL服務器。也即,將以下兩行配置進my.cnf文件
      slow_query_log=1
      slow_query_log_file=/usr/local/mysql/data/chengchangfudeMacBook-Pro-slow.log
      關於慢查詢的參數slow_query_log_file,它指定慢查詢日誌文件的存放路徑,系統默認會給一個缺省的文件 host_name_slow.log(若是沒有指定參數slow_query_log_file的話)。
  • 開啓了慢查詢日誌後,什麼樣的SQL纔會記錄到慢查詢日誌裏面
    這個是由參數long_query_time控制的,默認狀況下long_query_time的值爲10秒,命令:show variables like 'long_query_time%';

    可使用命令修改,也能夠在my.cnf參數裏面修改。
    假如在運行時間正好等於long_query_time的狀況,並不會被記錄下來。也就是說,在MySQL源碼裏是判斷大於long_query_time,而非大於等於。
  • case
    1. 查看當前多少秒算慢日誌
      show variables like 'long_query_time%';
    2. 設置慢日誌的閾值時間
      set global long_query_time=3;
      修改後,須要從新鏈接數據庫或新開一個會話才能看到修改值。
    3. 記錄慢查詢並分析

    4. 查詢當前系統中有多少條慢查詢記錄

  • 配置版
    [mysqld]下配置:

日誌分析工具mysqldumpslow

在生產環境中,若是要手工分析日誌,查找、分析SQL,顯然是個體力活,MySQL提供了日誌分析工具:mysqldumpslow

  • 查看mysqldumpslow的幫助信息
    使用mysqldumpslow --help命令進行查看

    s:是表示按照何種方式排序;
    c:訪問次數
    l:鎖定時間
    r:返回記錄
    t:查詢時間
    al:平均鎖定時間
    ar:平均返回記錄數
    at:平均查詢時間
    t:即爲返回前面多少條的數據;
    g:後邊搭配一個正則匹配模式,大小寫不敏感的;

  • 工做經常使用參考

    獲得返回記錄集最多的10個SQL
    mysqlddumpslow -s r -t 10 chengchangfudeMacBook-Pro-slow.log
    獲得訪問次數最多的10個SQL
    mysqlddumpslow -s c -t 10 chengchangfudeMacBook-Pro-slow.log
    獲得按照時間排序的前10條裏面含有左鏈接的查詢語句
    mysqlddumpslow -s t -t 10 -g "left join" chengchangfudeMacBook-Pro-slow.log
    另外建議在使用這些命令時結合|more使用,不然有可能出現爆屏狀況
    mysqlddumpslow -s r -t 10 chengchangfudeMacBook-Pro-slow.log | more

Show Profile

Show Profile是MySQL提供能夠用來分析當前會話中語句執行的資源消耗狀況。能夠用於SQL調優的測量。
官網介紹
默認狀況下,參數處於關閉狀態,並保存最近15次的運行結果,使用set profiling=on;進行開啓

  • 運行SQL
    select * from emp group by id%10 limit 150000;
    select * from emp group by id%20 order by 5;
  • 查看結果
  • 診斷SQL
    show profile cpu, block io for query 上一步前面的問題SQL數字號碼;
    如:show profile cpu, block io for query 3;

    上面詳細列出了一條查詢語句通過的全部的過程。
    參數備註:

    ALL 顯示全部的開銷信息
    BLOCK IO 顯示塊IO相關開銷
    CONTEXT SWITCHES 上下文切換相關開銷
    CPU 顯示CPU相關開銷信息
    IPC 顯示發送和接收相關開銷信息
    MEMORY 顯示內存相關開銷信息
    PAGE FAULTS 顯示頁面錯誤相關開銷信息
    SOURCE 顯示和Source_function,Source_file,Source_line相關的開銷信息
    SWAPS 顯示交換次數相關開銷信息

  • 平常開發須要注意的結論
    若是出現下列狀況,就糟糕了,就必須得對該條SQL語句進行優化。
    1. converting HEAP to MyISAM:查詢結果太大,內存都不夠用了往磁盤上搬了
    2. create tmp table:建立臨時表,拷貝數據到臨時表,用完再刪除
    3. copying to tmp table on disk:把內存中臨時表複製到磁盤,危險!!!
    4. locked
相關文章
相關標籤/搜索