站長資訊網
最全最豐富的資訊網站

實踐(2)–MySQL性能優化

實踐(2)--MySQL性能優化

相關學習推薦:mysql教程

前言

  • MySQL索引底層數據結構與算法
  • MySQL性能優化原理-前篇
  • 實踐(1)–MySQL性能優化

上一篇 《實踐(1)–MySQL性能優化》我們講了數據庫表設計的一些原則,Explain工具的介紹、SQL語句優化索引的最佳實踐,本篇繼續來聊聊 MySQL 如何選擇合適的索引。

MySQL Trace 工具

MySQL 最終是否選擇走索引或者一張表涉及多個索引,最終是如何選擇索引,可以使用 trace 工具來一查究竟,開啟 trace工具會影響 MySQL 性能,所以只能臨時分析 SQL 使用,用完之后立即關閉。

案例分析

講 trace 工具之前我們先來看一個案例:

# 示例表CREATE TABLE`employees`(`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',`age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',`position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間', PRIMARY KEY (`id`), KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE  )ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';  INSERT INTO employees(name,age,position,hire_time)VALUES('ZhangSan',23,'Manager',NOW());INSERT INTO employees(name,age,position,hire_time)VALUES('HanMeimei', 23,'dev',NOW());INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());復制代碼

MySQL 如何選擇合適的索引

EXPLAIN select * from employees where name > 'a';復制代碼
實踐(2)--MySQL性能優化

如果用name索引需要遍歷name字段聯合索引樹,然后還需要根據遍歷出來的主鍵值去主鍵索引樹里再去查出最終數據,成本比全表掃描還高,可以用覆蓋索引優化,這樣只需要遍歷name字段的聯合索引樹就能拿到所有結果,如下:

EXPLAIN select name,age,position from employees where name > 'a' ;復制代碼
實踐(2)--MySQL性能優化
EXPLAIN select * from employees where name > 'zzz' ;復制代碼

對于上面這兩種 name>'a'name>'zzz' 的執行結果,mysql最終是否選擇走索引或者一張表涉及多個索引,mysql最終如何選擇索引,我們可以用trace工具來一查究竟,開啟trace工具會影響mysql性能,所以只能臨時分析sql使用,用完之后立即關閉。

trace工具用法

開啟/關閉Trace

#開啟traceset session optimizer_trace="enabled=on",end_markers_in_json=on; #關閉traceset session optimizer_trace="enabled=off";復制代碼

案例1

執行這兩句sql

select * from employees where name >'a' order by position;sELECT * FROM information_schema.OPTIMIZER_TRACE;  復制代碼

提出來trace值,詳見注釋

{  "steps": [     {      "join_preparation": { --第一階段:SQL準備階段        "select#": 1,        "steps": [           {            "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position`"           }         ] /* steps */       } /* join_preparation */     },     {      "join_optimization": { --第二階段:SQL優化階段        "select#": 1,        "steps": [           {            "condition_processing": { --條件處理              "condition": "WHERE",              "original_condition": "(`employees`.`name` > 'a')",              "steps": [                 {                  "transformation": "equality_propagation",                  "resulting_condition": "(`employees`.`name` > 'a')"                 },                 {                  "transformation": "constant_propagation",                  "resulting_condition": "(`employees`.`name` > 'a')"                 },                 {                  "transformation": "trivial_condition_removal",                  "resulting_condition": "(`employees`.`name` > 'a')"                 }               ] /* steps */             } /* condition_processing */           },           {            "substitute_generated_columns": {             } /* substitute_generated_columns */           },           {            "table_dependencies": [ --表依賴詳情               {                "table": "`employees`",                "row_may_be_null": false,                "map_bit": 0,                "depends_on_map_bits": [                 ] /* depends_on_map_bits */               }             ] /* table_dependencies */           },           {            "ref_optimizer_key_uses": [             ] /* ref_optimizer_key_uses */           },           {            "rows_estimation": [ --預估表的訪問成本               {                "table": "`employees`",                "range_analysis": {                  "table_scan": { --全表掃描                    "rows": 3,  --掃描行數                    "cost": 3.7 --查詢成本                   } /* table_scan */,                  "potential_range_indexes": [ --查詢可能使用的索引                     {                      "index": "PRIMARY",   --主鍵索引                      "usable": false,                      "cause": "not_applicable"                     },                     {                      "index": "idx_name_age_position", --輔助索引                      "usable": true,                      "key_parts": [                        "name",                        "age",                        "position",                        "id"                       ] /* key_parts */                     },                     {                      "index": "idx_age",                      "usable": false,                      "cause": "not_applicable"                     }                   ] /* potential_range_indexes */,                  "setup_range_conditions": [                   ] /* setup_range_conditions */,                  "group_index_range": {                    "chosen": false,                    "cause": "not_group_by_or_distinct"                   } /* group_index_range */,                  "analyzing_range_alternatives": { --分析各個索引使用成本                    "range_scan_alternatives": [                       {                        "index": "idx_name_age_position",                        "ranges": [                          "a < name"    --索引使用范圍                         ] /* ranges */,                        "index_pes_for_eq_ranges": true,                        "rowid_ordered": false, --使用該索引獲取的記錄是否按照主鍵排序                        "using_mrr": false,                        "index_only": false,    --是否使用覆蓋索引                        "rows": 3,  --索引掃描行數                        "cost": 4.61, --索引使用成本                        "chosen": false, --是否選擇該索引                        "cause": "cost"                       }                     ] /* range_scan_alternatives */,                    "analyzing_roworder_intersect": {                      "usable": false,                      "cause": "too_few_roworder_scans"                     } /* analyzing_roworder_intersect */                   } /* analyzing_range_alternatives */                 } /* range_analysis */               }             ] /* rows_estimation */           },           {            "considered_execution_plans": [               {                "plan_prefix": [                 ] /* plan_prefix */,                "table": "`employees`",                "best_access_path": { --最優訪問路徑                  "considered_access_paths": [ --最終選擇的訪問路徑                     {                      "rows_to_scan": 3,                      "access_type": "scan",    --訪問類型:為sacn,全表掃描                      "resulting_rows": 3,                      "cost": 1.6,                      "chosen": true,   --確定選擇                      "use_tmp_table": true                     }                   ] /* considered_access_paths */                 } /* best_access_path */,                "condition_filtering_pct": 100,                "rows_for_plan": 3,                "cost_for_plan": 1.6,                "sort_cost": 3,                "new_cost_for_plan": 4.6,                "chosen": true               }             ] /* considered_execution_plans */           },           {            "attaching_conditions_to_tables": {              "original_condition": "(`employees`.`name` > 'a')",              "attached_conditions_computation": [               ] /* attached_conditions_computation */,              "attached_conditions_summary": [                 {                  "table": "`employees`",                  "attached": "(`employees`.`name` > 'a')"                 }               ] /* attached_conditions_summary */             } /* attaching_conditions_to_tables */           },           {            "clause_processing": {              "clause": "ORDER BY",              "original_clause": "`employees`.`position`",              "items": [                 {                  "item": "`employees`.`position`"                 }               ] /* items */,              "resulting_clause_is_simple": true,              "resulting_clause": "`employees`.`position`"             } /* clause_processing */           },           {            "reconsidering_access_paths_for_index_ordering": {              "clause": "ORDER BY",              "index_order_summary": {                "table": "`employees`",                "index_provides_order": false,                "order_direction": "undefined",                "index": "unknown",                "plan_changed": false               } /* index_order_summary */             } /* reconsidering_access_paths_for_index_ordering */           },           {            "refine_plan": [               {                "table": "`employees`"               }             ] /* refine_plan */           }         ] /* steps */       } /* join_optimization */     },     {      "join_execution": {   --第三階段:SQL執行階段                                                            "select#": 1,        "steps": [           {            "filesort_information": [               {                "direction": "asc",                "table": "`employees`",                "field": "position"               }             ] /* filesort_information */,            "filesort_priority_queue_optimization": {              "usable": false,              "cause": "not applicable (no LIMIT)"             } /* filesort_priority_queue_optimization */,            "filesort_execution": [             ] /* filesort_execution */,            "filesort_summary": {              "rows": 3,              "examined_rows": 3,              "number_of_tmp_files": 0,              "sort_buffer_size": 200704,              "sort_mode": "<sort_key, packed_additional_fields>"             } /* filesort_summary */           }         ] /* steps */       } /* join_execution */     }   ] /* steps */ }復制代碼

結論:全表掃描的成本低于索引掃描,所以MySQL最終選擇全表掃描。

案例2

select * from employees where name > 'zzz' order by position;SELECT * FROM information_schema.OPTIMIZER_TRACE;  復制代碼

結論:查看trace字段可知索引掃描的成本低于全表掃描,所以MySQL最終選擇索引掃描。

常見SQL深入優化

Order byGroup by 優化

案例1

EXPLAIN select * from employees where name = 'ZhangSan' and position = 'dev' order by age復制代碼
實踐(2)--MySQL性能優化

分析:

利用最左前綴法則:中間字段不能斷,因此查詢用到了 name索引 ,從 key_len = 74 也能看出,age 索引列用在排序過程過程中,因為 Extra 字段里沒有 using filesort 。

案例2

EXPLAIN select * from employees where name = 'ZhangSan' order by position復制代碼
實踐(2)--MySQL性能優化

分析:

從 explain 的執行結果來看:key_len = 74,查詢使用了 name 索引,由于用了 position 進行排序,跳過了 age,出現了 Using filesort。

案例3

EXPLAIN select * from employees where name = 'ZhangSan' order by age,position復制代碼
實踐(2)--MySQL性能優化

分析:

查詢只用到索引name,age 和 position 用于排序,無Using filesort。

案例4

EXPLAIN select * from employees where name = 'ZhangSan' order by position,age復制代碼
實踐(2)--MySQL性能優化

分析:

和案例3中explain的執行結果一樣,但是出現了Using filesort ,因為索引的創建順序為 name,age,position , 但是排序的時候 age 和 position 顛倒位置了。

案例5

EXPLAIN select * from employees where name = 'ZhangSan' and age = 18 order by position,age復制代碼
實踐(2)--MySQL性能優化

分析:

與案例4對比,在Extra中并未出現** Using filesort **,因為 age 為常量,在排序中被優化,所以索引未顛倒,不會出現 Using filesort

案例6

EXPLAIN select * from employees where name = 'ZhangSan' order by age asc, position desc;復制代碼
實踐(2)--MySQL性能優化

分析:

雖然排序的字段列與索引順序一樣,且 order by 默認升序,這里 position desc 變成列降序,導致與索引的排序方式不同,從而產生 Using filesort 。MySQL8 以上版本有降序索引可以支持該種查詢方式。

案例7

EXPLAIN select * from employees where name in ('ZhangSan', 'hjh') order by age, position;復制代碼

分析:

對于排序來說,多個相等條件也是范圍查詢。

案例8

EXPLAIN select * from employees where name > 'a' order by name;復制代碼
實踐(2)--MySQL性能優化

可以用覆蓋索引優化

EXPLAIN select name,age,position from employees where name > 'a' order by name;復制代碼
實踐(2)--MySQL性能優化

優化總結

  1. MySQL支持兩種方式的排序 filesortindex。Using index 是指MySQL 掃描索引本身完成排序。index 效率高,filesort 效率低。
  2. order by 滿足兩種情況會使用 Using index.
    • order by 語句使用索引最左前例。
    • 使用 where 子句與 order by 子句條件列組合滿足索引最左前例。
  3. 盡量在索引列上完成排序,遵循索引建立索引創建的順序)時的最左前綴法則。
  4. 如果 order by 的條件不在索引列上,就會產生 Using filesort。
  5. 能用覆蓋索引盡量用覆蓋索引。
  6. group by 和 order by 很類似,其實質是先排序后分組,遵循索引創建順序的最左前綴法則。對于 group by 的優化如果不需要排序的可以加上 order by null 禁止排序。注意:where 高于 having,能寫在 where 中的限定條件就不要去 having 限定了。

Using filesort文件排序原理

filesort文件排序方式

  • 單路排序:是一次性取出滿足條件行的所有字段,然后在 sort buffer 中進行排序;用 trace 工具可以看到 sort_mode 信息里顯示 < sort_key, additional_fields > 或者 < sort_key, packed_additional_fields >。
  • 雙路排序(又叫回表排序模式):是首先根據相應的條件取出相應的排序字段可以直接定位運行數據的行ID,然后在 sort buffer 中進行排序,排序完后需要再次取回其它需要的字段;用 trace 工具可以看到 sort_mode 信息里顯示 < sort_key, rowid >

MySQL 通過比較系統變量 max_length_for_sort_data (默認1024字節) 的大小和需要查詢的字段總大小來判斷使用那種排序模式。

  • 如果max_length_for_sort_data 比查詢的字段的總長度大,那么使用單路排序模式;
  • 如果max_length_for_sort_data 比查詢字段的總長度小,那么使用雙路排序模式。

驗證各種排序方式

EXPLAIN select * from employees where name = 'ZhangSan' order by position;復制代碼
實踐(2)--MySQL性能優化

查看下這條sql對應trace結果如下(只展示排序部分):

set session optimizer_trace="enabled=on",end_markers_in_json=on; #開啟traceselect * from employees where name = 'ZhangSan' order by position;select * from information_schema.OPTIMIZER_TRACE;復制代碼
      "join_execution": { --SQL執行階段         "select#": 1,         "steps": [           {             "filesort_information": [               {                 "direction": "asc",                 "table": "`employees`",                 "field": "position"               }             ] /* filesort_information */,             "filesort_priority_queue_optimization": {               "usable": false,               "cause": "not applicable (no LIMIT)"             } /* filesort_priority_queue_optimization */,             "filesort_execution": [             ] /* filesort_execution */,             "filesort_summary": { --文件排序信息               "rows": 1, --預計掃描行數               "examined_rows": 1, --參數排序的行               "number_of_tmp_files": 0, --使用臨時文件的個數,這個只如果為0代表全部使用的sort_buffer內存排序,否則使用的磁盤文件排序               "sort_buffer_size": 200704, --排序緩存的大小               "sort_mode": "<sort_key, packed_additional_fields>" --排序方式,這里用的單路排序             } /* filesort_summary */           }         ] /* steps */       } /* join_execution */復制代碼

修改系統變量 max_length_for_sort_data (默認1024字節) ,employees 表所有字段長度總和肯定大于10字節

set max_length_for_sort_data = 10;  select * from employees where name = 'ZhangSan' order by position;select * from information_schema.OPTIMIZER_TRACE;復制代碼

trace排序部分結果:

      "join_execution": {         "select#": 1,         "steps": [           {             "filesort_information": [               {                 "direction": "asc",                 "table": "`employees`",                 "field": "position"               }             ] /* filesort_information */,             "filesort_priority_queue_optimization": {               "usable": false,               "cause": "not applicable (no LIMIT)"             } /* filesort_priority_queue_optimization */,             "filesort_execution": [             ] /* filesort_execution */,             "filesort_summary": {               "rows": 1,               "examined_rows": 1,               "number_of_tmp_files": 0,               "sort_buffer_size": 53248,               "sort_mode": "<sort_key, rowid>" --排序方式,這里用餓的雙路排序             } /* filesort_summary */           }         ] /* steps */       } /* join_execution */    復制代碼

單路排序的詳細過程:

  1. 從索引 name 找到第一個滿足 name='ZhangSan' 條件的主鍵 id;
  2. 根據主鍵id取出整行,取出所有字段的值,存入sort_buffer中;
  3. 從索引name找到下一個滿足 name='ZhangSan' 條件的主鍵 id;
  4. 重復步驟2、3直到不滿足 name='ZhangSan';
  5. 對 sort_buffer 中的數據按照字段 position 進行排序;
  6. 返回結果給客戶端

雙路排序的詳細過程:

  1. 從索引 name 找到第一個滿足 name='ZhangSan' 的主鍵id;
  2. 根據主鍵id取出整行,把排序字段 position 和 主鍵id 這兩個字段放到 sort_buffer 中;
  3. 從索引 name 取下一個滿足 name='ZhangSan' 記錄的主鍵id;
  4. 重復步驟3、4直到不滿足 name='ZhangSan';
  5. 對 sort_buffer 中的字段 position 和 主鍵id按照 position 進行排序;
  6. 遍歷排序好的 id 和 字段 position,按照 id 的值回到原表中取出所有的字段的值返回給客戶端。

對比兩個排序模式,單路排序會把所有需要查詢的字段都放到 sort_buffer 中,而雙路排序只會把主鍵和需要排序的字段放到 sort_buffer 中進行排序,然后再通過主鍵回到原表查詢需要的字段。

如果MySQL排序內存配置的比較小并且沒有條件繼續增加了,可以適當把 max_length_for_sort_data 配置小點,讓優化器選擇使用雙路排序算法,可以在 sort_buffer 中一次排序

贊(0)
分享到: 更多 (0)
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
青草青草久热精品视频在线观看| 久久久一本精品99久久精品66直播| 91精品国产高清91久久久久久| 99久久精品费精品国产一区二区| 亚洲日韩AV一区二区三区四区| 国产精品久久久久久久久电影网| 国产精品国产三级国产an| 日韩精品乱码AV一区二区| 国产最新进精品视频| 国产精品无码一区二区在线观一| 亚洲日韩av无码中文| 日韩免费高清一级毛片| 国产精品福利久久香蕉中文| 国产欧美一区二区精品仙草咪| 91精品成人福利在线播放| 久久成人国产精品免费软件| 久久精品无码专区免费青青 | 亚洲AV日韩AV天堂久久| 女人国产香蕉久久精品| 99re6在线视频精品免费下载| 亚洲精品亚洲人成在线观看下载| 亚洲欧美日韩中文字幕在线一区| 日韩专区无码人妻| 国产日韩一区二区三免费高清| 日韩一级视频在线观看播放| 国产亚洲综合精品一区二区三区| 国产精品自线在线播放| 国产精品美女久久久浪潮av| 精品久久中文字幕| 国产精品成在线观看| 国产综合精品在线| 国产精品亚洲а∨无码播放不卡| 国产精品毛片无遮挡高清| 国产精品怡红院在线观看| 国产精品久久久精品三级| 国产精品久久久久毛片真精品| 国产精品中文字幕在线| 内射中出日韩无国产剧情| 中文字幕无码日韩专区| 亚洲日韩中文字幕| 福利姬在线精品观看|