MySQL —— 优化

概念

在应用的开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的SQL语句就成为整个系统性能的瓶颈,因此我们必须要对它们进行优化
MySQL优化方式有很多,大致可以从以下几点来优化MySQL:

  • 从设计上优化
  • 从查询上优化
  • 从索引上优化
  • 从存储上优化

查看SQL执行频率

MySQL客户端连接成功后,通过show [session|global] status命令可以查看服务器状态,通过查看状态信息可以查看对当前数据库的主要操作类型。

--下面的命令显示了当前session中所有统计参数的值
show session status like 'Com_______'; -- 查看当前会话统计结果
show global status like 'Com_______'; -- 查看数据库上次启动至今统计结果
show status like 'Innodb_rows_%'; --查看针对InnoDB引擎的统计结果

定位低效率执行SQL

可以通过两种方式定位执行效率较低的SQL语句:

  • 慢查询日志:通过慢查询日志定位那些执行效率较低的SQL语句
  • show processlist:该命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看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:

  • id:用户登录MySQL时,系统分配的‘connection_id’,可以通过函数connection_id()查看
  • user:显示当前用户,如果不是root,这个命令就只显示用户权限范围的sql语句
  • host:显示这个语句时从哪个ip端口发出的,可以用来追踪出现问题语句的用户
  • db:显示这个进程目前连接的是哪个数据库
  • command:显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
  • time:显示这个状态持续的时间,单位是秒
  • state:显示当前连接的sql语句的状态,很重要的列,state描述的是语句执行中的某一个状态,一个sql语句,以查询为例,可能需要经过copying to tem table、sorting result、sending data等状态才可以完成
  • info:显示这个SQL语句,是判断问题语句的一个重要依据
show processlist;

Explain分析执行计划

通过定位低效率执行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情况有三种:

  • id相同表示加载表的顺序是从上到下的
  • id不同id值越大,优先级越高,越先被执行
  • id有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有组中,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所需要返回的所有列数据均在一颗索引树上,避免访问表的数据行,效率不错

show profile分析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;

trace分析优化器执行计划

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 '%科技'; --弥补不足,不使用*号,使用索引列即可

注意:

  • 如果MySQL评估使用索引比全表更慢,则不使用索引
  • is null,is not null索引有时失效,有时有效
  • 普通索引in索引有效,not in索引失效;主键索引in和not in索引都有效
  • 单列索引和复合索引,尽量使用复合索引;如果一个表有多个单列索引,即使where中都使用了这些索引列,则只有一个最优索引生效

SQL优化

大批量插入数据
当使用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语句:

  • 如果需要同时对一张表插入很多行数据,尽量使用一条insert语句插入多行数据,这种方式大大的缩减客户端与数据库之间的连接、关闭等消耗,使得效率比分开执行单个insert语句快
  • 在事务中进行数据插入:手动提交事务,在事务中进行数据插入
  • 数据有序插入

优化order by语句:
两种排序方式:

  • 通过对返回数据进行排序,也就是通常说的filesort排序,所有不是通过索引直接返回排序结果都叫FileSort排序
  • 通过有序索引扫描直接返回有序数据,这种情况为using index,不需要额外排序,操作效率高

优化子查询:
使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表死锁,并且写起来也很容易,但是,有些情况下,子查询是可以被更高效的连接(join)替代(连接查询效率高于子查询,连接查询效率之所以高于子查询,是因为连接查询不需要在内存中创建临时表来完成逻辑上需要两个步骤的查询工作)

优化limit查询:
一般分页查询时,通过创建覆盖索引能够比较好地提高性能,一个常见又非常头疼的问题就是 limit 900000,10,此时需要MySQL排序前900010记录,仅仅返回900000 - 900010的数据,其他记录丢弃,查询排序代价非常大
优化思路:

  • 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容
  • 该方案是用于主键自增的表,可以把limit查询转换成某个位置的查询

你可能感兴趣的:(MySQL数据库,mysql,数据库)