索引调优技巧
- 长字段的索引调优
- 使用组合索引的技巧
- 覆盖索引
- 排序优化
- 冗余、重复索引的优化
长字段的索引调优
举例
实际项目中,我们可能需要给很长的字段添加索引。
比如以下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的值应该具备以下要求:
- 字段的长度应该比较的小,SHA1/MD5算法计算的值是不合适的
- 应当尽量避免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的冗余索引。所以将来删除某个冗余索引也需要注意下。
未使用的索引
某个索引根本未曾使用。这种索引是累赘,只会增加增删改的开销,应该删除!
如何发现重复索引、冗余索引、未使用的索引
人工分析?很累,不可靠。可以使用工具!后续介绍!