mysql 索引優化

我的mysql server 版本爲5.7 commutity 版本,低於該版本的trace 參數可能不存在(5.6.x 之後)!

mysql> EXPLAIN select * from employees where name > 'a';
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | idx_name_age_position | NULL | NULL    | NULL |    3 |      100 | Using where |
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
1 row in set

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

mysql> EXPLAIN select * from employees where name > 'zzz' ;
+----+-------------+-----------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table     | partitions | type  | possible_keys         | key                   | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-----------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | employees | NULL       | range | idx_name_age_position | idx_name_age_position | 74      | NULL |    1 |      100 | Using index condition |
+----+-------------+-----------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
1 row in set
對於上面這兩種 name>'a' 和 name>'zzz' 的執行結果,mysql最終是否選擇走索引或者一張表涉及多個索引,mysql最
終如何選擇索引,我們可以用trace工具來一查究竟,開啓trace工具會影響mysql性能,所以只能臨時分析sql使用,用
完之後立即關閉:

mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; 
Query OK, 0 rows affected

mysql>  select * from employees where name > 'a' order by position;
+----+-----------+-----+----------+---------------------+
| id | name      | age | position | hire_time           |
+----+-----------+-----+----------+---------------------+
|  2 | HanMeimei |  23 | dev      | 2020-03-14 16:45:53 |
|  3 | Lucy      |  23 | dev      | 2020-03-14 16:45:53 |
|  1 | LiLei     |  22 | manager  | 2020-03-14 16:45:53 |
+----+-----------+-----+----------+---------------------+
3 rows in set

mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;

step1:SQL準備階段

 

step2:SQL 優化階段

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 */

3.表依賴階段:

"table_dependencies": [
              {
                "table": "`employees`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },

 

4.預估表的訪問成本,

rows_estimation": [
              {
                "table": "`employees`",
                "range_analysis": {
                  "table_scan": {--全表掃描
                    "rows": 3,--掃描行
                    "cost": 3.7--查詢成本
                  } /* table_scan */,

5.可能使用到的索引

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 */

analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "idx_name_age_position",
                        "ranges": [
                          "a < name"
                        ] /* ranges */, --索引使用範圍
                        "index_dives_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 */,

7.

table": "`employees`",
                "best_access_path": {‐‐最優訪問路徑
                  "considered_access_paths": [‐‐最終選擇的訪問路徑

                    {
                      "rows_to_scan": 3,
                      "access_type": "scan", ‐‐訪問類型:爲scan,全表掃描

                      "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

 

8.SQL執行階段

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": 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 */

 

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

 

同理, mysql> select * from employees where name > 'zzz' order by position;

查看trace字段可知索引掃描的成本低於全表掃描,所以mysql最終選擇索引掃描
 
 
 
常見sql深入優化
 
Order by與Group by優化
mysql> explain select * from employees where name='LiLei' and position='dev' order by age;
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table     | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_name_age_position | idx_name_age_position | 74      | const |    1 |    33.33 | Using index condition |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
1 row in set
 
利用最左前綴法則:中間字段不能斷,因此查詢用到了name索引,從key_len=74也能看出,age索引列用
排序過程中,因爲Extra字段裏沒有using filesort
 
mysql> explain select * from employees where name='LiLei'  order by age,position;
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table     | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_name_age_position | idx_name_age_position | 74      | const |    1 |      100 | Using index condition |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
1 row in set
查找只用到索引name,age和position用於排序,無Using filesort
 
 

mysql> explain select * from employees where name='LiLei'  
  order by position,age;
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+---------------------------------------+
| id | select_type | table     | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                                 |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+---------------------------------------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_name_age_position | idx_name_age_position | 74      | const |    1 |      100 | Using index condition; Using filesort |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+---------------------------------------+
1 row in set

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

mysql> explain select * from employees where name='LiLei'  and age=18  order by position,age;
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-----------------------+
| id | select_type | table     | partitions | type | possible_keys         | key                   | key_len | ref         | rows | filtered | Extra                 |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-----------------------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_name_age_position | idx_name_age_position | 78      | const,const |    1 |      100 | Using index condition |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-----------------------+
1 row in set

在Extra中並未出現Using filesort,因爲age爲常量,在排序中被優化,所以索引未顛倒,
不會出現Using filesort。

 

mysql> explain select * from employees where name='xxx'  order by age asc,position desc;
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+---------------------------------------+
| id | select_type | table     | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                                 |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+---------------------------------------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_name_age_position | idx_name_age_position | 74      | const |    1 |      100 | Using index condition; Using filesort |
+----+-------------+-----------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+---------------------------------------+
1 row in set

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

mysql> explain select * from employees where name in('LiLei','xxx') order by age,position;
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table     | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | idx_name_age_position | NULL | NULL    | NULL |    3 |    66.67 | Using where; Using filesort |
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-----------------------------+
1 row in set

對於排序來說,多個相等條件也是範圍查詢

mysql> explain select * from employees where name >'a' order by name;
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table     | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | idx_name_age_position | NULL | NULL    | NULL |    3 |      100 | Using where; Using filesort |
+----+-------------+-----------+------------+------+-----------------------+------+---------+------+------+----------+-----------------------------+
1 row in set

用了filesort ,使用覆蓋索引優化:

mysql> explain select name,age,position from employees where name >'a' order by name;
+----+-------------+-----------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+--------------------------+
| id | select_type | table     | partitions | type  | possible_keys         | key                   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-----------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | employees | NULL       | index | idx_name_age_position | idx_name_age_position | 140     | NULL |    3 |      100 | Using where; Using index |
+----+-------------+-----------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+--------------------------+
1 row in set
 

優化總結:

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

 

 

 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章