美團SQL優化工具SQLAdvisor

介紹

在數據庫運維過程當中,優化 SQL 是 DBA 團隊的平常任務。例行 SQL 優化,不只能夠提高程序性能,還可以下降線上故障的機率。mysql

目前經常使用的 SQL 優化方式包括但不限於:業務層優化、SQL邏輯優化、索引優化等。其中索引優化一般經過調整索引或新增索引從而達到 SQL 優化的目的。索引優化每每能夠在短期內產生很是巨大的效果。若是可以將索引優化轉化成工具化、標準化的流程,減小人工介入的工做量,無疑會大大提升DBA的工做效率。git

SQLAdvisor 是由美團點評公司北京DBA團隊開發維護的 SQL 優化工具:輸入SQL,輸出索引優化建議。 它基於 MySQL 原生詞法解析,再結合 SQL 中的 where 條件以及字段選擇度、聚合條件、多表 Join 關係等最終輸出最優的索引優化建議。目前 SQLAdvisor 在公司內部大量使用,較爲成熟、穩定。github

如今,咱們很是高興地將 SQLAdvisor 開源,項目 GitHub 地址:https://github.com/Meituan-Dianping/SQLAdvisor 。咱們已經把相關開發工做全面轉到 GitHub 上,開源版本和內部使用版本保持徹底一致。但願與業內有相似需求的團隊,一塊兒打造一款優秀的 SQL 優化產品。sql

SQLAdvisor架構流程圖
mysql數據庫

SQLAdvisor使用舉例

 

SQLAdvisor快速入門教程架構

SQLAdvisor的優勢

  • 基於 MySQL 原生詞法解析,充分保證詞法解析的性能、準肯定以及穩定性;
  • 支持常見的 SQL(Insert/Delete/Update/Select);
  • 支持多表 Join 並自動邏輯選定驅動表;
  • 支持聚合條件 Order by 和 Group by;
  • 過濾表中已存在的索引。

SQLAdvisor原理介紹

Join 處理

  1. Join語法分爲兩種:Join on 和 Join using,而且 Join on 有時會存在 where 條件中。
  2. 分析 Join 條件首先會獲得一個 nested_join 的 table list,經過判斷它的 join_using_fields 字段是否爲空來區分 Join on 與 Join using。
  3. 生成的 table list 以二叉樹的形式進行存儲,之後序遍歷的方式對二叉樹進行遍歷。
  4. 生成內部解析樹時,right Join 會轉換成 left Join。
  5. Join 條件會存在當層的葉子節點上,若是左右節點都是葉子節點,會存在右葉子節點。
  6. 每個非葉子節點表明一次 Join 的結果。

上述實現時,涉及的函數爲:mysql_sql_parse_join(TABLE_LIST join_table) mysql_sql_parse_join(Item join_condition) ,主要流程圖以下:
mysql運維

where 處理

  1. 主要是提取 SQL 語句的 where 條件。where 條件中通常由 AND 和 OR 鏈接符進行鏈接,由於 OR 比較難以處理,因此忽略,只處理 AND 鏈接符。
  2. 因爲 where 條件中能夠存在 Join 條件,所以須要進行區分。
  3. 依次獲取 where 條件,當條件中的操做符是 like,若是不是前綴匹配則丟棄這個條件。
  4. 根據條件計算字段的區分度按照高低進行倒序排,若是小於30則丟棄。同時使用最左原則將 where 條件進行有序排列。

計算區分度

  1. 經過 「show table status like」 得到表的總行數 table_count。
  2. 經過計算選擇表中已存在的區分度最高的索引 best_index,同時Primary key > Unique key > 通常索引。
  3. 經過計算獲取數據採樣的起始值offset與採樣範圍rand_rows:
    • offset = (table_count / 2) > 10W ? 10W : (table_count / 2)
    • rand_rows =(table_count / 2) > 1W ? 1W : (table_count / 2)
    • 使用select count(1) from (select field from table force index(best_index) order by cl.. desc limit rand_rows) where field_print 獲得知足條件的rows。
    • cardinality = rows == 0 ? rand_rows : rand_rows / rows;
    • 計算完成選擇度後,會根據選擇度大小,將該條件添加到該表中的備選索引中。

主要涉及的函數爲:mysql_sql_parse_field_cardinality_new() 計算選擇度。
mysqlide

添加備選索引

  1. mysql_sql_parse_index()將條件按照選擇度添加到備選索引鏈表中。
  2. 上述兩函數的流程圖以下所示:
    mysql

Group 與 Order 處理

  1. Group 字段與 Order 字段可否用上索引,須要知足以下條件:
    • 涉及到的字段必須來自於同一張表,而且這張表必須是肯定下來的驅動表。
    • Group by 優於 Order by, 二者只能同時存在一個。
    • Order by 字段的排序方向必須徹底一致,不然丟棄整個 Order by 字段列。
    • 當 Order by 條件中包含主鍵時,若是主鍵字段爲 Order by。 字段列末尾,忽略該主鍵,不然丟棄整個 Order by 字段列。
  2. 整個索引列排序優先級:等值>(group by | order by )> 非等值。
  3. 該過程當中設計的函數主要有:
    • mysql_sql_parse_group() 判斷 Group 後的字段是否均來自於同一張表。
    • mysql_sql_parse_order() 判斷 Order 後的條件是否可使用。
    • mysql_sql_parse_group_order_add() 將字段依次按照規則添加到備選索引鏈表中。
      mysqlmysql

驅動表選擇

  1. 通過前期的 where 解析、Join 解析,已經將 SQL 中表關聯關係存儲起來,而且按照必定邏輯將候選驅動表肯定下來。
  2. 在侯選驅動表中,按照每一張表的侯選索引字段中第一個字段進行計算表中結果集大小。
  3. 使用 explain select * from table where field 來計算表中結果集。
  4. 結果集小最小的被確爲驅動表。
  5. 步驟中涉及的函數爲:final_table_drived(),在該函數中,調用了函數 get_join_table_result_set() 來獲取每張驅動候選表的行數。

添加被驅動表備選索引

  1. 經過上述過程,已經選擇了驅動表,也經過解析保存了語句中的條件。
  2. 因爲選定了驅動表,所以須要對被驅動表的索引,根據 Join 條件進行添加。
  3. 該過程涉及的函數主要是:mysql_index_add_condition_field(),流程以下:
    mysql

輸出建議

  1. 經過上述步驟,已經將每張表的備選索引鍵所有保存。此時,只要判斷每張表中的候選索引鍵是否在實際表中已存在。沒有索引,則給出建議增長對應的索引。
  2. 該步驟涉及的函數是:print_index() ,主要的流程圖爲:
    mysql

SQLAdvisor版本更新

  • Functionality Added or Changed
    • 調整架構將 SQLParser 與 SQLAdvisor 模塊隔離,方便調試。
    • 從新架構多表 Join 關係的 find_join_elements() 函數,思路更加清晰。
    • 修改選定驅動表的策略,確保驅動表爲小結果集。
    • 添加 where 條件中的 like 處理。
    • 優化 Order by 邏輯,忽略 Order by primary key 場景。
    • 輸出索引建議前,增長判斷索引是否已存在。
  • Bugs Fixed
    • 修復 SQL 沒法處理中文問題。
    • 修復字段屢次出如今 where 條件中從而致使屢次出如今索引列中問題。
    • 修復在 find_best_index() 函數中,對 MySQL API 中的 result 對象提早 free,致使指針失效問題。

 

SQLAdvisor手冊

    1. SQLAdvisor快速入門教程.
    2. SQLAdvisor原理和架構.
    3. SQLAdvisor release notes.
    4. SQLAdvisor開發規範.
    5. FAQ.
相關文章
相關標籤/搜索