14.MySQL 优化

2023-12-13 15:31:36

目录

概述

????????优化方式:

查看sql执行频率

定位 低效率执行的SQL

慢查询日志

show processlist

Explain 分析执行计划

Explain 之 id

Explain 之 select_type

select_type的取值

?Explain 之 type

type的取值

Explain 之 其他指标字段

Explain 之 table

Explain 之 rows

Explain 之 key

Explain 之 extra

show profile 分析SQL

了解SQL执行的过程

1、我们可以执行一系列的操作

2、执行完上述命令之后,再执行show profiles 指令, 来查看SQL语句执行的耗时:

3、通过show? profile for? query? query_id 语句可以查看到该SQL执行过程中每个线程的状态和消耗的时间:

4、在获取到最消耗时间的线程状态后,MySQL支持进一步选择all、cpu、block io 、context switch、page faults等明细类型类查看MySQL在使用什么资源上耗费了过高的时间。例如,选择查看CPU的耗费时间? :

trace分析优化器执行计划

执行步骤

优化

索引优化

避免索引失效应用

全值匹配

最左前缀法则

其他匹配原则 -- 1

其他匹配原则 -- 2

大批量数据加载优化

1、?主键顺序插入

2、关闭唯一性校验

DML优化

优化 insert 语句

DQL优化

优化 order by 语句

子查询 优化

优化 limit 语句


概述

????????优化方式:

????????????????分别从 设计、查询、索引、存储 上优化

查看sql执行频率

-- SQL的优化
insert into account values(3,'wangwu',1000);
-- 查看当前会话SQL执行类型的统计信息
show session status like 'Com______';
-- 查看全局(自从上次MySQL启动至今)执行类型的统计信息
show global status like 'Com______';
-- 查看针对innodb引擎的统计信息
show status like 'Innodb_rows_%';

定位 低效率执行的SQL

慢查询日志

通过慢查询日志定位执行效率低的SQL语句

-- 查看慢日志配置信息
show variables like '%slow_query_log%';
-- 开启慢日志查询
set global slow_query_log=1;
-- 查看慢日志记录sql的最低阈值时间  默认执行时间>=10s,就会被记录在慢日志中
show variables like 'long_query_log%';
-- 修改慢日志记录sql的最低阈值时间,修改的是本会话的最低阈值时间,如果想修改全局,修改ini文件
set global long_query_log=4;

show processlist

查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时的查看sql的执行情况,同时对一些锁表操作进行优化

Explain 分析执行计划

Explain 之 id

  • id字段是select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。id情况有三种:
    • id相同表示加载表的顺序是从上到下
    • id不同id值越大优先级越高越先被执行
    • id有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中,id的值越大,优先级越高,越先执行。
-- Explain 之 id
-- id相同表示加载表的顺序是从上到下。
explain select * from user u, user_role ur, role r where u.uid = ur.uid and ur.rid = r.rid ;

-- id不同id值越大,优先级越高,越先被执行。
explain select * from role where rid = (select rid from user_role where uid = (select uid from user where uname = '张飞'));

-- id有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中,id的值越大,优先级越高,越先执行
explain select * from role r , (select * from user_role ur where ur.uid = (select uid from user where uname = '张飞')) t where r.rid = t.rid ;

Explain 之 select_type

select_type的取值

-- Explain 之 select_type

-- simple:没有子查询和union
explain select * from user;

-- primary:主查询,也就是子查询中的最外层查询
explain select * from role where rid = (select rid from user_role where uid = (select uid from user where uname = '张飞'));

-- subquery:在select和where中包含子查询
explain select * from role where rid = (select rid from user_role where uid = (select uid from user where uname = '张飞'));

-- derived:在from中包含子查询,被标记为衍生表
explain select * from (select * from user limit 2) t;

-- union 
-- union result 联合表
explain select * from user where uid = 1 union select * from user where uid = 3;

?Explain 之 type

type显示的是访问类型,是较为重要的一个指标

type的取值

结果从最到最坏以此system > const > eq_ref > ref > range > index > ALL

Explain 之 其他指标字段

Explain 之 table

显示这—步所访问数据库中表名称有时不是真实的表名字,可能是简称,
?

Explain 之 rows

扫描行的数量。
?

Explain 之 key

possible_keys :显示可能应用在这张表的索引,一个或多个。

key :实际使用的索引,如果为NULL,则没有使用索引。

key_len :表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。

Explain 之 extra

其他的额外的执行计划信息,在该列展示。

show profile 分析SQL

  • Mysql5.0.37版本开始增加了对 show profiles show profile 语句的支持。show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了

了解SQL执行的过程

1、我们可以执行一系列的操作

2、执行完上述命令之后,再执行show profiles 指令, 来查看SQL语句执行的耗时

3、通过show? profile for? query? query_id 语句可以查看到该SQL执行过程中每个线程的状态和消耗的时间:

4、在获取到最消耗时间的线程状态后,MySQL支持进一步选择allcpublock io context switchpage faults等明细类型类查看MySQL在使用什么资源上耗费了过高的时间。例如,选择查看CPU的耗费时间? :

trace分析优化器执行计划

  • ??MySQL5.6提供了SQL的跟踪trace, 通过trace文件能够进一步了解为什么优化器选择A计划, 而不是选择B计划

执行步骤

????????1、打开trace , 设置格式为 JSON,并设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。

????????2、执行SQL语句 :

????????3、最后, 检查?information_schema.optimizer_trace?就可以知道MySQL如何执行SQL的 :

优化

索引优化

????????索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL的性能优化问题。

避免索引失效应用

全值匹配

该情况下,索引生效,执行效率高。

-- 创建组合索引 
create index idx_seller_name_sta_addr on tb_seller(name,status,address);

-- 全值匹配 和字段匹配成功即可,与字段无关
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';

explain select * from tb_seller where  status='1' and name='小米科技' and address='北京市';
最左前缀法则

从左边第一个开始,逐个配对

-- 最左前缀法则
 -- 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
explain select * from tb_seller where name='小米科技'; -- 403
?
explain select * from tb_seller where name='小米科技' and status='1'; -- 410
explain select * from tb_seller where  status='1' and name='小米科技'; -- 410
-- 违法最左前缀法则 , 索引失效:
explain select * from tb_seller where status='1'; -- nulll
?
-- 如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:
explain select * from tb_seller where name='小米科技'  and address='北京市'; -- 403
其他匹配原则 -- 1
-- 范围查询(status >'1')右边的列,不能使用索引 。 
explain select * from tb_seller where name='小米科技' and status >'1' and address='北京市’; 

-- 不要在索引列上进行运算操作, 索引将失效。 
explain select * from tb_seller where substring(name,3,2)='科技’ 

-- 字符串不加单引号,造成索引失效。 
explain select * from tb_seller where name='小米科技' and status = 1 ;
-- 1、范围查询右边的列,不能使用索引 。
-- 根据前面的两个字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引。
explain select * from tb_seller where name='小米科技' and status >'1' and address='北京市';
?
-- 2、不要在索引列上进行运算操作, 索引将失效。
explain select * from tb_seller where substring(name,3,2)='科技'
?
-- 3、字符串不加单引号,造成索引失效。 
explain select * from tb_seller where name='小米科技' and status = 1 ;
?
-- 4、尽量使用覆盖索引,避免select *
-- 需要从原表及磁盘上读取数据  -- 效率低
explain select * from tb_seller where name='小米科技'  and address='北京市';  -- 效率低
?
-- 从索引树中就可以查询到所有数据
explain select name from tb_seller where name='小米科技'  and address='北京市';  -- 效率高
explain select name,status,address from tb_seller where name='小米科技'  and address='北京市';  -- 效率高
-- 如果查询列,超出索引列,也会降低性能。
explain select name,status,address,password from tb_seller where name='小米科技'  and address='北京市';  -- 效率低

-- 尽量使用覆盖索引,避免select *
-- 需要从原表及磁盘上读取数据
explain select * from tb_seller where name='小米科技'  and address='北京市';  -- 效率低
?
-- 从索引树中就可以查询到所有数据
explain select name from tb_seller where name='小米科技'  and address='北京市';  -- 效率高
explain select name,status,address from tb_seller where name='小米科技'  and address='北京市';  -- 效率高
-- 如果查询列,超出索引列,也会降低性能。
explain select name,status,address,password from tb_seller where name='小米科技'  and address='北京市';  -- 效率低

-- 用or分割开的条件, 那么涉及的索引都不会被用到。
explain select * from tb_seller where name='黑马程序员' or createtime = '2088-01-01 12:00:00'; 
explain select * from tb_seller where name='黑马程序员' or address = '西安市';  
explain select * from tb_seller where name='黑马程序员' or status = '1';   
?
-- 以%开头的Like模糊查询,索引失效。
explain select * from tb_seller where name like '科技%'; -- 用索引
explain select * from tb_seller where name like '%科技'; -- 不用索引
explain select * from tb_seller where name like '%科技%';-- 不用索引
-- 弥补不足,不用*,使用索引列
explain select name from tb_seller where name like '%科技%';


其他匹配原则 -- 2

-- 1、如果MySQL评估使用索引比全表更慢,则不编辑

-- 2、is NULL , is NOT NULL有时有效,有的索引失效。

-- 3、 in走索引,not in索引失效。

-- 4、单列索引和复合索引,尽量使用复合索引
?

--  1、如果MySQL评估使用索引比全表更慢,则不使用索引。
  -- 这种情况是由数据本身的特点来决定的
create index index_address on tb_seller(address);
?
explain select * from tb_seller where address = '北京市'; -- 没有使用索引
explain select * from tb_seller where address = '西安市'; -- 没有使用索引
?
?
--  2、is  NULL , is NOT NULL  有时有效,有时索引失效。
create index index_address on tb_seller(nickname);
explain select * from tb_seller where nickname is NULL;  -- 索引有效
explain select * from tb_seller where nickname is not NULL; -- 无效

--  3、 in走索引,not in索引失效。--普通索引
explain select * from tb_seller where nickname in('阿里小店', '百度小店');--使用索引
explain select * from tb_seller where nickname not in('阿里小店','百度小店');--不使用索引
--主键索引
explain select * from tb_seller where sellerid in( 'alibaba' , 'baidu'); --使用索引explain select * from tb_seller where sellerid not in( 'alibaba' , 'baidu' ); --使用索引

--  4、单列索引和复合索引,尽量使用复合索引
create index idx_seller_name_sta_addr on tb_seller(name,status,address);
/*
等价于:
    name
    name + status
    name + status + address
*/
--如果一张表有多个单列索引,即使where中都使用了这些索引列,则只有一个最优索引生效
-- 最优索引由优化器决定

大批量数据加载优化

当使用load 命令导入数据的时候,适当的设置可以提高导入的效率。对于 InnoDB 类型的表,有以下几种方式可以提高导入的效率:

1、?主键顺序插入

????????因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。

????????如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。


-- 1、首先,检查一个全局系统变量 'local_infile' 的状态, 如果得到如下显示 Value=OFF,则说明这是不可用的
show global variables like 'local_infile';
?
-- 2、修改local_infile值为on,开启local_infile
set global local_infile=1;
?
-- 3、加载数据 
-- 结论  当通过load向表中加载数据时,尽量保证文件中的主键有序,这样可以提高执行效率
/*
脚本文件介绍 :
    sql1.log  ----> 主键有序
    sql2.log  ----> 主键无序
*/
-- 主键有序 -22.617s
load data local infile 'D:\\sql_data\\sql1.log' into table tb_user fields terminated by ',' lines terminated by '\n';

-- 主键无序 - 81.739s
load data local infile 'D:\\sql_data\\sql2.log' into table tb_user fields terminated by ',' lines terminated by '\n';
2、关闭唯一性校验

????????在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验
????????在导入结束后执行 SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。

-- 关闭唯一性校验
SET UNIQUE_CHECKS=0;
?
truncate table tb_user;
load data local infile 'D:\\sql_data\\sql1.log' into table tb_user fields terminated by ',' lines terminated by '\n';
?
SET UNIQUE_CHECKS=1;

DML优化

优化 insert 语句

1、如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗。使得效率比分开执行的单个insert语句快。

2、在事务中进行数据插入

3、数据有序插入

DQL优化

优化 order by 语句

第一种是通过对返回数据进行排序,也就是通常说的 filesort 排序,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

?第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。?

-- 排序, order by
explain select * from emp order by age; -- Using filesort
explain select * from emp order by age,salary; -- Using filesort

explain select id from emp order by age; -- Using index
explain select id,age from emp order by age; -- Using index
explain select id,age,salary, name from emp order by age;-- Using filesort

-- order by后边的多个排序字段要求尽量排序方式相同
explain select id,age from emp order by age asc, salary desc; -- Using index; Using filesort
explain select id,age from emp order by age desc, salary desc;-- Backward index scan;Using index

-- order by后边的多个排序字段顺序尽量和组合索引字段顺序一致
explain select id,age from emp order by salary,age; -- Using index; Using filesort

Filesort 的优化

通过创建合适的索引,能够减少 Filesort 的出现,但是在某些情况下,条件限制不能让Filesort消失,那就需要加快 Filesort的排序操作。对于Filesort MySQL 有两种排序算法:

1) 两次扫描算法 :MySQL4.1 之前,使用该方式排序。首先根据条件取出排序字段和行指针信息,然后在排序区 sort buffer 中排序,如果sort buffer不够,则在临时表 temporary table 中存储排序结果。完成排序之后,再根据行指针回表读取记录,该操作可能会导致大量随机I/O操作。

2)一次扫描算法:一次性取出满足条件的所有字段,然后在排序区 sort? buffer 中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法要

????????MySQL 通过比较系统变量 max_length_for_sort_data 的大小Query语句取出的字段总大小, 来判定是否那种排序算法,如果max_length_for_sort_data 更大,那么使用第二种优化之后的算法;否则使用第一种。

????????可以适当提高 sort_buffer_size? 和 max_length_for_sort_data? 系统变量,来增大排序区的大小,提高排序的效率

子查询 优化

  • 多表查询的效率高于子查询

优化 limit 语句

文章来源:https://blog.csdn.net/weixin_45963712/article/details/134788039
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。