1. 程式人生 > >智慧運維:美團SQL Advisor的自動化SQL優化實現

智慧運維:美團SQL Advisor的自動化SQL優化實現

2017年10月19日,網際網路+生活服務平臺美團點評宣佈完成新一輪40億美元融資,投後估值300億美元。

在技術上,美團點評技術團隊以及成為業界一流的研發組織,形成了比較完整的技術體系。在2017第七屆資料技術嘉年華大會上,我們有幸邀請了美團點評的技術專家龍雪剛進行主題分享,將全面介紹和解讀他們獨立研發的SQL優化利器之SQLAdvisor的技術細節和底層架構。

為了讓大家更好地瞭解該產品,今天揀選 此文分享。更多的技術細則,歡迎來資料技術嘉年華,目前限時免費報名中

介紹

在資料庫運維過程中,優化 SQL 是 DBA 團隊的日常任務。例行 SQL 優化,不僅可以提升程式效能,還能夠降低線上故障的概率。

目前常用的 SQL 優化方式包括但不限於:業務層優化、SQL邏輯優化、索引優化等。其中索引優化通常通過調整索引或新增索引從而達到 SQL 優化的目的。索引優化往往可以在短時間內產生非常巨大的效果。如果能夠將索引優化轉化成工具化、標準化的流程,減少人工介入的工作量,無疑會大大提高DBA的工作效率。

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

現在,我們非常高興地將 SQLAdvisor 開源,專案 GitHub 地址:https://github.com/Meituan-Dianping/SQLAdvisor 。我們已經把相關開發工作全面轉到 GitHub 上,開源版本和內部使用版本保持完全一致。希望與業內有類似需求的團隊,一起打造一款優秀的 SQL 優化產品。

SQLAdvisor架構流程圖:

SQLAdvisor架構

SQLAdvisor使用舉例

sql: SELECT id FROM crm_loan WHERE id_card = ‘1234567’

cmd: ./sqladvisor -h xx -P xx -u xx -pxx -d xx -q “SELECT id FROM              crm_loan WHERE id_card = ‘1234567’”

SQLAdvisor輸出: alter table crm_loan add index idx_id_card(id_card)

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 處理

  • 主要是提取 SQL 語句的 where 條件。where 條件中一般由 AND 和 OR 連線符進行連線,因為 OR 比較難以處理,所以忽略,只處理 AND 連線符。
  • 由於 where 條件中可以存在 Join 條件,因此需要進行區分。
  • 依次獲取 where 條件,當條件中的操作符是 like,如果不是字首匹配則丟棄這個條件。
  • 根據條件計算欄位的區分度按照高低進行倒序排,如果小於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() 計算選擇度。

函式

新增備選索引

  1. mysql_sql_parse_index()將條件按照選擇度新增到備選索引連結串列中。
  2. 上述兩函式的流程圖如下所示:
函式

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() 將欄位依次按照規則新增到備選索引連結串列中。

索引

驅動表選擇

  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(),流程如下:

輸出建議

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

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,導致指標失效問題。