MySQL的性能调优已经随着逐年内卷,越来越多的进入到高级程序员的面试中;而调优这件事儿根据不同的场景也有不同的技巧,和方法策略。下面我们通过,性能监控,数据类型优化,执行计划,索引优化,SQL优化等5个方面展开。

性能监控

在进行调优之前,我们应当对目前生产环境MySQL性能有一定的了解,可以通过以下3种方式。

show profile

-- 默认关闭,如下配置开启
set profiling=1;
-- 查看最近的数条
show profiles;

-- 查看最近的1条
show profile;

show profile官方文档

performance schema

MySQL的performance schema 用于监控MySQL server在一个较低级别的运行过程中的资源消耗、资源等待等情况。

performance schema官方文档

show processlist

show processlist

结果截图:
image-1655255889753

show processlist官方文档

数据类型优化

在表设计之初,我们应当选择合适数据类型;不仅可以节省表空间,可以提高索引的效率。

1.整数类型

MySQL的整数类型有:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT分别使用8,16,24,32,64位存储空间。尽量使用满足需求的最小数据类型。

2.字符或字符串类型

varchar根据实际内容长度保存数据,char固定长度的字符串。

3.BLOB和TEXT类型

MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。
两者都是为了存储很大数据而设计的字符串类型,分别采用二进制和字符方式存储。

4.datetime和timestamp

datetime占8个字节,与时区无关,数据库底层时区配置,对datetime无效;可保存到毫秒。

timestamp占4个字节,时间范围:1970-01-01到2038-01-19,精确到秒,采用整形存储,时间范围:1970-01-01到2038-01-19;依赖数据库底层设置时区。

date占用的字节数比使用字符串、datetime、int存储要少,使用date类型只需要3个字节,使用date类型还可以利用日期时间函数进行日期之间的计算,date类型用于保存1000-01-01到9999-12-31之间的日期。

5.主键的选择

代理主键:与业务无关的,无意义的数字序列

自然主键:事物属性中的自然唯一标识

推荐使用代理主键,它们不与业务耦合,因此更容易维护。

6.范式与反范式的合理选择

执行计划

explain官方文档

可以使用explain+SQL语句来模拟优化器执行SQL查询语句,从而知道mysql是如何处理sql语句的。

调优人员需要重点关注:type,key,rows,extra等输出的信息。

执行计划包含的信息

Column Meaning
id The SELECT identifier
select_type The SELECT type
table The table for the output row
partitions The matching partitions
type The join type
possible_keys The possible indexes to choose
key The index actually chosen
key_len The length of the chosen key
ref The columns compared to the index
rows Estimate of rows to be examined
filtered Percentage of rows filtered by table condition
extra Additional information

id

select查询的序列号,包含一组数字,表示查询中执行select子句或者操作表的顺序

id号分为三种情况:

​ 1、如果id相同,那么执行顺序从上到下

explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal;

​ 2、如果id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

explain select * from emp e where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');

​ 3、id相同和不同的,同时存在:相同的可以认为是一组,从上往下顺序执行,在所有组中,id值越大,优先级越高,越先执行

explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');

select_type

主要用来分辨查询的类型,是普通查询还是联合查询还是子查询

select_type Value Meaning
SIMPLE Simple SELECT (not using UNION or subqueries)
PRIMARY Outermost SELECT
UNION Second or later SELECT statement in a UNION
DEPENDENT UNION Second or later SELECT statement in a UNION, dependent on outer query
UNION RESULT Result of a UNION.
SUBQUERY First SELECT in subquery
DEPENDENT SUBQUERY First SELECT in subquery, dependent on outer query
DERIVED Derived table
UNCACHEABLE SUBQUERY A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query
UNCACHEABLE UNION The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY)
--sample:简单的查询,不包含子查询和union
explain select * from emp;

--primary:查询中若包含任何复杂的子查询,最外层查询则被标记为Primary
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;

--union:若第二个select出现在union之后,则被标记为union
explain select * from emp where deptno = 10 union select * from emp where sal >2000;

--dependent union:跟union类似,此处的depentent表示union或union all联合而成的结果会受外部表影响
explain select * from emp e where e.empno  in ( select empno from emp where deptno = 10 union select empno from emp where sal >2000)

--union result:从union表获取结果的select
explain select * from emp where deptno = 10 union select * from emp where sal >2000;

--subquery:在select或者where列表中包含子查询
explain select * from emp where sal > (select avg(sal) from emp) ;

--dependent subquery:subquery的子查询要受到外部表查询的影响
explain select * from emp e where e.deptno in (select distinct deptno from dept);

--DERIVED: from子句中出现的子查询,也叫做派生类,
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;

--UNCACHEABLE SUBQUERY:表示使用子查询的结果不能被缓存
 explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
 
--uncacheable union:表示union的查询结果不能被缓存:sql语句未验证

table

对应行正在访问哪一个表,表名或者别名,可能是临时表或者union合并结果集
1、如果是具体的表名,则表明从实际的物理表中获取数据,当然也可以是表的别名

​ 2、表名是derivedN的形式,表示使用了id为N的查询产生的衍生表

​ 3、当有union result的时候,表名是union n1,n2等的形式,n1,n2表示参与union的id

type

type显示的是访问类型,访问类型表示我是以何种方式去访问我们的数据,最容易想的是全表扫描,直接暴力的遍历一张表去寻找需要的数据,效率非常低下,访问的类型有很多,效率从最好到最坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般情况下,得保证查询至少达到range级别,最好能达到ref

--all:全表扫描,一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。
explain select * from emp;

--index:全索引扫描这个比all的效率要好,主要有两种情况,一种是当前的查询时覆盖索引,即我们需要的数据在索引中就可以索取,或者是使用了索引进行排序,这样就避免数据的重排序
explain  select empno from emp;

--range:表示利用索引查询的时候限制了范围,在指定范围内进行查询,这样避免了index的全索引扫描,适用的操作符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN() 
explain select * from emp where empno between 7000 and 7500;

--index_subquery:利用索引来关联子查询,不再扫描全表
explain select * from emp where emp.job in (select job from t_job);

--unique_subquery:该连接类型类似与index_subquery,使用的是唯一索引
 explain select * from emp e where e.deptno in (select distinct deptno from dept);
 
--index_merge:在查询过程中需要多个索引组合使用,没有模拟出来

--ref_or_null:对于某个字段即需要关联条件,也需要null值的情况下,查询优化器会选择这种访问方式
explain select * from emp e where  e.mgr is null or e.mgr=7369;

--ref:使用了非唯一性索引进行数据的查找
 create index idx_3 on emp(deptno);
 explain select * from emp e,dept d where e.deptno =d.deptno;

--eq_ref :使用唯一性索引进行数据查找
explain select * from emp,emp2 where emp.empno = emp2.empno;

--const:这个表至多有一个匹配行,
explain select * from emp where empno = 7369;
 
--system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现

possible_keys

​ 显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

key

​ 实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠。

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

key_len

表示索引中使用的字节数,可以通过key_len计算查询中使用的索引长度,在不损失精度的情况下长度越短越好。

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

rows

根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的sql找了多少数据,在完成目的的情况下越少越好

explain select * from emp;

extra

包含额外的信息。

--using filesort:说明mysql无法利用索引进行排序,只能利用排序算法进行排序,会消耗额外的位置
explain select * from emp order by sal;

--using temporary:建立临时表来保存中间结果,查询完成之后把临时表删除
explain select ename,count(*) from emp where deptno = 10 group by ename;

--using index:这个表示当前的查询时覆盖索引的,直接从索引中读取数据,而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找,如果没有,表面索引被用来读取数据,而不是真的查找
explain select deptno,count(*) from emp group by deptno limit 10;

--using where:使用where进行条件过滤
explain select * from t_user where id = 1;

--using join buffer:使用连接缓存,情况没有模拟出来

--impossible where:where语句的结果总是false
explain select * from emp where empno = 7469;

索引优化

索引的分类

索引通常分为:主键索引,唯一索引,普通索引,组合索引,全文索引

索引的数据结构

通常索引采用的数据结构有:哈希表,B+树

具体的跟存储引擎相关,例如:Memory存储引擎所采用的是哈希表,MyISAM 和 InnoDB采用的是B+树。

索引的匹配方式

案例为:name,age,pos的组合索引

  • 全值匹配
    全值匹配指的是和索引中的所有列进行匹配
explain select * from staffs where name = 'July' and age = '23' and pos = 'dev';
  • 匹配最左前缀
    只匹配前面的几列
explain select * from staffs where name = 'July' and age = '23';
  • 匹配列前缀
    可以匹配某一列的值的开头部分
explain select * from staffs where name like 'J%';
  • 匹配范围值
    可以查找某一个范围的数据
explain select * from staffs where name > 'Mary';
  • 精确匹配某一列并范围匹配另外一列
    可以查询第一列的全部和第二列的部分
explain select * from staffs where name = 'July' and age > 25;
  • 只访问索引的查询
    查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
explain select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev';

组合索引

当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要。

案例,建立组合索引a,b,c
image-1655260952331

优化小细节

1、当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层。

select actor_id from actor where actor_id=4;

select actor_id from actor where actor_id+1=5;

2、尽量使用主键查询,而不是其他索引,因为主键查询不会触发回表查询

3、可以使用前缀索引

4、尽量使用索引来排序,因为索引本身就是有序的。

5、union all,in,or都能够使用索引,但是推荐使用in;明确数据无交集时,union ,union all都可以使用,通常选择union all,因为union默认会有distinct操作。

explain select * from actor where actor_id = 1 union all select * from actor where actor_id = 2;

explain select * from actor where actor_id in (1,2);

explain select * from actor where actor_id = 1 or actor_id =2;

6、范围列可以用到索引
范围条件是:<、>等

范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列

7、强制类型转换会全表扫描

--不会触发索引
explain select * from user where phone=13800001234;

--触发索引
explain select * from user where phone='13800001234';

8、更新十分频繁,数据区分度不高的字段上不宜建立索引

  • 更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能;

  • 类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据,

  • 一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算

9、创建索引的列,尽量不允许为null,可能会得到不符合预期的结果

10、当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致

11、明确数据返回量的情况下,能使用limit的时候尽量使用limit

12、单表索引建议控制在5个以内

13、单索引字段数不允许超过5个(组合索引)

索引监控

show status like 'Handler_read%';

Handler_read_first:读取索引第一个条目的次数

Handler_read_key:通过index获取数据的次数

Handler_read_last:读取索引最后一个条目的次数

Handler_read_next:通过索引读取下一条数据的次数

Handler_read_prev:通过索引读取上一条数据的次数

Handler_read_rnd:从固定位置读取数据的次数

Handler_read_rnd_next:从数据节点读取下一条数据的次数

常用服务器参数配置

general

参数名 含义
datadir=/var/lib/mysql 数据文件存放的目录
socket=/var/lib/mysql/mysql.sock mysql.socket表示server和client在同一台服务器,并且使用localhost进行连接,就会使用socket进行连接
pid_file=/var/lib/mysql/mysql.pid 存储mysql的pid
port=3306 mysql服务的端口号
default_storage_engine=InnoDB mysql存储引擎
skip-grant-tables 当忘记mysql的用户名密码的时候,可以在mysql配置文件中配置该参数,跳过权限表验证,不需要密码即可登录mysql

character

参数名 含义
character_set_client 客户端数据的字符集
character_set_connection mysql处理客户端发来的信息时,会把这些数据转换成连接的字符集格式
character_set_results mysql发送给客户端的结果集所用的字符集
character_set_database 数据库默认的字符集
character_set_server mysql server的默认字符集

connection

参数名 含义
max_connections mysql的最大连接数,如果数据库的并发连接请求比较大,应该调高该值
max_user_connections 限制每个用户的连接个数
back_log mysql能够暂存的连接数量,当mysql的线程在一个很短时间内得到非常多的连接请求时,就会起作用,如果mysql的连接数量达到max_connections时,新的请求会被存储在堆栈中,以等待某一个连接释放资源,如果等待连接的数量超过back_log,则不再接受连接资源
wait_timeout mysql在关闭一个非交互的连接之前需要等待的时长
interactive_timeout 关闭一个交互连接之前需要等待的秒数

log

参数名 含义
log_error 指定错误日志文件名称,用于记录当mysqld启动和停止时,以及服务器在运行中发生任何严重错误时的相关信息
log_bin 指定二进制日志文件名称,用于记录对数据造成更改的所有查询语句
binlog_do_db 指定将更新记录到二进制日志的数据库,其他所有没有显式指定的数据库更新将忽略,不记录在日志中
binlog_ignore_db 指定不将更新记录到二进制日志的数据库
sync_binlog 指定多少次写日志后同步磁盘
general_log 是否开启查询日志记录
general_log_file 指定查询日志文件名,用于记录所有的查询语句
slow_query_log 是否开启慢查询日志记录
slow_query_log_file 指定慢查询日志文件名称,用于记录耗时比较长的查询语句
long_query_time 设置慢查询的时间,超过这个时间的查询语句才会记录日志

cache

参数名 含义
key_buffer_size 索引缓存区的大小(只对myisam表起作用)
query_cache_size 查询缓存的大小,未来版本被删除
query_cache_limit 超出此大小的查询将不被缓存
query_cache_min_res_unit 缓存块最小大小
query_cache_type 缓存类型,决定缓存什么样的查询
sort_buffer_size 每个需要排序的线程分派该大小的缓冲区
max_allowed_packet=32M 限制server接受的数据包大小
join_buffer_size=2M 表示关联缓存的大小

InnoDB

参数名 含义
innodb_buffer_pool_size 该参数指定大小的内存来缓冲数据和索引,最大可以设置为物理内存的80%
innodb_flush_log_at_trx_commit 主要控制innodb将log buffer中的数据写入日志文件并flush磁盘的时间点,值分别为0,1,2
innodb_thread_concurrency 设置innodb线程的并发数,默认为0表示不受限制,如果要设置建议跟服务器的cpu核心数一致或者是cpu核心数的两倍
innodb_log_buffer_size 此参数确定日志文件所用的内存大小,以M为单位
innodb_log_file_size 此参数确定数据日志文件的大小,以M为单位
innodb_log_files_in_group 以循环方式将日志文件写到多个文件中
read_buffer_size mysql读入缓冲区大小,对表进行顺序扫描的请求将分配到一个读入缓冲区
read_rnd_buffer_size mysql随机读的缓冲区大小
innodb_file_per_table 此参数确定为每张表分配一个新的文件