【图灵MySQL】MySQL索引优化实战(上)

news2024/11/21 1:46:39

【图灵MySQL】MySQL索引优化实战(上)

数据准备-SQL

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=1 DEFAULT CHARSET=utf8 COMMENT='员工记录表';

INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'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());

-- 插入一些示例数据
drop procedure if exists insert_emp; 
delimiter //
create procedure insert_emp()        
begin
  declare i int;                    
  set i=1;                          
  while(i<=100000)do                 
    insert into employees(name,age,position) values(CONCAT('zhuge',i),i,'dev');  
    set i=i+1;                       
  end while;
end //
delimiter ;
call insert_emp();

如果上面的存储过程报错,那就不用理会了,用navicat工具自动生成10w+的数据就行。在下面的的案例中,表的数据量,也会影响MySQL底层对于索引策略的选择

表字段 

索引情况

一些复杂的索引优化例子 

1、联合索引第一个字段用范围不会走索引

EXPLAIN SELECT * FROM employees 
WHERE name > 'LiLei' AND age = 22 AND position ='manager';

联合索引第一个字段就用范围查找不会走索引,MySQL内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不如就全表扫描。

我们不妨做个实验

EXPLAIN SELECT * FROM employees 
WHERE name = 'LiLei' AND age > 22 AND position ='manager';

EXPLAIN SELECT * FROM employees 
WHERE name = 'LiLei' AND age = 22 AND position >'manager';

用了两个字段索引 

用了三个字段索引

2、强制走索引

EXPLAIN SELECT * FROM employees force index(idx_name_age_position) 
WHERE name > 'LiLei' AND age = 22 AND position ='manager';

虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高。

不妨看看下面的这个例子

如果是MySQL8.0以下的版本,MySQL还有缓存,所以要先将其关闭!

MySQL8.0以上的可以忽略! 

-- 关闭查询缓存,8.0以上版本没有这个东西了!
set global query_cache_size=0;  
set global query_cache_type=0;

执行结果如下,反而走全表扫描的查询效率更高!(尽管扫描行数更多)

-- 执行时间0.081s,不走索引,扫描行数:99977
SELECT * FROM employees WHERE name > 'LiLei';

-- 执行时间0.176s,强制走索引,扫描行数:49988
SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei';

-- 如果使用覆盖索引,我们会发现执行效率更快!!!
-- 执行时间0.036s,扫描行数:49988
SELECT name,age,position FROM employees WHERE name > 'LiLei'

3、覆盖索引优化

EXPLAIN SELECT name,age,position FROM employees 
WHERE name > 'LiLei' AND age = 22 AND position ='manager';

所以一般情况下,我们遇到上述的这些问题,直接使用覆盖索引就好了!这也是为什么阿里巴巴手册上写着,慎用 “ SELECT * ” 的原因了! 

4、in和or在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描

EXPLAIN SELECT * FROM employees 
WHERE name in ('LiLei','HanMeimei','Lucy') 
AND age = 22 AND position ='manager';

大体的的意思就是数据量太大了,走索引的效率会高一点;但是如果数据量很小,那直接全表扫描可能效率更高。 

5、like KK% 一般情况都会走索引(索引下推

我们之前有提到过这样的一张表 

-- ALL
EXPLAIN SELECT * FROM employees 
WHERE name > 'LiLei' AND age = 22 AND position ='manager';

-- range (不管数据量大小都会走索引)
EXPLAIN SELECT * FROM employees 
WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees_copy 
WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

这里主要是因为发生了索引下推!

索引下推(Index Condition Pushdown,ICP), 是MySQL5.6版本之后才有的东西。like KK%其实就是用到了索引下推优化。

索引下推

什么是索引下推?

对于辅助的联合索引(name,age,position),正常情况按照最左前缀原则

SELECT * FROM employees 
WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和position是无序的,无法很好的利用索引。

MySQL5.6之前的版本,这个查询只能在联合索引里匹配到名字是 'LiLei' 开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再比对age和position这两个字段的值是否符合。

MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数!!!

使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 'LiLei' 开头的索引之后,同时还会在索引里过滤age和position这两个字段,拿着过滤完剩下的索引对应的主键id再回表查整行数据。

所以这里的key_len是140,表示这3个字段的索引都有用到!!!

索引下推会减少回表次数,对于innodb引擎的表索引下推只能用于二级索引,innodb的主键索引(聚簇索引)树叶子节点上保存的是全行数据,所以这个时候索引下推并不会起到减少查询全行数据的效果。

为什么范围查找(大于号)MySQL没有用索引下推优化?

估计应该是MySQL认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里MySQL选择给 like KK% 用了索引下推优化(不管数据量是大还是小!)

MySQL如何选择合适的索引 

我们先来看看下面的这几条SQL

-- 执行时间 0.121s   ALL
EXPLAIN SELECT * FROM employees WHERE name > 'a';

-- 执行时间 0.070s   range 
EXPLAIN SELECT name,age,position FROM employees WHERE name > 'a';

如果用name索引需要遍历name字段联合索引树,然后还需要根据遍历出来的主键值(如果符合条件的值很多),再去主键索引树里再去查出最终数据。这样做的成本比全表扫描还高,可以用覆盖索引优化,这样只需要遍历name字段的联合索引树就能拿到所有结果。

从两者的执行时间上来看,也可以发现走覆盖索引的效率要高得多!

再看看这一条SQL

不难发现它与第一条SQL相比,只是判断条件从“ name > 'a' ” 变成了 “ name > 'z' ”,但是后者居然就可以走索引了!

-- 执行时间 0.021s   range 
EXPLAIN SELECT * FROM employees WHERE name > 'zzz';

对于上面这两种 name>'a' 和 name>'zzz' 的执行结果,mysql最终如何选择索引,我们可以用trace工具来查看!

trace工具用法

我们先要开启trace

-- 开启trace
set session optimizer_trace="enabled=on",end_markers_in_json=on;  

-- 关闭trace
set session optimizer_trace="enabled=off";    

将下面的两条语句选中,一起执行!

select * from employees where name > 'a' order by position;
SELECT * FROM information_schema.OPTIMIZER_TRACE;

使用trace工具,我们可以清晰的看到SQL执行的三个阶段。为了方便阅读,我们将这一整个JSON拆成3个片段。

第一阶段:SQL准备阶段,格式化sql

第二阶段:SQL优化阶段

第三阶段:SQL执行阶段 

第一阶段:SQL准备阶段,格式化sql

"join_preparation": {      // 第一阶段:SQL准备阶段,格式化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 */

第二阶段:SQL优化阶段 

"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": 99977,          // 扫描行数
							"cost": 10104.1         // 查询成本
						} /* 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 */
							}
						] /* 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": "idx_name_age_position",
									"usable": false,
									"cause": "query_references_nonkey_column"
								}
							] /* potential_skip_scan_indexes */
						} /* skip_scan_range */,
						"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,                 // 是否使用覆盖索引
									"in_memory": 1,               
									"rows": 49988,                       // 索引扫描行数
									"cost": 17496.1,                     // 索引使用成本
									"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": 99977,
								"access_type": "scan",             // 访问类型:为scan,全表扫描
								"resulting_rows": 99977,
								"cost": 10102,
								"chosen": true,                    // 
								"use_tmp_table": true
							}
						] /* considered_access_paths */
					} /* best_access_path */,
					"condition_filtering_pct": 100,
					"rows_for_plan": 99977,
					"cost_for_plan": 10102,
					"sort_cost": 99977,
					"new_cost_for_plan": 110079,
					"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 */
		},
		{
			"optimizing_distinct_group_by_order_by": {
				"simplifying_order_by": {
					"original_clause": "`employees`.`position`",
					"items": [
						{
							"item": "`employees`.`position`"
						}
					] /* items */,
					"resulting_clause_is_simple": true,
					"resulting_clause": "`employees`.`position`"
				} /* simplifying_order_by */
			} /* optimizing_distinct_group_by_order_by */
		},
		{
			"reconsidering_access_paths_for_index_ordering": {
				"clause": "ORDER BY",
				"steps": [
				] /* steps */,
				"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 */
		},
		{
			"finalizing_table_conditions": [
				{
					"table": "`employees`",
					"original_table_condition": "(`employees`.`name` > 'a')",
					"final_table_condition   ": "(`employees`.`name` > 'a')"
				}
			] /* finalizing_table_conditions */
		},
		{
			"refine_plan": [
				{
					"table": "`employees`"
				}
			] /* refine_plan */
		},
		{
			"considering_tmp_tables": [
				{
					"adding_sort_to_table": "employees"
				} /* filesort */
			] /* considering_tmp_tables */
		}
	] /* steps */
} /* join_optimization */

第三阶段:SQL执行阶段

"join_execution": {
	"select#": 1,
	"steps": [
		{
			"sorting_table": "employees",
			"filesort_information": [
				{
					"direction": "asc",
					"expression": "`employees`.`position`"
				}
			] /* filesort_information */,
			"filesort_priority_queue_optimization": {
				"usable": false,
				"cause": "not applicable (no LIMIT)"
			} /* filesort_priority_queue_optimization */,
			"filesort_execution": [
			] /* filesort_execution */,
			"filesort_summary": {
				"memory_available": 262144,
				"key_size": 40,
				"row_size": 190,
				"max_rows_per_buffer": 1379,
				"num_rows_estimate": 99977,
				"num_rows_found": 100003,
				"num_initial_chunks_spilled_to_disk": 35,
				"peak_memory_used": 271736,
				"sort_algorithm": "std::stable_sort",
				"sort_mode": "<fixed_sort_key, packed_additional_fields>"
			} /* filesort_summary */
		}
	] /* steps */
} /* join_execution */

所以我们这边得出的结论:

全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描  

通过上面的JSON数据,我们就可以知道MySQL底层计算出来的策略。

有一些SQL语句,我们可能觉得它应该要走索引,但是实际上并没有,我们就可以通过trace,这个工具来看看SQL底层的执行逻辑,从而对其做出优化策略!

Order by与Group by优化(重点!!!)

 

 

 

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

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

相关文章

1024程序员节带你玩转图片Exif信息获取之JavaScript

目录 一、前言 二、背景 三、Exif.js 1、Exif.js 简介 2、Exif.js 引入 四、多场景展示数据获取 1、原始图片直接获取 2、base64 编码文件加载 3、文件上传的方式加载 五、总结 一、前言 1024是2的十次方&#xff0c;二进制计数的基本计量单位之一。1G1024M&#xff0c;而…

git工具基本操作命令

初始化 首先在某个文件下新建一个项目。然后使用git初始化命令开始正式管理写好的代码。 首先新建一个项目&#xff1a; 然后在上述文件夹中右键选择git&#xff0c;或者直接在该文件路径下打开cmd进行操作&#xff1a; 上述操作出现了.git文件夹&#xff0c;今后所有的操作都…

Sharding-JDBC实现读写分离

前言 快一个月没有更新文章了&#xff0c;太忙了太忙了&#xff0c;虽然慢了一点&#xff0c;但是我肯定不会断更。上一篇文章是《Mysql主从复制》&#xff0c;光是数据库层面的主从复制可不行&#xff0c;应用层面也是需要读写分离的&#xff0c;所以接上一篇文章我们来讲如何…

赶紧进来看看---万字博客详解C/C++中的动态内存管理

本篇博客主要介绍了C/C程序内部的内存开辟.动态内存分布 动态内存函数malloc calloc realloc free的使用 常见的动态内存错误.以及柔性数组的概念与使用 学会动态内存管理将不再局限于使用静态的空间,对内存空间的理解和使用将更进一层楼~ C/C动态内存管理一.认识C/C程序的内存…

【C++升级之路】类与对象(中)

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【C学习与应用】 ✒️✒️本篇内容&#xff1a;类与对象知识汇总&#xff0c;包括6大默认成员函数、日期类的实现 &#x1f6a2;&#x1f6a2;作者简介&…

graphQL入门分享

是什么 一种用于 API 的查询语言&#xff1b;它与特定技术无关&#xff0c;你可以用任何语言实现它 简单理解&#xff0c;他能提供一个接口&#xff0c;让我们来调用&#xff0c;只是返回的数据格式更多是由我们前端来控制 为什么 官网&#xff1a;https://graphql.cn/ 1.请求你…

深入理解计算机系统前篇总结

&#x1f343;博主昵称&#xff1a;一拳必胜客 博主主页面链接&#xff1a;博主主页传送门 博主专栏页面连接&#xff1a;专栏传送门–计算机考研 &#x1f351;创作初心&#xff1a;本博客的初心是每天分享记录自己学习的脚步&#xff0c;和各位技术友探讨交流&#xff0c;同时…

惠州龙门大米飘香 国稻种芯-中国水稻节:广东乡村振兴样板

惠州龙门大米飘香 国稻种芯-中国水稻节&#xff1a;广东乡村振兴样板 人民日报客户端 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#xff1a; 粒粒“龙门大米”精美飘…

百度地图API的使用(附案例)

文章目录JavaScript API GL一、申请秘钥Hello World显示地址案例定位功能步行导航搜索功能地铁路线规划JavaScript API GL 百度地图JavaScript API是一套由JavaScript语言编写的应用程序接口&#xff0c;可帮助您在网站中构建功能丰富、交互性强的地图应用&#xff0c;支持PC端…

经典文献阅读之--用于自动驾驶的高清地图生成技术

0. 简介 这篇文章我们介绍一下论文“High-Definition Map Generation Technologies For Autonomous Driving: A Review“&#xff0c;2022年6月11日&#xff0c;来自加拿大Ontario Tech University。相较于网上的其他文章&#xff0c;本文更在意向读者更轻松全面的了解文章中介…

【Web前端大作业】基于HTML+CSS+JavaScript制作西北大学新闻网站(7页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

悬浮坐标解决方案:如何在图片获取xy鼠标位置和增加标注信息

悬浮坐标的定义&#xff0c;基于固定分辨率的图片&#xff0c;通过获取该图片x和y坐标确定位置后并添加标注&#xff0c;实现位置展示、对应图片内物品展示的一种标注开发方式。 技术要点 自动获取图片x和y坐标&#xff1b;将多个坐标xy在图片上通过CSS定位的方式予以展示&am…

pytorch学习(三)——模型层

文章目录1. 自定义模型层2. 使用预训练模型3. 模型构建风格3.1 使用 add_module 方法3.2 添加进 Sequential3.3 Sequential作为模型容器3.4 ModuleList作为模型容器3.5 ModuleDict作为模型容器当我们构建了数据管道能够将数据一个batch一个batch的取出来后&#xff0c;下一步就…

微信小程序函数处理之保姆级讲解

目录 生命周期函数 生命周期函数的调用过程 页面事件函数 页面路由管理 自定义函数 setData设值函数 生命周期函数 在使用Page&#xff08;&#xff09;构造器注册页面时&#xff0c;需要使用生命周期函数&#xff0c;包括onLoad&#xff08;&#xff09;页面加载时生命周…

硬件工程师成长之路(10.1)——芯片选型

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录前言一、电机驱动类1 、直流电机驱动芯片2、步进电机③、资料前言 送给大学毕业后找不到奋斗方向的你&#xff08;每周…

【车间调度】基于全球邻域和爬坡来优化模糊柔性作业车间调度问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

VS Studio 搭建跨平台开发环境

VS Studio 搭建跨平台开发环境 增加VS的工作负载 打开Visual Studio Installer 安装器&#xff0c;点击修改 在这个界面找到Linux开发环境&#xff0c;勾上然后在点击右下角的修改等待安装。我的是因为已经有了所以下面那里显示的是关闭&#xff0c;没有的是显示的修改 等待安…

LabVIEW强制重新安装无法运行或损坏的NI软件

LabVIEW强制重新安装无法运行或损坏的NI软件 可以参考附件的录像说明。LabVIEW强制重新安装无法运行或损坏的NI软件 - 北京瀚文网星科技有限公司 (bjcyck.com) 某些NI软件&#xff0c;工具包或驱动程序已损坏&#xff0c;损坏或无法按预期运行&#xff0c;想尝试重新安装以进…

【ArchSummit】众安金融微服务架构演进实战

前言 &#x1f4eb; 作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &a…

网络原理——传输层_UDP

JavaEE传送门JavaEE JavaEE——No.2 套接字编程(TCP) JavaEE——网络原理_应用层 目录传输层UDP传输层 端到端之间的传输, 重点关注的是起点和终点 核心的协议有两个: UDP: 无连接, 不可靠传输,面向数据报, 全双工 TCP: 有链接, 可靠传输, 面向字节流, 全双工 UDP UDP协议…