一,优化思路
性能调优的目标是让查询更快。一个查询的流程是多个环节组成的,每个环节都会消耗时间。要减少查询所消耗的时间,要从每一个环节入手。
二,连接----配置优化
客户端连接到服务端,有可能是服务端的连接数不够导致应用程序获取不到连接。
比如 Mysql:error 1040: Too many connections的错误,就是超过了服务端设置的最大并发连接数。
可以从两个方面来解决连接数不够的问题:
1,从服务端,可以增加服务端的可用连接数。如果有多个应用或者很多请求同时访问数据库,连接数不够的时候,可以
(1) 增加可用连接数,修改max_connetctions的大小
show variables like 'max_connetctions'; -- 修改最大连接数,当有多个应用连接的时候
(2) 及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是28800秒,8小时,可以把这个值调小。
show global variables like 'wait_timeout'; -- 及时释放不活动的连接,注意不要释放连接池还在使用的连接
2,从客户端,减少从服务端获取的连接数。如果想要不是每次执行SQL都创建一个新的连接,这个时候可以引用连接池,实现连接的重用。
常见的数据连接池有DBCP和C3P0、阿里的druid,Hikari(Spring Boot 2.x版本默认的连接池)。
连接池也不是越大越好,只要维护一定数量大小的连接池,其它的客户端排队等待获取连接就可以了。有时候连接池越大,效率反而降低。Druid的默认最大连接池是8。Hikari的默认最大连接池是10。
在Hikari的github文档中,给出了一个postgreSQL数据库建议的设置连接池大小的公式。https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
它的建议是机器的核数乘以2加1。也就是说4核的机器,连接池维护9个连接就够了。每一个连接,服务端都要创建一个线程去处理它。连接数越多,服务端创建的线程数就会越多。CPU通过时间片,上下文切换来执行远超过它的核数据的任务。而CPU的核数是有限的,频繁的上下文切换会造成较大的性能开销。
三,架构优化
1,缓存
为了减轻数据库的压力,和提升查询效率,可以把数据放到内存缓存起来,比如使用Redis。运行独立的缓存服务,属于架构层面的优化。
2,集群,主从复制
一种提升可用性的手段,叫做冗余,也就是创建集群。
集群必然会面临不同节点之间数据一致性的问题。如果同时读写多台数据库节点,怎么使节点数据保持一致?需要使用到复制技术(replication),被复制的节点称为master,复制的节点称为slave。slave节点也可以作为其它节点的数据来源,这个叫做级联复制。
MySQL主从复制的实现:MySQL的所有更新语句都会记录到server的binlog。有了这个binlog,从服务器会不断获取主服务器的binlog文件,然后解析里面的SQL语句,在从服务器上执行一遍,保持主从的数据一致。
这里涉及到3个线程:
(1),连接到master获取binlog,并且解析binlog写入中继日志,这个线程叫I/O线程。
(2),Master节点上有一个log dump线程,用来发送binlog给slave的。
(3),从库的SQL线程,是用来读取relay log,把数据写入数据库的。
做了主从复制配置方案之后,只把数据写入到master节点,而读的请求可以分担到slave节点,这种方案叫读写分离。对于读多写少的项目来说,读写分离对减轻主服务器的访问压力很有用。
3,分库分表
如果单张表存储的数据过大的时候,比如一张上亿的数据,单表的查询性能还是会大幅下降。这个时候,就要用到分布式架构中的分片。把单个节点的数据分散到多个节点存储,减少存储和访问压力,这个就是分库分表。
分库分表分为两类。垂直分库,减少并发压力。水平分表,解决存储瓶颈。
垂直分库,就是把数据库按照业务拆分成不同的数据库:
水平分表,就是把单张表的数据按照一定的规则分布到多个数据库。
四,优化器-----SQL语句分析和优化
1,慢查询日志 slow query log
1,打开慢查询日志,默认是关闭的:
show variables like 'slow_query%';
参数有两种修改方式:
(1),set 动态修改(重启后失效)
set @@global.slow_query_log = 1; -- 1 开启 0 关闭,重启后失效
set @@global.long_query_time = 3; -- 超过几秒算慢查询,默认10秒,另开一个窗口才能查到最新值
show variables like '%long_query%';
show variables like '%slow_query%';
(2),修改配置文件my.cfg
以下配置定义了慢查询日志的开关、慢查询的时间、日志文件的存放路径。
slow_query_log = ON
long_query_time = 2
slow_query_log_file = /var/lib/mysql/localhost-slow.log
模拟慢查询:
select sleep(10);
2,慢日志分析
(1),日志内容
less /var/lib/mysql/localhost-slow.log
(2),mysqldumpslow
MySQL提供了mysqldumpslow的工具,在MySQL的bin目录下。
mysqldumpslow --help
例如查询用时最多的10条慢sql:
mysqldumpslow -s -t 10 -g 'select' /var/lib/mysql/localhost-slow.log
Count代表这个SQL执行了多少次;
Time代表执行的时间,括号里是累计的时间;
Lock表示锁定的时间,括号是累计;
Rows表示返回的记录数,括号是累计。
(3),其它系统命令
show processlist 运行线程,用于显示用户的运行线程,可以用id号kill线程。
show full processlist;
select * from information_schema.processlist;
列 | 含义 |
---|---|
Id | 线程的唯一标志,可以根据它kill线程 |
User | 启动这个线程的用户,普通用户只能看到自己的线程 |
Host | 哪个IP端口发起的连接 |
db | 操作的数据库 |
Command | 线程的命令 https://dev.mysql.com/doc/refman/5.7/en/thread-commands.html |
Time | 操作持续时间,单位秒 |
State | 线程状态,比如查询可能有copying to tmp table,Sorting result,Sending data https://dev.mysql.com/doc/refman/5.7/en/general-thread-states.html |
Info | SQL语句的前100个字符,如果要查看完整的SQL,用show full processlist |
show status 服务器运行状态,重启后清空
show global status;
有session和global两种作用域,格式:参数--值。可用like带通配符过滤。
show global status like 'com_select'; -- 查看select次数
show engine 显示存储引擎当前运行信息,包括事务持有的表锁、行锁信息;事务的锁等待情况;线程信号量等待;文件IO请求;buffer pool统计信息。
show engine innodb status;
开启InnoDB监控:
-- 开启标准监控和锁监控
set global innodb_status_output=ON;
set global innodb_status_output_locks=ON:
2,explain 执行计划
MySQL提供了一个执行计划的工具。通过EXPLAIN可以模拟服务端执行SQL查询语句的过程。通过这种方式可以分析语句或者表的性能瓶颈。
explain 的字段分析:
1,id 是查询序列编号,每张表都是单独访问的,一个select会有一个序号。id值不同的时候,先查询id值大的(先大后小),子查询只能以这种方式进行,先拿到内层的结果才能进行外层的查询。
id值相同时,表的查询是从上往下顺序执行。在连接查询时,先查询的叫做驱动表,后查询的叫做被驱动表。应该先查小表(得到结果少的表),因为它的中间结果最少。
2,select type 查询类型
- simple:简单查询,不包含子查询,也不包含关联查询union
- PRIMARY:子查询SQL语句中的主查询,也就是最外面的那层查询
- SUBQUERY:子查询中所有的内层查询都是SUBQUERY类型的
- DERIVER:衍生查询,表示在得到最终查询结果之前会用到临时表。对于关联查询,先执行右边的table(UNION)再执行左边的table,类型是DERIVED。
- UNION:用到了UNION查询
-
UNION RESULT:主要显示哪些表之间存在UNION查询
3,type 连接类型
常用的连接类型中:system > const > eq_ref > ref > range > index > all
其它的有: fulltext、ref_or_null、index_merger、unique_subquery、index_subquery。
以上访问类型除了all,都能用到索引。 -
const:主键索引或者唯一索引,只能查到一条数据的sql。
- system:system是const的一种特例,只有一行满足条件,对于MyISAM、Memory的表,只查询到一条记录,也是system。
- eq_ref:通常出现在多表的join查询,被驱动表通过唯一性索引(UNIQUE或PRIMARY KEY)进行访问,此时被驱动表的访问方式就是eq_ref。 eq_ref是除const之外最好的访问类型。
- ref:查询用到了非唯一性索引,或者关联操作只使用了索引的最左前缀。
- range:索引范围扫描。如果where后面是between and 或 < 或 > 或 >= 或 <=或 in这些,type类型就是range。
- index:Full Index Scan,查询全部索引中的数据(比不走索引要快)
- all:Full Table Scan,如果不有索引或者没有用到索引,type就是ALL。代表全表扫描。
- NULL:不和访问表或者索引就能得到结果。
小结:一般来说,需要保证查询的type至少达到range级别,最好能达到ref。ALL(全表扫描)和 index(查询全部索引)都是需要优化的。
4,possible_key、key
可能用到的索引和实际用到的索引。如果是NULL就代表没有用到索引。
possible_key可以有一个或者多个,可能用到索引不代表一定用到索引。possible_key为空,key也可能有值(查询优化,覆盖索引)。如果通过分析发现没有用到索引,就要检查SQL或者创建索引。
5,key_len
索引的长度(使用的字节数)。跟索引字段的类型、长度有关。
6,rows
MySQL认为扫描多少行才能返回请求的数据,是一个预估值,越少越好。
7,filtered
这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,是一个百分比。如果比例很低,说明存储引擎返回的数据需要经过大量过滤,这个会消耗性能。
8,ref
使用哪个列或者常数和索引一起从表中筛选数据。
9,Extra
执行计划给出的额外信息说明。
- using index:用到了覆盖索引,不需要回表。
- using where:使用了where过滤,表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤(跟是否使用索引没有关系)
- using index condition:索引条件下推
- using filesort:不能使用索引来排序,用到了额外的排序(跟磁盘或文件没有关系)。需要优化。
- using temporary:用到了临时表。如 distinct 非索引列; group by 非索引列;使用join的时候,group 任意列。这种情况需要优化,比如创建复合索引。
如果需要具体的cost信息,可以用EXPLAIN FORMAT=JSON。如果觉得EXPLAIN还不够详细,可以开启optimizer trace。
show variables like 'optimizer_trace';
set optimizer_trace='enabled=on';
select * from information_schema.optimizer_trace;
总结:模拟优化器执行SQL查询语句的时候,知道MySQL是怎么处理一条SQL语句的,通过这种方式可以分析语句或表的性能瓶颈。
3,SQL与索引优化
如果SQL语句比较复杂,有多个关联和子查询的时候,就要分析SQL语句有没有改写的方法。如:
-- 大偏移量的limit
select * from user limit 9000000,10;
-- 改成先过滤id,再limit
select * from user where id > 9000000 limit 10;
五,存储引擎
1,存储引擎的选择:不同的业务选择不同的存储引擎,比如查询多插入少的业务表用MyISAM,临时表用Memory。常规的并发大更新多的用InnoDB
2,分区或者分表
3,字段定义:使用正确存储数据的最小数据类型。
- 整数类型: int有6种类型,不同类型的最大存储范围是不一样的,占用的存储空间也是不一样的。比如性别字段,用tinyint,因为enum也是整数存储
- 字符类型:变长情况下,varchar更节省空间,但是对于varchar字段,需要一个字节来记录长度。固定长度的用char,不要用varchar。
- 非空:非空字段尽量定义成NOT NULL,提供默认值,或者使用特殊值、空串代替null。NULL类型的存储、优化、使用都会存在问题。
- 不要用外键、触发器、视图:降低了可读性,影响数据库的性能,应该把计算的事情交给程序,数据库专心做存储;数据的完整性应该在程序中检查。
- 大文件存储:图片、音频、视频的存储,不要用数据库存储图片(base64编码)或者大文件,把文件放在NAS上,数据库只需要存储URL。
- 表拆分或者字段冗余:将不常用的字段拆分出去,避免列数过多或者数据量过大。
六,优化体系
除了对于代码、SQL语句、表定义、架构、配置优化之外,业务层面的优化也不有忽视。在应用层面有很多其它的方案来优化,达到尽量减轻数据库的压力的目的,比如限流,或者引入MQ削峰等。
七,优化案例
服务端状态分析,如果出现连接变慢,查询被阻塞,无法获取连接的情况。
1,重启!
2,show processlist查看线程状态,连接数数量、连接时间、状态
3,查看锁的状态
4,kill有问题的线程
对于具体的慢SQL:
1,分析查询基本情况,涉及到的表的结构,字段的索引情况、每张表的数据量、查询的业务含义。
2,找出慢的原因:(1) 查看执行计划,分析SQL的执行情况,了解表访问顺序、访问类型、索引、扫描行数等信息。(2) 如果总体的时间很长,不确定哪一个因素影响最大,通过条件的增减,顺序的调整,找出引起查询慢的主要原因,不断尝试验证。找到原因,比如没走索引,还是关联查询引起的,还是order by 引起的。
3,对症下药:
- 创建索引或者联合索引。
- 改写SQL,比如使用小表驱动大表,用join来代码子查询,not exist转换成left join IS NULL, or 改成union,如果结果集允许重复的话,使用UNION ALL代替UNION,大偏移的limit先过滤再排序。
- 表结构(冗余、拆分、not null等),架构优化(缓存、读写分离、分库分表)。
- 业务的优化,必须条件是否必要。