数据库监控与调优【十一】—— 索引调优技巧

news2024/12/23 5:38:56

索引调优技巧

  • 长字段的索引调优
  • 使用组合索引的技巧
  • 覆盖索引
  • 排序优化
  • 冗余、重复索引的优化

长字段的索引调优

举例

实际项目中,我们可能需要给很长的字段添加索引。

比如以下first_name字段里面存储的数据普遍在200以上。

SELECT
	* 
FROM
	employees 
WHERE
	first_name = 'Facello';

比如以上sql直接给first_name字段创建索引即可,但是first_name存储的数据字符串很大,就会导致索引占用的空间很大。

而且作用在超长字段上的索引查询效率也不高

优化方案一——伪"Hash索引"

额外新建一个字段first_name_hash,

ALTER TABLE employees ADD first_name_hash INT DEFAULT 0 COMMENT 'first_name字段的hashcode';

存储数据时:

INSERT INTO employees ( emp_no, birth_date, first_name, last_name, gender, hire_date, first_name_hash ) 
VALUE
	( 999999, now( ), '大目......................', '大', 'M', now( ), CRC32( '大目......................' ) );

first_name_hash的值应该具备以下要求:

  1. 字段的长度应该比较的小,SHA1/MD5算法计算的值是不合适的
  2. 应当尽量避免hash冲突,就目前来说,流行使用CRC32()或者FNV64(),CRC32()或者FNV64()都属于hash算法,而且FNV64()算法的哈希冲突的概率比CRC32()要低一些

做完以上这些后,查询语句可以修改为:

SELECT
	* 
FROM
	employees 
WHERE
	first_name_hash = CRC32( 'Facello' ) 
	AND first_name = 'Facello';

这样只需要在first_name_hash字段上添加索引即可

主要注意的是,依然带有first_name = 'Facello’作为查询条件的原因是为了sql在哈希冲突时也能正确返回结果

ps: 引入hash字段,作为索引,也叫做伪"Hash索引"。注意这里的Hash索引和之前的Hash索引不是一回事,这个索引本质上还是看加在hash字段上的索引是什么类型

优化方案二——前缀索引

优化方案一对于以下这种sql查询是无力的,因为我们是对sql的完整值进行hash的,所以like查询没有办法使用优化方案一进行优化

SELECT
	* 
FROM
	employees 
WHERE
	first_name LIKE 'Facello%';

如果我们依然希望索引字段的值比较小,该怎么办?

使用前缀索引,5代表前缀长度,表示取first_name字段前5个字符做索引

ALTER TABLE employees ADD KEY ( first_name ( 5 ) );

我们肯定希望5这个数字尽可能的小,这样仅能节省空间,也能提升性能。同时我们也希望索引的选择性足够高

索引的选择性公式 = 不重复的索引值/数据表的总记录数。结果数值越大,表示选择性越高,性能越好

SELECT
	count( DISTINCT first_name ) / count( * ) 
FROM
	employees;
-- 前缀5: 0.0038   前缀6:0.0041   前缀7:0.0042   前缀8:0.0042
-- 完整列的选择性:0.0042[这个字段的最大选择性了]
SELECT
	count( DISTINCT LEFT ( first_name, 7 ) ) / count( * ) 
FROM
	employees;

结论:前缀长度设置成7是最好的,即能节省空间又能获得最好的选择性

ALTER TABLE employees ADD KEY ( first_name ( 7 ) );

前缀索引的总结

好处:前缀索引可以有效得让索引更小,更加高效,而且对上层应用是透明的,我们的数据库表不需要做任何改造,使用成本低。所以,这是一种比较容易落地的优化方案。

坏处:存在局限性,无法做order by、group by;而且无法使用覆盖索引

前缀索引的应用

具体业务场景中要学会活学活用这个索引。

比如业务场景需要利用某个字段的后缀进行查询,而mysql不存在后缀索引,此时,可以这样做

“后缀索引”:额外创建一个字段,比如说first_name_reverse,在存储的时候,把first_name的值翻转过来再存储。比方说:Facello ==> ollecaF存储到first_name_reverse,接着再为first_name_reverse字段创建一个前缀索引

单列索引 vs 组合索引

单列索引

如下sql在salaries表没有任何索引的情况下

SELECT
	* 
FROM
	salaries 
WHERE
	from_date = '1986-06-26' 
	AND to_date = '1987-06-26';
	
-- 花费0.334s

执行EXPLAIN查看执行计划

EXPLAIN SELECT
	* 
FROM
	salaries 
WHERE
	from_date = '1986-06-26' 
	AND to_date = '1987-06-26';

在这里插入图片描述

可以看到type显示ALL,走的是全表扫描

创建两个单列索引

CREATE INDEX `salaries_from_date_index` ON salaries ( `from_date` );
CREATE INDEX `salaries_to_date_index` ON salaries ( `to_date` );

执行之前的sql,可以发现只需要0.001s

再次执行EXPLAIN查看执行计划

在这里插入图片描述

可以看到结果type为index_merge,发生了索引合并,key为salaries_to_date_index,salaries_from_date_index,表示使用到了创建的两个单列索引。Extra里面的Using intersect(salaries_to_date_index,salaries_from_date_index); Using where表示使用这两个索引做取交集操作

使用OPTIMIZER TRACE查看以上sql更详细的执行过程

首先开启OPTIMIZER TRACE

SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
SET optimizer_trace_offset=-30, optimizer_trace_limit=30;   

再执行sql

之后查询OPTIMIZER_TRACE信息

SELECT
	* 
FROM
	information_schema.OPTIMIZER_TRACE 
WHERE
	QUERY LIKE '%salaries%' 
	LIMIT 30;

-- 取出TRACE项结果
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE limit 30;

查看结果中TRACE项结果

{
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `salaries`.`emp_no` AS `emp_no`,`salaries`.`salary` AS `salary`,`salaries`.`from_date` AS `from_date`,`salaries`.`to_date` AS `to_date` from `salaries` where ((`salaries`.`from_date` = '1986-06-26') and (`salaries`.`to_date` = '1987-06-26'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`salaries`.`from_date` = '1986-06-26') and (`salaries`.`to_date` = '1987-06-26'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(multiple equal('1986-06-26', `salaries`.`from_date`) and multiple equal('1987-06-26', `salaries`.`to_date`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(multiple equal('1986-06-26', `salaries`.`from_date`) and multiple equal('1987-06-26', `salaries`.`to_date`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(multiple equal(DATE'1986-06-26', `salaries`.`from_date`) and multiple equal(DATE'1987-06-26', `salaries`.`to_date`))"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [
              {
                "table": "`salaries`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
              {
                "table": "`salaries`",
                "field": "from_date",
                "equals": "DATE'1986-06-26'",
                "null_rejecting": true
              },
              {
                "table": "`salaries`",
                "field": "to_date",
                "equals": "DATE'1987-06-26'",
                "null_rejecting": true
              }
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [
              {
                "table": "`salaries`",
                "range_analysis": {
                  ## 表示如果做全表扫描,需要扫描的行数和开销
                  "table_scan": {
                    "rows": 2838426,
                    "cost": 287765
                  } /* table_scan */,
                  ## 列出表中所有索引,并说明是否可用
                  "potential_range_indexes": [
                    ## 主键索引是不可用的
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    ## 作用在from_date上面的索引salaries_from_date_index可用
                    {
                      "index": "salaries_from_date_index",
                      "usable": true,
                      "key_parts": [
                        "from_date",
                        "emp_no"
                      ] /* key_parts */
                    },
                    ## 作用在to_date上面的索引salaries_to_date_index可用
                    {
                      "index": "salaries_to_date_index",
                      "usable": true,
                      "key_parts": [
                        "to_date",
                        "emp_no",
                        "from_date"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  ## 分析是否能够执行跳跃扫描,是mysql8.0引入的新特性,可以在mysql内部优化特性的查询
                  "skip_scan_range": {
                    "potential_skip_scan_indexes": [
                      {
                        "index": "salaries_from_date_index",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      },
                      {
                        "index": "salaries_to_date_index",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      }
                    ] /* potential_skip_scan_indexes */
                  } /* skip_scan_range */,
                  ## 分析各种索引使用的成本
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      ## 作用在from_date上面的索引salaries_from_date_index预估会扫描88行,开销是65.8188
                      {
                        "index": "salaries_from_date_index",
                        "ranges": [
                          "from_date = '1986-06-26'"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": false,
                        "in_memory": 0,
                        "rows": 88,
                        "cost": 65.8188,
                        ## 代表使用了该索引
                        "chosen": true
                      },
                      ## 作用在to_date上面的索引salaries_to_date_index预估会扫描86行,开销是64.3377
                      {
                        "index": "salaries_to_date_index",
                        "ranges": [
                          "to_date = '1987-06-26'"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": false,
                        "in_memory": 0,
                        "rows": 86,
                        "cost": 64.3377,
                        ## 代表使用了该索引
                        "chosen": true
                      }
                    ] /* range_scan_alternatives */,
                    ## 用来做索引合并
                    "analyzing_roworder_intersect": {
                      ## 做交集操作的索引有哪些
                      "intersecting_indexes": [
                        {
                          "index": "salaries_to_date_index",
                          "index_scan_cost": 1.10366,
                          "cumulated_index_scan_cost": 1.10366,
                          "disk_sweep_cost": 54.9916,
                          "cumulated_total_cost": 56.0952,
                          "usable": true,
                          "matching_rows_now": 86,
                          "isect_covering_with_this_index": false,
                          "chosen": true
                        },
                        {
                          "index": "salaries_from_date_index",
                          "index_scan_cost": 1.1061,
                          "cumulated_index_scan_cost": 2.20976,
                          "disk_sweep_cost": 0,
                          "cumulated_total_cost": 2.20976,
                          "usable": true,
                          "matching_rows_now": 0.00266627,
                          "isect_covering_with_this_index": false,
                          "chosen": true
                        }
                      ] /* intersecting_indexes */,
                      "clustered_pk": {
                        "clustered_pk_added_to_intersect": false,
                        "cause": "no_clustered_pk_index"
                      } /* clustered_pk */,
                      ## 求交集的开销
                      "rows": 1,
                      "cost": 2.20976,
                      "covering": false,
                      "chosen": true
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "index_roworder_intersect",
                      "rows": 1,
                      "cost": 2.20976,
                      "covering": false,
                      "clustered_pk_scan": false,
                      "intersect_of": [
                        {
                          "type": "range_scan",
                          "index": "salaries_to_date_index",
                          "rows": 86,
                          "ranges": [
                            "to_date = '1987-06-26'"
                          ] /* ranges */
                        },
                        {
                          "type": "range_scan",
                          "index": "salaries_from_date_index",
                          "rows": 88,
                          "ranges": [
                            "from_date = '1986-06-26'"
                          ] /* ranges */
                        }
                      ] /* intersect_of */
                    } /* range_access_plan */,
                    "rows_for_plan": 1,
                    "cost_for_plan": 2.20976,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`salaries`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "salaries_from_date_index",
                      "rows": 88,
                      "cost": 65.1682,
                      "chosen": true
                    },
                    {
                      "access_type": "ref",
                      "index": "salaries_to_date_index",
                      "rows": 86,
                      "cost": 63.6871,
                      "chosen": true
                    },
                    {
                      "rows_to_scan": 1,
                      "access_type": "range",
                      "range_details": {
                        "used_index": "intersect(salaries_to_date_index,salaries_from_date_index)"
                      } /* range_details */,
                      "resulting_rows": 1,
                      "cost": 2.30976,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 1,
                "cost_for_plan": 2.30976,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`salaries`",
                  "attached": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`salaries`",
                "original_table_condition": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))",
                "final_table_condition   ": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))"
              }
            ] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`salaries`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

OPTIMIZER_TRACE对于单表查询最重要的是rows_estimation,具体见上面注释

组合索引

创建一个组合索引

CREATE INDEX `salaries_from_date_to_date_index` ON salaries ( `from_date`, `to_date` );

再次执行上面查询语句

EXPLAIN SELECT
	* 
FROM
	salaries 
WHERE
	from_date = '1986-06-26' 
	AND to_date = '1987-06-26';

在这里插入图片描述

可以看到结果type为ref,而ref的性能是比index_merge好的,所以组合索引是比之前两个单列索引要好。key为salaries_from_date_to_date_index,表示使用到了创建的组合索引。

执行之前的sql,可以发现也需要0.001s,可以发现和上面两个单列索引的耗时应该差不多。

但是按理来说,组合索引的性能应该是比多个单列索引做索引合并的性能要好,但是为啥耗时没有太大变化呢?

这是因为在例子中求交集的开销并不大,只是88行数据和86行数据求交集而已。

但是一旦求交集的数据多了,求交集的开销就会很大,此时组合索引的优势就体现出来了。

总结

  • SQL存在多个条件,多个单列索引,会使用索引合并
  • 如果出现索引合并,往往说明索引不够合理
  • 但是如果SQL暂时没有性能问题,暂时可以不管
    • 就比如以上的例子,如果之前使用的是两个单列索引,因为数据求交集的行数不够,即数据量不够,查询性能能够满足需求的话,依然推荐继续使用不做任何优化。等后续数据量增大出现性能达不到要求再做组合索引也可。
  • 组合索引要注意索引列顺序【最左前缀原则】

覆盖索引

  • 对于索引X,SELECT的字段只需从索引就能获得,而无需到表数据里获取,这样的索引就叫覆盖索引
    • 只需要直接通过索引拿到想要的数据,不需要再到表数据里面读取行,提升性能
    • 假设需要查询id=5的数据,id为主键索引,先在索引里面搜索id=5,然后在表数据里面获取数据,但如果主键就能覆盖所有查询字段就不需要到表数据里面获取数据了,直接返回索引即可
    • 假设索引为非主键索引,可以通过索引拿到数据的主键,再通过主键到表数据里面获得数据,但如果非主键索引能够覆盖所有的查询字段就可以省略两步,第一通过非主键获得主键,第二通过主键获得数据

实验

创建一个组合索引

CREATE INDEX `salaries_from_date_to_date_index` ON salaries ( `from_date`, `to_date` );

执行以下sql,组合索引是无法覆盖查询字段的

SELECT
	* 
FROM
	salaries 
WHERE
	from_date = '1986-06-26' 
	AND to_date = '1987-06-26';

explain结果

在这里插入图片描述

使用OPTIMIZER_TRACE查看sql的执行详情:

{
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `salaries`.`emp_no` AS `emp_no`,`salaries`.`salary` AS `salary`,`salaries`.`from_date` AS `from_date`,`salaries`.`to_date` AS `to_date` from `salaries` where ((`salaries`.`from_date` = '1986-06-26') and (`salaries`.`to_date` = '1987-06-26'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`salaries`.`from_date` = '1986-06-26') and (`salaries`.`to_date` = '1987-06-26'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(multiple equal('1986-06-26', `salaries`.`from_date`) and multiple equal('1987-06-26', `salaries`.`to_date`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(multiple equal('1986-06-26', `salaries`.`from_date`) and multiple equal('1987-06-26', `salaries`.`to_date`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(multiple equal(DATE'1986-06-26', `salaries`.`from_date`) and multiple equal(DATE'1987-06-26', `salaries`.`to_date`))"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [
              {
                "table": "`salaries`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
              {
                "table": "`salaries`",
                "field": "from_date",
                "equals": "DATE'1986-06-26'",
                "null_rejecting": true
              },
              {
                "table": "`salaries`",
                "field": "to_date",
                "equals": "DATE'1987-06-26'",
                "null_rejecting": true
              }
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [
              {
                "table": "`salaries`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 2838426,
                    "cost": 287736
                  } /* table_scan */,
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "salaries_from_date_to_date_index",
                      "usable": true,
                      "key_parts": [
                        "from_date",
                        "to_date",
                        "emp_no"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "skip_scan_range": {
                    "potential_skip_scan_indexes": [
                      {
                        "index": "salaries_from_date_to_date_index",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      }
                    ] /* potential_skip_scan_indexes */
                  } /* skip_scan_range */,
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "salaries_from_date_to_date_index",
                        "ranges": [
                          "from_date = '1986-06-26' AND to_date = '1987-06-26'"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": false,
                        "in_memory": 0,
                        "rows": 86,
                        "cost": 63.9297,
                        "chosen": true
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "salaries_from_date_to_date_index",
                      "rows": 86,
                      "ranges": [
                        "from_date = '1986-06-26' AND to_date = '1987-06-26'"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 86,
                    "cost_for_plan": 63.9297,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`salaries`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "salaries_from_date_to_date_index",
                      "rows": 86,
                      "cost": 63.2839,
                      "chosen": true
                    },
                    {
                      "access_type": "range",
                      "range_details": {
                        "used_index": "salaries_from_date_to_date_index"
                      } /* range_details */,
                      "chosen": false,
                      "cause": "heuristic_index_cheaper"
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 86,
                "cost_for_plan": 63.2839,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`salaries`",
                  "attached": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`salaries`",
                "original_table_condition": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))",
                "final_table_condition   ": null
              }
            ] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`salaries`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

修改查询语句为,此时索引能覆盖查询字段

SELECT
	from_date, to_date 
FROM
	salaries 
WHERE
	from_date = '1986-06-26' 
	AND to_date = '1987-06-26';

explain结果

在这里插入图片描述

explain结论:可以看到使用覆盖索引并不会修改sql的执行过程,因为type和rows是一样的,但是一旦使用覆盖索引,会在Extra里面展示Using index,这是一个显著的标志

使用OPTIMIZER_TRACE查看sql的执行详情:

{
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `salaries`.`from_date` AS `from_date`,`salaries`.`to_date` AS `to_date` from `salaries` where ((`salaries`.`from_date` = '1986-06-26') and (`salaries`.`to_date` = '1987-06-26'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`salaries`.`from_date` = '1986-06-26') and (`salaries`.`to_date` = '1987-06-26'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(multiple equal('1986-06-26', `salaries`.`from_date`) and multiple equal('1987-06-26', `salaries`.`to_date`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(multiple equal('1986-06-26', `salaries`.`from_date`) and multiple equal('1987-06-26', `salaries`.`to_date`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(multiple equal(DATE'1986-06-26', `salaries`.`from_date`) and multiple equal(DATE'1987-06-26', `salaries`.`to_date`))"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [
              {
                "table": "`salaries`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
              {
                "table": "`salaries`",
                "field": "from_date",
                "equals": "DATE'1986-06-26'",
                "null_rejecting": true
              },
              {
                "table": "`salaries`",
                "field": "to_date",
                "equals": "DATE'1987-06-26'",
                "null_rejecting": true
              }
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [
              {
                "table": "`salaries`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 2838426,
                    "cost": 287736
                  } /* table_scan */,
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "salaries_from_date_to_date_index",
                      "usable": true,
                      "key_parts": [
                        "from_date",
                        "to_date",
                        "emp_no"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  ## 当使用覆盖索引多出来的,表示这个sql是使用了覆盖索引了
                  "best_covering_index_scan": {
                    "index": "salaries_from_date_to_date_index",
                    "cost": 288342,
                    "chosen": true,
                    "cause": "cost"
                  } /* best_covering_index_scan */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "skip_scan_range": {
                    "potential_skip_scan_indexes": [
                      {
                        "index": "salaries_from_date_to_date_index",
                        "usable": false,
                        "cause": "no_range_predicate"
                      }
                    ] /* potential_skip_scan_indexes */
                  } /* skip_scan_range */,
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      ## 使用覆盖索引比没有使用之前小很多,以下一些开销也一样
                      {
                        "index": "salaries_from_date_to_date_index",
                        "ranges": [
                          "from_date = '1986-06-26' AND to_date = '1987-06-26'"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": true,
                        "in_memory": 0,
                        "rows": 86,
                        "cost": 9.74471,
                        "chosen": true
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "salaries_from_date_to_date_index",
                      "rows": 86,
                      "ranges": [
                        "from_date = '1986-06-26' AND to_date = '1987-06-26'"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 86,
                    "cost_for_plan": 9.74471,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`salaries`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "salaries_from_date_to_date_index",
                      "rows": 86,
                      "cost": 9.73471,
                      "chosen": true
                    },
                    {
                      "access_type": "range",
                      "range_details": {
                        "used_index": "salaries_from_date_to_date_index"
                      } /* range_details */,
                      "chosen": false,
                      "cause": "heuristic_index_cheaper"
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 86,
                "cost_for_plan": 9.73471,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`salaries`",
                  "attached": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`salaries`",
                "original_table_condition": "((`salaries`.`to_date` = DATE'1987-06-26') and (`salaries`.`from_date` = DATE'1986-06-26'))",
                "final_table_condition   ": null
              }
            ] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`salaries`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

具体不同见上面注释

总结

覆盖索引能够有效提升性能。所以写SQL尽量只返回想要的字段,避免select *

  • 如果查询字段都被索引包含,就可以使用覆盖索引,降低开销
  • 尽量减少查询的字段也减少了网络传输的开销

重复索引、冗余索引、未使用的索引如何处理

索引是有开销的!主要是增删改的时候,索引的维护开销;索引越多,这个维护开销也就越大,所以在条件允许的情况下,应该少创建索引

重复索引

  • 在相同的列上按照相同的顺序创建的索引
  • 尽量避免重复索引,如果发现重复索引应该删除

例如:

-- 针对id字段创建了主键索引、唯一索引、普通索引
-- 但其实唯一索引就是在普通索引上增加了唯一性约束;而主键索引又是在唯一索引上增加了非空约束
-- 这就相当于在id字段上创建了三个重复的索引
-- 一般来说,重复索引需要避免
create table test_table
(
    id int not null primary key auto_increment,
    a  int not null,
    b  int not null,
    UNIQUE (id),
    INDEX (id)
) ENGINE = InnoDB;

– 发生了重复索引,改进方案:

-- 可以把唯一索引、普通索引删除,只保留主键索引
create table test_table
(
    id int not null primary key auto_increment,
    a  int not null,
    b  int not null
) ENGINE = InnoDB;

冗余索引

  • 如果已经存在索引index(A,B),又创建了index(A),那么index(A)就是index(A,B)的冗余索引
    • 这样其实index(A)是index(A,B)的前缀索引
  • 冗余索引都是针对B-Tree和B+Tree索引来说的,对于哈希索引、全文索引等都没有冗余索引的概念
  • 一般要避免,但有特例!一定要避免掉进陷阱里!
EXPLAIN SELECT
	* 
FROM
	salaries 
WHERE
	from_date = '1986-06-26' 
ORDER BY
	emp_no;

– index(from_date):type=ref extra=null,ORDER BY子句使用了索引
因为index(from_date)是一个非主键索引,非主键索引会在B+Tree的叶子节点存储主键值,也就是前面说的非主键索引会先查询主键是啥。所以某种意义上来说就相当于index(from_date, emp_no),故ORDER BY子句也可以使用索引

– index(from_date, to_date):type=ref extra=Using filesort,ORDER BY子句无法使用索引,只能使用文件排序
因为index(from_date, to_date)某种意义上来说就相当于index(from_date, to_date, emp_no),不符合最左前缀原则,故ORDER BY子句没有使用索引

因此我们在项目中假设遇到上述情况类似的SQL,并且已经存在了组合索引index(from_date, to_date),那么可能还不够,还需要额外创建index(from_date)作用在from_date的冗余索引。所以将来删除某个冗余索引也需要注意下。

未使用的索引

某个索引根本未曾使用。这种索引是累赘,只会增加增删改的开销,应该删除!

如何发现重复索引、冗余索引、未使用的索引

人工分析?很累,不可靠。可以使用工具!后续介绍!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/692935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt 动态手势识别“握拳”

系列文章目录 通过Qt实现手势识别控制软件操作相关系列技术方案 (一)Qt 将某控件、图案绘制在最前面的方法,通过QGraphicsScene模块实现 (二)Qt QGraphicsScene模块实现圆点绘制在所有窗体的最前方,实现圆…

永磁同步电机无位置传感器控制,采用的是龙贝格,基于模型的 定点开发,仿真效果和实际95%高度吻合

永磁同步电机无位置传感器控制,采用的是龙贝格,基于模型的 定点开发,仿真效果和实际95%高度吻合,可以仿真学习,也可以直接移植到项目中 YID:32768642079012761

深度学习框架背景

深度学习框架背景 本文目录: 一、深度学习框架概念 二、为何要用深度学习框架 三、掌握深度学习框架要做哪些准备 四、深度学习主要应用场景 五、常见深度学习框架的对比 六、深度学习框架在市场上的占比 七、中国深度学习开源框架状况 八、备注 一、深度学…

redis------Hash操作(字典)

Hash操作,redis中Hash在内存中的存储格式如下图: # hash类型就是咱们python中的字典,key-value,字典又叫hash类型 字典的key必须可hash -字典类型在底层存储,基于数组存的 key---{key:value,key:value} hset(name, key, val…

【Vue】CI持续集成Vue前端项目--20230627

我的实践(CI) 1.打包 npm run build,产生dist文件 2.将dist文件的内容拷贝到static下面。不用在nginx文件夹中 3.编写nginx配置 default.conf server {listen 80;listen [::]:80;server_name localhost;location / {root /usr/…

【MySQL数据库】MHA高可用配置及故障切换

目录 一、MHA简介1.1什么是MHA1.2MHA的组成1.3MHA的特点 二、搭建MHA2.1故障模拟2.2故障修复 一、MHA简介 1.1什么是MHA MHA(MasterHigh Availability)是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。MHA 的出现就是解决MySQL 单点的问题。M…

掌握构造函数:打破面向对象编程难题

文章目录 I. 介绍解释构造函数的概念及其作用为什么构造函数是面向对象编程中至关重要的一部分 II. 创建构造函数介绍如何定义并创建一个构造函数着重介绍构造函数的语法和使用方法定义一个构造函数创建一个对象实例使用对象实例继承和原型 III. 构造函数的参数介绍构造函数的参…

模拟测试 青少年软件编程(Python)等级考试试卷(一级) 试题解析

【单选题】(每题2分) 1、与a>b and b>c等价的是?( ) A、a>b or b>c B、a>b>c C、a>b or not b D、not a 正确答案:B 试题解析:a>b并且b>c,那么a…

施工作业票管理

施工作业票管理是建筑行业中的一项重要管理,它是指在进行安全施工前,必须依据工程特点及安全要求,制定符合施工现场实际情况的施工作业方案,并按照程序进行作业申请及作业批准的程序书。比如说吊装作业票,高空作业票&a…

控制请求来源的HTML Meta标签 - Referrer详解

83. 控制请求来源的HTML Meta标签 - Referrer详解 在Web开发中&#xff0c;为了保护用户的隐私和安全&#xff0c;控制请求的来源信息是至关重要的。HTML中的<meta>标签提供了一种简单而有效的方式来控制请求的来源&#xff0c;其中包括Referrer&#xff08;引荐者&…

NVIDIA Jetson TX1,TX2,TX2 NX,AGX Xavier 和 Nano开发板GPIO口配置及应用

NVIDIA Jetson TX1&#xff0c;TX2&#xff0c;TX2 NX&#xff0c;AGX Xavier 和 Nano开发板GPIO口配置及应用 简介 ​ Jetson TX1、TX2、TX2 NX、AGX Xavier和Nano开发板都配备了一个包含40个引脚的GPIO接头&#xff0c;这些引脚可以用于数字输入输出。如下图 ​ 类似于Rasp…

cesium系列:根据中心点绘制圆和返回离某点最近的一个点的方法

1.背景 有需求写一个方法cesium 提供一个代码方法接收参数为function aaa(point1,point2,radius,num)其中点1和点2是经度纬度高度对象&#xff0c; 以point2为圆心&#xff0c;radius为半径生成一个圆弧&#xff0c;并创建一个数组在圆弧上均匀生成 num个点,点需要包含经纬高,…

2023年江西省研究生数学建模竞赛题目三解题思路

为了竞赛公平性&#xff0c;本人只提供思路&#xff0c;不提供具体的解决方法&#xff0c;欢迎交流讨论。给你们分享省赛特等奖的解题思路&#xff0c;哈哈哈&#xff0c;欢迎评论区讨论。这里给出第三题的解题思路。第二题的思路在这里&#xff1a;2023年江西省研究生数学建模…

打造加速组织变革的PMO—新药研发PMO实践分享︱先声药业研发PMO负责人

先声药业研发PMO负责人谢少斐先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;打造加速组织变革的PMO—新药研发PMO实践分享。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 2023年生物医药…

力扣 110. 平衡二叉树

题目来源&#xff1a;https://leetcode.cn/problems/balanced-binary-tree/description/ C题解1&#xff1a;递归法&#xff0c;后续遍历&#xff0c;从叶子节点开始&#xff0c;判断左右子树的深度差是否大于1。 /*** Definition for a binary tree node.* struct TreeNode {…

搭建OpenGL开发环境

资料 安装教程 软件下载 Visual Studio 2019CMakeGLFWGLAD glfw 3.3.8 下载glfw的目的&#xff1a; 获取压缩包里的include生成glfw3.lib 获取include 解压找到Include&#xff0c;include包含GLFW文件夹 生成glfw3.lib 使用CMake生成GLFW的工程文件在生成的目录中找…

基于OpenCV的傅里叶变换

基于OpenCV的傅里叶变换 傅里叶变换&#xff0c;表示能将满足一定条件的某个函数表示成三角函数&#xff08;正弦和/或余弦函数&#xff09;或者它们的积分的线性组合。在图像中变化剧烈的地方&#xff08;比如边界&#xff09;经过傅里叶别换后就相当与高频&#xff0c;反之变…

研究发现,电源指示灯的闪烁也能泄露密码

在巧妙的旁路攻击中&#xff0c;一组学者发现可以通过分析设备电源 LED 指示灯的变化来破解设备密钥。 内盖夫本古里安大学和康奈尔大学的研究人员在一项研究中表示&#xff0c;CPU 执行的密码计算会改变设备的功耗&#xff0c;从而影响设备电源 LED 的亮度。通过利用这一观察…

Git使用与配置

Git分布式版本控制工具 一、Git安装与配置 Git基本配置 打开Git Bash 设置用户信息 # 配置用户名和用户邮箱 git config --global user.name xxx git config --global user.email xxxxx.com查看用户信息 # 查看用户名和用户邮箱 git config --global user.name git config --g…

HVV蓝队扫盲,关于HVV你不知道的全在这

HVV蓝队扫盲,关于HVV你不知道的全在这 1.蓝队实战阶段职责分工2.蓝队防守的四个阶段3.蓝队常用防护手段4.蓝队常用安全设备边界防御设备安全检测设备流量监测设备终端防护设备威胁情报系统5.蓝队作战的三个阶段6.演练禁止采用的攻击方式1.蓝队实战阶段职责分工 1、领导小组。为…