SQL优化 - 索引成本计算和优化建议

2023-12-26 04:52

本文主要是介绍SQL优化 - 索引成本计算和优化建议,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Mysql优化器会对SQL进行优化生成执行计划,后续所有的执行流程都是按照这样的执行计划执行,在此阶段就会决策评估索引的选择,mysql在对于索引选择会有关键性的评估依据:成本

说白了,假如有2个索引,优化器会分别对这两个索引使用成本进行评估,这里会涉及到mysql成本评估模型。最终得到成本结果cost,最终对比两者的成本,成本低被选择。

一、查看成本COST

方式一  EXPLAIN FORMAT=JSON

这种方式在SQL执行流程已经介绍过,它包括了最终选择的索引执行计划成本信息。如果想要看更详细的成本决策信息,可以通过OPTIMIZER_TRACE(优化器跟踪)进行跟踪查看。

方式二 OEPTIMIZER_TRACE

在Mysql5.6之后,支持了OEPTIMIZER_TRACE(优化器跟踪)功能,可以在SQL执行后,跟踪记录当前SQL执行优化过程,类似于探针监控PINPOINT。该功能默认关闭状态,可以手动开启,当然在生产区不建议开启,因为会有额外性能损耗。

show variables like '%optimizer_trace%';

开启:

set optimizer_trace="enabled=on",end_markers_in_json=on

这里默认limit=1 ,offset=-1 ,表示显示最后一条记录,具体可以根据需求来配置:MySQL: The Optimizer Trace

二、SQL优化跟踪

开启OPTIMIZER_TRACE之后,就可以进行优化器跟踪功能,比如执行完如下sql

select * from project where tenantsid = 286478108025408

查看优化器内容:

select * from information_schema.optimizer_trace limit 1; // 显示最近一条
| select * from project where tenantsid = 286478108025408 | {    // 原始SQL信息"steps": [{1. 准备阶段"join_preparation": {"select#": 1,"steps": [{// 拓展查询内容,select * 会被转成所有可达字段"expanded_query": "/* select#1 */ select `project`.`tenantsid` AS `tenantsid`,`project`.`project_id` AS `project_id`,`project`.`code` AS `code`,`project`.`name` AS `name`,`project`.`sales_code` AS `sales_code`,`project`.`sales_name` AS `sales_name` from `project` where (`project`.`tenantsid` = 286478108025408)"}]} },{  // 2. 优化阶段"join_optimization": {"select#": 1,"steps": [{// 2.1 条件优化过程 "condition_processing": {"condition": "WHERE",  "original_condition": "(`project`.`tenantsid` = 286478108025408)",   // 原始的条件"steps": [{"transformation": "equality_propagation",  // 开始等价替换步骤 , 比如where a=1 and b=a  ==> where a=1 and b=1"resulting_condition": "multiple equal(286478108025408, `project`.`tenantsid`)" // 没有可替换},{"transformation": "constant_propagation", // 常常量条件句转换"resulting_condition": "multiple equal(286478108025408, `project`.`tenantsid`)" // 没有可替换},{"transformation": "trivial_condition_removal",  // 无效条件去除 ,比如 where a >10 and a > 5 ==> where a>10"resulting_condition": "multiple equal(286478108025408, `project`.`tenantsid`)"  // 没有可替换}] /* steps */} },{"substitute_generated_columns": {} /* substitute_generated_columns */},{// 2.2 多表关联关系信息,如果为多表,则会有多个表信息"table_dependencies": [{"table": "`project`","row_may_be_null": false,"map_bit": 0,   // 表的映射编号,从0开始递增"depends_on_map_bits": [  // 依赖的映射表,当使用STRAIGHT_JOIN强行控制连接顺序或者LEFT JOIN/RIGHT JOIN有顺序差别时,会在depends_on_map_bits中展示前置表的map_bit值] }] },{// 2.3 列出所有可用的ref类型的索引"ref_optimizer_key_uses": [{"table": "`project`","field": "tenantsid","equals": "286478108025408","null_rejecting": false},{"table": "`project`","field": "tenantsid","equals": "286478108025408","null_rejecting": false}]},{// 2.4 扫描行数评估(核心),Mysql会将所有可使用到的索引进行评估和成本计算,最终选择最优的成本索引"rows_estimation": [{// 2.4.1 当前表扫描分析 "table": "`project`","range_analysis": {"table_scan": {  // 全表扫描计算"rows": 6212,  // 预估扫描行数,预估值"cost": 1271.5 // 成本} ,//判断所有可能的索引是否可用"potential_range_indexes": [  {"index": "PRIMARY",   // 主键索引"usable": false,      // 不可用"cause": "not_applicable"  // 原因:不适合使用},{"index": "index_01",   // index_01索引"usable": true,        // 可以使用(不代表就要用)"key_parts": [         // 使用到的索引列   "tenantsid","code","name","project_id"]},{"index": "index02",   // index_02索引"usable": true,       // 可以使用(不代表就要用)"key_parts": [        // 使用到的索引列   "tenantsid","name","sales_code","code","project_id"]}] ,"setup_range_conditions": [  //如果有可下推的条件,则带条件考虑范围查询] ,"group_index_range": {   // 是否有合适的索引处理分组"chosen": false,       //  无"cause": "not_group_by_or_distinct"  // 原因:没有grouy或者distinct使用}  // 索引扫描成本分析"analyzing_range_alternatives": {"range_scan_alternatives": [{"index": "index_01",   // 对index_01索引扫描进行分析"ranges": [            // 扫描条件"286478108025408 <= tenantsid <= 286478108025408"  ] ,"index_dives_for_eq_ranges": true,    // 是否使用idnex_dives(下面介绍下),精确统计"rowid_ordered": false,               // 扫描的结果集是否按照主键PK进行排序"using_mrr": false,                   // 是否使用到MRR算法(下面也提一下)"index_only": false,                  // 是否仅仅使用到索引扫面就能满足,即是否索引覆盖了 "rows": 3106,                         // 预估扫描行数(预估值)"cost": 3728.2,                       // 计算成本"chosen": false,                      // 不选择 ,当然这个还不是最终结论,在 best_access_path 阶段,MySQL 会根据最优索引计算最终的成本修正的部分:每个表的大小、碎片率和使用率不同,所以最终的成本也会不同、另外还有根据当前的系统负载来调整成本,所以在做索引分析时,会以best_access_path结论为主"cause": "cost"                       // 原因:成本相对高},{"index": "index02",                   // 同上,因为使用索引条件相同,所以与index_01整体就没什么差异"ranges": ["286478108025408 <= tenantsid <= 286478108025408"] ,"index_dives_for_eq_ranges": true,"rowid_ordered": false,"using_mrr": false,"index_only": false,"rows": 3106,"cost": 3728.2,"chosen": false,"cause": "cost"}] ,"analyzing_roworder_intersect": {          // 分析是否使用了索引合并(index merge)"usable": false,                         // 未使用"cause": "too_few_roworder_scans"        // 原因:太少有序扫描行} } }}] },// 2.5 执行计划评估和选择(深思熟虑后的结论){"considered_execution_plans": [{"plan_prefix": [   // 前置执行计划] ,"table": "`project`","best_access_path": {// 深思熟虑后的执行计划路径选择"considered_access_paths": [{"access_type": "ref",   // 所以类型"index": "index_01",    // idnex_01索引"rows": 3106,           // 扫描行数(预估值)"cost": 702.2,          // 最终执行计划路径成本。从这里可以看出来上面对索引成本评估的数据和这里是有差距,这里就是在索引估算结果基础上进一步修正,评估出访问路径成本"chosen": true          // 确定选择使用},{"access_type": "ref",   // 同上"index": "index02","rows": 3106,"cost": 702.2,"chosen": false},{"rows_to_scan": 6212,   // 全表扫描的成本"access_type": "scan","resulting_rows": 6212,"cost": 1269.4,"chosen": false}] } ,"condition_filtering_pct": 100, //类似于explain的filtered列,是一个估算值"rows_for_plan": 3106,   // 扫描行数"cost_for_plan": 702.2,  // 最优的路径成本"chosen": true}] },// 附加条件信息,除了原始的条件之外,优化器可能会附件一些条件,在不印象结果的基础上提高效能{"attaching_conditions_to_tables": {"original_condition": "(`project`.`tenantsid` = 286478108025408)", // 原始条件"attached_conditions_computation": [  使用启发式算法计算已使用的索引,如果已使用的索引的访问类型是ref,则计算用range能否使用组合索引中更多的列,如果可以,则用range的方式替换ref] ,// 附件条件信息"attached_conditions_summary": [{"table": "`project`","attached": null}] } },{"refine_plan": [  //改善执行计划{"table": "`project`"}] }] } },// 3. 执行步骤{"join_execution": {"select#": 1,"steps": [] /* steps */} /* join_execution */}] /* steps */
} 

上面针对trace分析结果做了详细的解释,报文比较大,针对优化分析来说,可以按照下面侧重点来关注:

2、rows_estimation步骤-索引成本计算

(1)Mysql成本理解

Mysql在计算成本时候分为IO和CPU两个成本,即最终的成本数据也就是两者之和。

IO成本:从磁盘读取数据页加载到内存的过程。

CPU成本:数据被加载到内存后,读取和检索记录是否满足搜索要求以及结果集排序等操作。

在Mysql5.7版本,并不能准确预测某个查询需要访问的数据中有哪些已经加载到内存中,有哪些还停留在磁盘上。所以MySQL很粗暴的认为不管有没有加载到内存中,使用的成本都是1.0。而在8.0版本中的能更加准确的预测是否加入到缓存了,成本为0.25;

不需要太深入,只要记住:在Mysql5.7版本中:

1、IO成本:读取一个页面花费的成本默认是1.0

2、CPU成本:读取以及检测一条记录是否符合搜索条件的成本默认是0.2

这些数值可以理解为系数,比如从磁盘读取一个数据块过程,需要的成本为1

索引成本计算:

COST = IO成本 + CPU成本

那我们依照上面的trace结论来说明下的,成本计算

(2)索引成本计算

A、全表扫描成本计算

对于InnoDB存储引擎来说,全表扫描的意思就是把聚集索引中的记录都依次与给定的搜索条件进行比较,把符合搜索条件的记录加入到结果集中。 所以需要将聚集引对应的页面加载到内存中,然后再检测记录是否符合搜索条件。

全表扫描计算公式:数据页(IO成本)* 1.0  +  数据行数 * 0.2 (CPU成本)

 解释:

1、全表扫描时,数据会以页单位读取到内存中,所以当前表一共多少数据页就需要多少次读取,即为IO成本

2、数据读取到内存后,需要对每个数据进行读取和检索,N笔数据就需要N*0.25的CPU成本。

因此我们就需要得到这两数据:页数、数据行数

这里page数指的是数据占用业务,而不是innodb底层数据页数,所以能用innodb_ruby来查看,我们可以通过:

show table status like 'project'  
或者 
select data_length,table_rows from information_schema.tables where table_name = 'project'

 数据页数  =  data_length / 16(页大小kb) / 1024 = 27 

 数据行数 = 6212(预估值)

IO成本 = 27 * 1.0 + 1(微调值,不用关心) = 28

CPU成本 = 6212 * 0.2 + 1.1(微调值,不用关心) = 1243.5

cost成本 = 28 + 1243.5 = 1271.5  (与上面相同)

B、索引成本计算

index01索引成本计算

二级索引成本计算 = IO成本+ 数据数据CPU成本 + 回表IO成本 + 回表CPU成本

这个流程前面有说过:SQL优化-深入了解SQL处理流程原理(Server层与存储引擎交互、数据管理结构)-CSDN博客

索引IO成本 = 1 * 1(Mysql认为一个区间数据获取成本就是1次IO,这里区间就是

*** <= tenantsid <=**** , 这个解释也说得通,因为同一个区间页是连续存储的,读取效率快)

索引数据CPU成本 = 3106 * 0.2  = 621.2

回表IO成本 = 3106 * 1.0(因为回表是随机IO读取,所以每1条数据都需要一次IO)= 3106

回表CPU成本 = 3106 * 0.2 = 621.2

总成本:1 + 621.2 + 3106 + 621.2 = 4349.4

因为在MySQL的实际计算中,在和全文扫描比较成本时,使用索引的成本会去除掉读取和检测回表后聚簇索引记录的成本,所以结果是4349.4 - 621.2 = 3728.2

B、连接表成本

对驱动表进行查询后得到的记录条数称之为驱动表的扇出(fanout)。很显然驱动表的扇出值越小,对被驱动表的查询次数也就越少,连接查询的总成本也就越低。

连接查询总成本 = 单次访问驱动表的成本 + 驱动表扇出数 x 单次访问被驱动表的成本

注意:trace成本分析时可以根据索引成本结果来分析索引选择过程,但是索引成本结果并不是最终执行计划结果,最终的执行计划成本best_access_path,会基于索引成本结果进一步修正,比如每个表的大小、碎片率和使用率不同,所以最终的成本也会不同、另外还有根据当前的系统负载来调整成本。所以会看到best_access_path成本和索引成本有差异,最终也是按照best_access_path结果来决定最终的执行计划路径。

结论和优化建议:

OPTIMIZER_TRACE可以对SQL进行优化器跟踪,可以帮助我们了解优化器过程,同时可以通过此信息了解索引决策。在实际开发中可能并不需要这么详细的计算索引使用成本,但是通过对以上知识了解我们可以等到结论和优化建议:

1、索引目的就是快速的定位数据,得出扫描数据,即扫描数据越少索引成本越低,效率越快。因此索引设计时,索引栏位尽量要保证选择性高,即表中当前栏位重复率小的数据,可以通过索引基数(Cardinality,不重复的数据来识别,这样能保证使用索引时,能过滤掉更多的数据。

2、并不是有了索引就能使用到索引,通过SQL执行流程和成本评估,当扫描行数过大,同时需要回表时,可能最终的成本很高,这时可能会走全表扫描。

3、可以看出来发生回表时,成本会增幅很大,因为需要随机IO,所以几乎每一条扫描数据都需要1成本,所以尽量减少回表次数就很重要。

4、Mysql虽然认为每一个区间IO成本就是1,但是可能存在多单点区间场景,比如in查询,每个in元素都是一个单点区间,即1次IO成本(这里还涉及到一个index_dives,后续再说吧)。所以尽量减少单点区间数量,避免因为成本太高,而走全表扫描。

5、你写的SQL在优化器优化下可能会发生很大的变化,比如关联表顺序、连接字段可能都在优化器作用下进行调整,所以具体分析SQL执行过程要看详细执行计划,而不是SQL本身。

这篇关于SQL优化 - 索引成本计算和优化建议的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/538195

相关文章

SQL中的CASE WHEN用法小结

《SQL中的CASEWHEN用法小结》文章详细介绍了SQL中的CASEWHEN函数及其用法,包括简单CASEWHEN和CASEWHEN条件表达式两种形式,并通过多个实际场景展示了如何使用CASEWH... 目录一、简单CASE WHEN函数:二、CASE WHEN条件表达式函数三、常用场景场景1:不同状态展

mysql删除无用用户的方法实现

《mysql删除无用用户的方法实现》本文主要介绍了mysql删除无用用户的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 1、删除不用的账户(1) 查看当前已存在账户mysql> select user,host,pa

使用国内镜像源优化pip install下载的方法步骤

《使用国内镜像源优化pipinstall下载的方法步骤》在Python开发中,pip是一个不可或缺的工具,用于安装和管理Python包,然而,由于默认的PyPI服务器位于国外,国内用户在安装依赖时可... 目录引言1. 为什么需要国内镜像源?2. 常用的国内镜像源3. 临时使用国内镜像源4. 永久配置国内镜

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

使用Dify访问mysql数据库详细代码示例

《使用Dify访问mysql数据库详细代码示例》:本文主要介绍使用Dify访问mysql数据库的相关资料,并详细讲解了如何在本地搭建数据库访问服务,使用ngrok暴露到公网,并创建知识库、数据库访... 1、在本地搭建数据库访问的服务,并使用ngrok暴露到公网。#sql_tools.pyfrom

MySQL中实现多表查询的操作方法(配sql+实操图+案例巩固 通俗易懂版)

《MySQL中实现多表查询的操作方法(配sql+实操图+案例巩固通俗易懂版)》本文主要讲解了MySQL中的多表查询,包括子查询、笛卡尔积、自连接、多表查询的实现方法以及多列子查询等,通过实际例子和操... 目录复合查询1. 回顾查询基本操作group by 分组having1. 显示部门号为10的部门名,员

MyBatis-Plus中静态工具Db的多种用法及实例分析

《MyBatis-Plus中静态工具Db的多种用法及实例分析》本文将详细讲解MyBatis-Plus中静态工具Db的各种用法,并结合具体案例进行演示和说明,具有很好的参考价值,希望对大家有所帮助,如有... 目录MyBATis-Plus中静态工具Db的多种用法及实例案例背景使用静态工具Db进行数据库操作插入

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C

通过ibd文件恢复MySql数据的操作方法

《通过ibd文件恢复MySql数据的操作方法》文章介绍通过.ibd文件恢复MySQL数据的过程,包括知道表结构和不知道表结构两种情况,对于知道表结构的情况,可以直接将.ibd文件复制到新的数据库目录并... 目录第一种情况:知道表结构第二种情况:不知道表结构总结今天干了一件大事,安装1Panel导致原来服务

mysql关联查询速度慢的问题及解决

《mysql关联查询速度慢的问题及解决》:本文主要介绍mysql关联查询速度慢的问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql关联查询速度慢1. 记录原因1.1 在一次线上的服务中1.2 最终发现2. 解决方案3. 具体操作总结mysql