在应用的开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的SQL语句就成为整个系统性能的瓶颈,因此我们必须要对它们进行优化
MySQL优化方式有很多,大致可以从以下几点来优化MySQL:
MySQL客户端连接成功后,通过show [session|global] status命令可以查看服务器状态,通过查看状态信息可以查看对当前数据库的主要操作类型。
--下面的命令显示了当前session中所有统计参数的值
show session status like 'Com_______'; -- 查看当前会话统计结果
show global status like 'Com_______'; -- 查看数据库上次启动至今统计结果
show status like 'Innodb_rows_%'; --查看针对InnoDB引擎的统计结果
可以通过两种方式定位执行效率较低的SQL语句:
慢查询日志:
--查看慢日志配置信息
show variables like '%slow_query_log%';
--开启慢日志查询
set global slow_query_log = 1;
--查看慢日志记录SQL的最低阈值时间
show variables like 'long_query_time%';
--修改慢日志记录SQL的最低阈值时间
set global long_query_time = 4;
show processlist:
show processlist;
通过定位低效率执行SQL后,可以通过explain命令获取MySQL如何执行select语句的信息,包括在select语句执行过程中表如何连接和连接的顺序
字段 | 含义 |
---|---|
id | select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序 |
select_type | 表示select的类型,常见的取值有SIMPLE(简单表,即不适用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个select)等 |
table | 输出结果集的表 |
type | 表示表的连接类型,性能由好到差的连接类型为(system —> const —> eq_ref —> ref —> ref_or_null —> index_merge —> index_subquery —> range —> index —> all) |
possible_keys | 表示查询时,可能使用的索引,一个或多个 |
key | 表示实际使用的索引,如果为null,则没有使用索引 |
key_len | 索引字段的长度,该值为索引字段最大长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好 |
rows | 扫描行的数量 |
extra | 执行情况的说明和描述 |
Explain-id:
id字段是select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序,id情况有三种:
Explain-select_type:
表示select的类型,常见的取值:
select_type | 含义 |
---|---|
SIMPLE | 简单的select查询,查询中不包含子查询或者union |
PRIMARY | 查询中若包含任何复杂的子查询,最外层查询标记为该标记 |
SUBQUERY | 在select或where列表中包含子查询 |
DERIVED | 在form列表中包含的子查询,被标记为DERIVED (衍生)MySQL会递归执行这些子查询,把结果放入临时表 |
UNION | 若第二个select出现union之后,则标记为union;若union包含在from子句的子查询中,外层select将被标记:DERIVED |
UNION RESULT | 从union表获取结果的select |
Explain-type:
type显示的是访问类型,是较为重要的一个指标:
type | 含义 |
---|---|
null | MySQL不访问任何表,索引,直接返回结果 |
system | 系统表,少量数据,往往不需要进行磁盘i/o;如果5.7及以上版本的话就不是system了,而是all,即便只有一条记录 |
const | 命中主键或者唯一索引;被连接的部分是一个常量值 |
eq_ref | 对于前表的每一行,后表只有一行被扫描;左表有主键,而且左表的每一行数据和右表的每一行刚好匹配(1)join(2)命中主键或者非空索引(3)等值连接 |
ref | 非唯一性索引扫描,返回匹配某个单独值得所有行,对于前表的每一行(row),后表可能有多于一行的数据被扫描;左表是普通索引,和右表匹配可能匹配多行 |
range | 只检查给定返回的行,使用一个索引来选择行,where之后出现between,<,>,in等操作 |
index | 需要扫描索引上的全部数据 |
all | 全表扫描,此时id上无索引 |
== 结果值从最好到最坏依次是:system —> const —> eq_ref —> ref —> range —> index —> all ==
Explain-extra:
extra | 含义 |
---|---|
using filesort | 说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,称为“文件排序”,效率低 |
using temporary | 需要建立临时表来暂存中间结果,常见于order by和group by;效率低 |
using index | SQL所需要返回的所有列数据均在一颗索引树上,避免访问表的数据行,效率不错 |
MySQL从5.0.37版本开始增加了对show profile和show profiles语句的支持,show profile能够在做SQL优化时帮助我们了解时间都耗费到哪里去了
通过have_profiling参数,能够看到当前MySQL是否支持profile:
select @@have_profiling;
set profiling = 1; --开启profiling开关
通过profile,我们能够更清楚地了解SQL执行的过程
show profiles; --查看全部SQL执行过程
show profile for query SQL语句执行id; --查询单条SQL执行过程
在获取到最消耗时间的线程状态后,MySQL支持进一步选择all、cpu、block io、context switch、page faults等明细类型查看MySQL在使用什么资源上耗费了过高的时间,例如:选择查看CPU耗费的时间:show profile cpu for query SQL执行id;
MySQL5.6提供了对SQL的跟踪trace,通过trace文件能够进一步了解为什么优化器选择A计划而不是B计划。
打开trace,设置格式为JSON,并设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示
set optionizer_trace = 'enabled=on',end_markers_in_json=on;
set optionizer_trace_max_men_size = 1000000;
#执行SQL语句
select * from user;
#通过检查information_schema.optionizer_trace就可以知道MySQL是如何执行SQL的
select * from information_schema.optionizer_trace\G;
索引是数据库优化最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的MySQL的性能优化问题
#数据准备
create table tb_seller(
sellerid varchar(100),
name varchar(100),
nickname varchar(50),
password varchar(50),
status varchar(1),
address varchar(100),
createtime datetime,
primary key(sellerid)
)engine = innodb default charset = utf8mb4;
#创建组合索引
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 name='小米科技';
explain select * from tb_seller where name='小米科技' and status = '1';
#违反最左前缀法则,索引失效
explain select * from tb_seller where status = '1';
#符合最左前缀法则,但出现了跳跃某一索引列,所以只有最左边的索引生效
explain select * from tb_seller where name='小米科技' and address = '北京'; --只有name列的索引生效,address列的索引不生效
避免索引失效应用—其他匹配原则:
#该情况下,索引生效,执行效率高
explain select * from tb_seller where name='小米科技' and status = '1' and address = '北京';
#范围查询右边的列,不能使用索引
explain select * from tb_seller where name='小米科技' and status > '1' and address = '北京'; --name和status走了索引,address没走索引
#不要在索引列上进行运算操作,索引将失效
explain select * from tb_seller where substring(name,3,2) = '科技';
#字符串不加单引号,造成索引失效
explain select * from tb_seller where name='小米科技' and status = 1;
#尽量使用覆盖索引,避免select *
explain select name,status,address from tb_seller where name='小米科技' and status = '1' and address = '北京';
#用or分割开的条件,那么涉及的索引都不会被用到
explain select * from tb_seller where name='小米科技' or status = 1;
#以%开头的like模糊查询,索引失效
explain select * from tb_seller where name like '%科技'; --弥补不足,不使用*号,使用索引列即可
注意:
大批量插入数据
当使用load命令导入数据时,适当的设置可以提高导入的效率,对于InnoDB类型的表,有以下几种方式可以提高导入的效率
1.主键顺序插入
因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率,如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率
--首先检查一个全局系统变量‘local_infile’的状态,如果得到显示value = off,则说明这是不可用的
show global variables like 'local_infile';
--修改local_infile值为on,开启local_infile
set global local_infile = 1;
--加载sql脚本
load data local infile '文件路径' into table 表名 fields terminated by ',' lines terminated by '\n';
2.关闭唯一性校验
在导入数据前执行 set unique_checks = 0,关闭唯一性校验,在导入结束后执行set unique_checks = 1,恢复唯一性校验,可以提高导入的效率
--关闭唯一性校验
set unique_checks = 0;
--加载sql脚本
load data local infile '文件路径' into table 表名 fields terminated by ',' lines terminated by '\n';
--开启唯一性校验
set unique_checks = 1;
优化insert语句:
优化order by语句:
两种排序方式:
优化子查询:
使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表死锁,并且写起来也很容易,但是,有些情况下,子查询是可以被更高效的连接(join)替代(连接查询效率高于子查询,连接查询效率之所以高于子查询,是因为连接查询不需要在内存中创建临时表来完成逻辑上需要两个步骤的查询工作)
优化limit查询:
一般分页查询时,通过创建覆盖索引能够比较好地提高性能,一个常见又非常头疼的问题就是 limit 900000,10,此时需要MySQL排序前900010记录,仅仅返回900000 - 900010的数据,其他记录丢弃,查询排序代价非常大
优化思路: