Mysql 优化
🎐【一、explain】分析 SQL#
explain 中,包含了如下几个字段(不同版本可能会有所差异):
字段 | 含义 |
---|---|
id | select 查询的序列号,是一组数字,表示的是查询中执行 select 子句或者是操作表的顺序。 |
select_type | 表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等 |
table | 输出结果集的表 |
partitions | 查询时匹配到的分区信息,对于非分区表值为 NULL,当查询的是分区表时,partitions 显示分区表命中的分区情况。 |
type | 表示表的连接类型,性能由好到差的连接类型为 (system —> const —–> eq_ref ——> ref ——-> ref_or_null—-> index_merge —> index_subquery —–> range —–> index ——> all ) |
possible_keys | 表示查询时,可能使用的索引 |
key | 表示查询时,实际使用的索引 |
key_len | 索引字段的长度,可用来区分长短索引 |
rows | 扫描行的数量 |
filtered | 表里符合条件的记录数所占的百分比 |
extra | 执行情况的说明和描述 |
看完是不是很懵,感觉好多要记忆的,别着急,下边我们通过实际案例,来加深记忆
id#
id 字段是 select 查询的序列号,是一组数字,表示的是查询中执行 select 子句或者是操作表的顺序。
id 情况有三种 :
- 此处只是单表查询,id 只有一个
- id 一样,则从上到下
- id 不同,则 id 值越大,优先级越高
此处是嵌套子查询,最内部的子查询,自然是最先执行的
简而言之:#
- id 值越大,优先级越高;
- id 值一样,则从上到下;
select_type#
SELECT_TYPE | 含义 |
---|---|
SIMPLE | 简单的 select 查询,查询中不包含子查询或者 UNION |
PRIMARY | 查询中若包含任何复杂的子查询,最外层查询标记为该标识 |
SUBQUERY | 在 SELECT 或 WHERE 列表中包含了子查询 |
DERIVED | 在 FROM 列表中包含的子查询,被标记为 DERIVED(衍生) MYSQL 会递归执行这些子查询,把结果放在临时表中 |
UNION | 若第二个 SELECT 出现在 UNION 之后,则标记为 UNION ; 若 UNION 包含在 FROM 子句的子查询中,外层 SELECT 将被标记为 : DERIVED |
UNION RESULT | 从 UNION 表获取结果的 SELECT |
PRIMARY,SUBQUERY#
DERIVED (需要临时表,自然比上述效率低)#
type#
TYPE | 含义 |
---|---|
NULL | MySQL 不访问任何表,索引,直接返回结果 |
system | 表只有一行记录 (等于系统表),这是 const 类型的特例,一般不会出现 |
const | 表示通过索引一次就找到了,const 常用于 primary key 或者 unique 索引 (本质上都是唯一索引)。因为只匹配一行数据,所以很快。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。const 于将 “主键” 或 “唯一” 索引的所有部分与常量值进行比较 |
eq_ref | 类似 ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描 |
ref | 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个) |
range | 只检索给定返回的行,使用一个索引来选择行。 where 之后出现 between , <,> , in 等操作。 |
index | index 与 ALL 的区别为 index 类型只是遍历了索引树, 通常比 ALL 快, ALL 是遍历数据文件。 |
all | 将遍历全表以找到匹配的行 |
结果值从最好到最坏以此是:
NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system > const > eq_ref > ref > range > index > ALL
一般至少要达到 range 级别,最好达到 ref 。
const#
唯一索引,非关联查询
eq_ref,ref#
eq_ref 跟 const 的区别是:两者都利用唯一索引,但前者是关联查询,后者只是普通查询?
eq_ref 跟 ref 的区别:后者是非唯一索引
index,all#
都是读全表,区别在于 index 是遍历索引树读取,而 ALL 是从硬盘中读取。
不走索引就会遍历全表
possible_keys,key#
possible_keys : 显示可能应用在这张表的索引, 一个或多个。
key :实际使用的索引, 如果为 NULL, 则没有使用索引。(可能是没有走索引,需要分析)
key_len : 表示索引中使用的字节数, 在不损失精确性的前提下, 长度越短越好 。
- 单列索引,那么需要将整个索引长度算进去;
- 多列索引,不是所有列都能用到,需要计算查询中实际用到的列。
ref#
显示索引的哪一列被使用了,如果可能的话,是一个常数。
- 当使用常量等值查询,显示 const
- 当关联查询时,会显示相应关联表的关联字段
- 如果查询条件使用了表达式、函数,或者条件列发生内部隐式转换,可能显示为 func
- 其他情况为 null
- id 是索引,而且是 id=1,一个常数,故 ref = const
- user_id 不是索引,ref 直接为 null
t1.id 是索引,且 = 号后边不是常量,故显示 t1.id,即显示相应关联表的关联字段
rows#
扫描行的数量,一般越小越好
- 用索引 rows 就为 1,无论是唯一索引还是非唯一索引
- 其他情况一般是全表扫描,rows 等于表的行数。
filtered#
表里符合条件的记录数的所占的百分比。
extra#
其他的额外的执行计划信息,在该列展示 ,需要把前两个优化为 using index。
EXTRA | 含义 |
---|---|
using filesort | 说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,表示无法利用索引完成的排序操作, 称为 “文件排序”, 效率低。 |
using temporary | 使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于 order by 和 group by; 效率低 |
using index | 表示相应的 select 操作使用了覆盖索引, 直接从索引中过滤掉不需要的结果,无需回表, 效率不错。 |
using index condition | 索引下推!!查找使用了索引,但是需要回表查询数据,此时就是因为索引列没有完全包含查询列 |
具体 using index condition 中的索引下推是什么意思,可以参考这篇 索引的原理 && 设计原则
using where#
不同版本好像不一样
5.7:表示 MySQL 首先从数据表(存储引擎)中读取记录,返回给 MySQL 的 server 层,然后在 server 层过滤掉不满足条件的记录,即无法直接在存储引擎过滤掉。
简单来说,就是查询时 where 中用的不是索引。
🎐【二、索引失效】的几个场景#
0. SQL 准备#
create table `tb_seller` (
`sellerid` varchar (100),
`name` varchar (100),
`nickname` varchar (50),
`password` varchar (60),
`status` varchar (1),
`address` varchar (100),
`createtime` datetime,
primary key(`sellerid`)
)engine=innodb default charset=utf8mb4;
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑马程序员','黑马程序员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
-- 创建联合索引
create index idx_seller_name_sta_addr on tb_seller(name,status,address);
1. 不满足最左前缀#
所谓最左前缀,可以想象成一个爬楼梯的过程,假设我们有一个复合索引:name,status,address,那这个楼梯由低到高依次顺序是:name,status,address,最左前缀,要求我们不能出现跳跃楼梯的情况,否则会导致我们的索引失效:
- 按楼梯从低到高,无出现跳跃的情况–此时符合最左前缀原则,索引不会失效
- 出现跳跃的情况
- 直接第一层 name 都不走,当然都失效
- 走了第一层,但是后续直接第三层,只有出现跳跃情况前的不会失效(此处就只有 name 成功)
- 同时,这个顺序并不是由我们 where 中的排列顺序决定,比如:
- where name=’小米科技’ and status=’1’ and address=’北京市’
- where status=’1’ and name=’小米科技’ and address=’北京市’
这两个尽管 where 中字段的顺序不一样,第二个看起来越级了,但实际上效果是一样的
其实是因为我们 MySQL 有一个 Optimizer(查询优化器),查询优化器会将 SQL 进行优化,选择最优的查询计划来执行。
2. 范围查询之后#
范围查询之后的索引字段,会失效!!!但本身用来范围查询的那个索引字段依然有效,如图中的 status。
- 而图中 address 失效了,对比一下长度便可看出来。
3. 索引字段做运算#
对索引字段做运算,使用函数等都会导致索引失效。
4. 字符串不加’ ‘#
索引字段为字符串类型,由于在查询时,没有对字符串加单引号,MySQL 的查询优化器,会自动的进行类型转换,造成索引失效。
5. 避免 select *#
危害#
- 消耗更多的 CPU 和 IO 以网络带宽资源
- 可减少表结构变更带来的影响
- 无法使用覆盖索引
🎈覆盖索引#
尽量使用覆盖索引(索引列完全包含查询列),减少 select *
当查询列中包含了非索引项,虽然我们还是能够利用到索引,但是为了获取非索引项字段,我们需要回表去查询数据,效率会比较低。
6. or 分割开的条件#
用 or 分割开的条件, 如果 or 前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
示例,name 字段是索引列 , 而 createtime 不是索引列,中间是 or 进行连接是不走索引的 :
- 因为有一个不走索引,又是 or 条件,两个都要判断一下,相当于不管如何,都还是得去走全表查询,没有利用到索引。
explain select * from tb_seller where name='黑马程序员' or createtime = '2088-01-01 12:00:00'\G;
7. 以 % 开头的 Like 模糊查询#
可以联系字典树 Trie 的匹配吧。
比如要找‘abc’,如果是 % bc,一开始的根都找不到了,自然没办法利用到索引树
而如果是 ab%,还能利用到前两个。
% 开头的失效,% 结尾的还能利用索引(实际上这里就相当于字符串的最左前缀原则,可以这么理解)
解决方法:使用覆盖索引#
当真的需要两边都使用 % 来模糊查询时,只有当 作为模糊查询的条件字段(例子中的 name)以及 想要查询出来的数据字段(例子中的 name & status & address)都在索引列上时,才能真正使用索引。
关于覆盖索引,可以参考这篇 -> 索引原理,设计原则
8. MySQL 认为全表更快#
此处是由于数据的特殊性,‘北京市’所占的比例很高,还不如全表扫描
8.1 is null 和 is not null#
本质上跟上边是一样的
MySQL 底层会自动判断,如果全表扫描快,则直接使用全表扫描,不走索引。如果表中该索引列数据绝大多数是非空值,则使用 is not null 的时候走索引,使用 is null 的时候不走索引(还不如全表扫描快),全表扫描;反之亦然。
如果表中 is null 的比较多,那自然就直接全表扫描,如果 is null 的很少,会走索引。
8.2 in 和 not in#
为了方便测试,我们单独建了一个 status 索引,观察该表数据,status 中 2 很少,而 1 很多。
所以 in (‘1’) 的话,不如走全表,没有用到索引
in (‘2’) 就会走索引
总结#
我们建立索引的时候,对于数据分布均匀且重复的字段,我们一般不考虑对其添加索引,因为此时 MySQL 会认为全表更快,会走全表扫描而非索引,导致我们的索引失效。
9. != 或者 <>#
使用不等式也会导致索引失效
相关习题#
说完几个索引失效的场景,下边呢,是我们具体的应用场景,在如下几种特定情况下,我们需要采取不同的 SQL 优化方式,或采用索引,或利用外部条件
🎎【三、优化场景】1. 大批量插入数据#
环境准备#
CREATE TABLE `tb_user_2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(45) NOT NULL,
`password` varchar(96) NOT NULL,
`name` varchar(45) NOT NULL,
`birthday` datetime DEFAULT NULL,
`sex` char(1) DEFAULT NULL,
`email` varchar(45) DEFAULT NULL,
`phone` varchar(45) DEFAULT NULL,
`qq` varchar(32) DEFAULT NULL,
`status` varchar(32) NOT NULL COMMENT '用户状态',
`create_time` datetime NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_username` (`username`) -- 唯一性约束
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
load 命令#
适当的设置可以提高导入的效率。
对于 InnoDB 类型的表,有以下几种方式可以提高导入的效率:
1) 主键顺序插入#
因为 InnoDB 类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果 InnoDB 表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。
脚本文件介绍 :
sql1.log —-> 主键有序
sql2.log —-> 主键无序
插入主键顺序排列数据:
主键无序:
出现了权限问题#
执行:set global local_infile=on;
但又出现了另一个问题:#
其实我们开启之后, 需要退出重新连接,再次连接时便可以正常操作了
- 如果还是不行的话,连接的时候可以这样连接:
mysql --local_infile=1 -u root -ppassword
2)关闭唯一性校验#
在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行 SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。
🎪2. order by 排序#
环境准备#
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`age` int(3) NOT NULL,
`salary` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into `emp` (`id`, `name`, `age`, `salary`) values('1','Tom','25','2300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('2','Jerry','30','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('3','Luci','25','2800');
insert into `emp` (`id`, `name`, `age`, `salary`) values('4','Jay','36','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('5','Tom2','21','2200');
insert into `emp` (`id`, `name`, `age`, `salary`) values('6','Jerry2','31','3300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('7','Luci2','26','2700');
insert into `emp` (`id`, `name`, `age`, `salary`) values('8','Jay2','33','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('9','Tom3','23','2400');
insert into `emp` (`id`, `name`, `age`, `salary`) values('10','Jerry3','32','3100');
insert into `emp` (`id`, `name`, `age`, `salary`) values('11','Luci3','26','2900');
insert into `emp` (`id`, `name`, `age`, `salary`) values('12','Jay3','37','4500');
create index idx_emp_age_salary on emp(age,salary);
两种排序方式#
using index#
直接能在索引列完成查询,无需回表,关于回表查询,可以参考 这篇文章 ,此时需要保证所查询的字段都是索引字段,才会是 using index
但这个不太现实,不可能说我们要查的,都是索引的字段,所以很多情况下,我们并没有办法把 using filesort 优化为 using index,只能退而求其次,尽量从 filesort 的角度去优化,通过外部条件。
" class="reference-link">🎑using filesort
#
何时会出现:#
- order by 的字段不是索引
- order by 字段是索引字段,但是 select 中没有使用覆盖索引
- order by 中同时存在 ASC 升序排序和 DESC 降序排序
- order by 中用到的是复合索引,但没有保持复合索引中字段的先后顺序(即违背了最左前缀原则)
比如图中的 select id,age,salary from emp order by salary,age;
为什么呢?这里我们得回顾一下复合索引是如何存储的,比如:我们建立一个复合索引 (name,status,address),索引中也是按这个字段来存储的,类似图中表格这样:
复合索引树 (只存储索引列和主键用于回表),而且是先按 name 排序,name 相同了再按 status 排序,以此类推
name | status | address | id (主键) |
---|---|---|---|
小米 1 | 0 | 1 | 1 |
小米 2 | 1 | 1 | 2 |
所以如果我们不按照索引的先后顺序来 order by 的话,就跟索引树中的排序规则不一样了,索引此时排好的序,我们都没办法合理利用到,自然 MySQL 不会去走索引了。
🎏Filesort 的优化#
两种扫描算法#
对于 Filesort , MySQL 有两种排序算法:
以这条 SQL 语句为例,我们来看看他是怎么执行的:
select * from emp where age=1 order by salary;
1) 两次扫描算法 :MySQL4.1 之前,使用该方式排序。
①首先根据 where 条件,过滤得到相应的满足 age=1 的 salary,取出排序字段 salary 和对应的行指针信息 (用于回表),然后在排序区 sort buffer 中排序,如果 sort buffer 不够,则在临时表 temporary table 中存储排序结果。
②完成排序之后,再根据行指针回表读取所有字段,而次该操作可能会导致大量随机 I/O 操作,是我们需要改进的地方。
这就是所谓的两次扫描,第一次扫描,我们拿到的只是排序字段,然后在 sort buffer 排好序;第二次扫描,才去回表读取所有字段,最终返回。
该如何优化呢?为什么要分成两次,有没有一种可能是空间不够呢?那我们如果有足够的空间,以空间换时间,是不是就可以开辟出一种新的方法,只需要一次扫描即可
2)一次扫描算法:一次性取出满足条件的所有字段,然后在排序区 sort buffer 中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法要高,典型的以空间换时间的思想。
具体使用哪种算法呢?
MySQL 通过比较系统变量 max_length_for_sort_data 的大小和 Query 语句取出的字段总大小, 来判定使用那种排序算法,如果 max_length_for_sort_data 更大,那么使用一次扫描算法;否则使用两次扫描算法。
优化方案#
① 增大前者 max_length_for_sort_data
可以适当 max_length_for_sort_data 系统变量,来增大排序区的大小,提高排序的效率,这是典型的空间换时间的思想。
② 减小后者 Query 语句取出的字段总大小
如果内存实在不够富裕的话,我们可以减少查询的字段,避免 select *
③ 提高 sort_buffer_size :
由上文可知,通过增大该参数,可以让 MySQL 尽量减少在排序过程中对须要排序的数据进行分段,避免需要使用到临时表 temporary table 来存储排序结果,再把多次的排序结果串联起来。
可惜,MySQL 无法查看它用了哪个算法。如果增加了 max_Length_for_sort_data 变量的值,磁盘使用率上升了,CPU 使用率下降了,并且 Sort_merge_passes 状态变量相对于修改之前开始很快地上升,也许是 MySQL 强制让很多的排序使用了一次扫描算法。
具体的实战修改过程,需要结合 MySQL 中另一个工具–trace 分析优化器,来分析执行计划,后续有机会,我们再来详细聊一聊!
3. group by 分组#
由于 GROUP BY 实际上也同样会进行排序操作,而且与 ORDER BY 相比,GROUP BY 主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在 GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。
1. 使用索引#
先来看看无索引的情况:using temporary;using filesort
创建索引#
create index idx_emp_age_salary on emp(age,salary);
2. 加上 order by null 禁止排序#
如果查询包含 group by 但是用户想要避免排序结果的消耗, 则可以执行 order by null 禁止排序。如下 :
3. 需要排序 (则跟 order by 的优化大体相同)#
4. 优化子查询#
Mysql4.1 版本之后,开始支持 SQL 的子查询。这个技术可以使用 SELECT 语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的 SQL 操作,同时也可以避免事务或者表锁死,并且写起来也很容易。
但是,有些情况下,子查询是可以被更高效的连接(JOIN)替代的!!
示例 ,查找有角色的所有的用户信息 :
explain select * from t_user where id in (select user_id from user_role );
执行计划为 :
优化后 :
explain select * from t_user u , user_role ur where u.id = ur.user_id;
连接 (Join) 查询之所以更有效率一些 ,是因为 MySQL 不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。
5. 优化 OR 条件#
对于包含 OR 的查询子句,如果要利用索引,则 OR 之间的每个条件列都必须用到索引 , 而且不能使用到复合索引; 如果没有索引,则应该考虑增加索引。
我们此处有一个 id 主键索引,和一个 age,salary 复合索引:
单列 + 复合中的某一个#
explain select * from emp where id = 1 or age = 30;
单列 + 单列(两个一样)#
实际上等效于 range,此处只是提供一个示例
解决:使用 union 优化!!!#
优化前#
- type:index_merge
优化后#
- type:一个是 const,一个是 ref,都比 index 快
6. 使用 SQL 提示#
SQL 提示,是优化数据库的一个重要手段,简单 来说,就是在 SQL 语句中加入一些人为的提示来达到优化操作的目的。
use index#
在查询语句中表名的后面,添加 use index 来提供希望 MySQL 去参考的索引列表,就可以让 MySQL 不再考虑其他可用的索引。
ingore index#
如果用户只是单纯的想让 MySQL 忽略一个或者多个索引,则可以使用 ignore index 作为 hint 。
force index#
强制走索引,即使 MySQL 认为全表更快,我们用 force 也可以强制走索引。
跟 use 的区别#
- use 只是提供一个参考,具体用不用还得看 MySQL 的优化器怎么想的
✨7. 优化 limit 分页#
一个常见又非常头疼的问题就是 limit 2000000,10 ,此时需要 MySQL 排序 前 2000010 记录,仅仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。
比如我们有这样一条语句,select * from tb_item limit 2000000,10 ;
此时默认是根据 id 排序的。
优化思路一#
在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
优化思路二#
该方案适用于主键自增的表,可以把 Limit 查询转换成某个位置的查询 。(局限性:主键不能断层)
- 如果要根据其他字段来排序的话,此方法就无法做到了。
8. 优化 insert 操作#
一次连接,多次插入#
比如我们需要插入三条数据:
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
此时需要建立三次连接,每次连接都要消耗资源,为了提高单次连接的执行效率,我们会采取:
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
同时,insert 的时候最好是保持数据的有序插入
🎊总结#
explain 分析 SQL 中,其中比较重要的主要是 type,key,ref 以及 extra,我们不需要死记硬背,多拿几条语句去 explain 比对比对,更有利于我们辅助记忆。
索引失效的几个场景,借用 b 站热评:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
Like 百分写最右,覆盖索引不写星;
不等空值还有 or,索引失效要少用;
VAR 引号不可丢,SQL 高级也不难!
- 优化基本原则:巧用索引,减少连接次数。
最后,能看到这里真的是很不容易了,其实这篇文章,相比上篇,更多还是以黑马课程的思路来整理的,自己只是在其中补充和完善了一下小案例,指正了小错误,受众可能更偏向于 b 站视频用户。
当然了,这也只是初级阶段,跟着视频学,整理笔记,初级阶段过后呢,就需要在此基础上,去对实际项目中的 SQL 去优化,优化过后还需要深入理解如此优化的原理,本文的 order by,还没有过多深入其底层原理,只是大概提及到了有 sort_buffer 这么个东西,以及对应的两种扫描算法,order by 底层到底是如何实现的,怎么通过本文的方法,去优化诸如以下这种语句:
select * from table order by xxx;
这个 xxx,不管是不是索引,按这样去查询的话,大概率都是 using filesort 且不会用到索引的,除非说我们去 limit xx,这个 xx 还要很小,才会使用到索引。
这些,才是我们更进一步的底梁柱,笔记大家都有,只是谁整理得好看一点,多了一些自己的思考罢了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: