性能优化是一个老生常谈的问题,今天讲讲MySQL的性能优化有哪些手段。
一、配置优化
1.1 连接配置优化
SQL执行第一步是客户端与服务端建立连接。对于服务端来说,当并发特别大的时候,可能出现服务端连接数不够的情况,这时会出现:Mysql: error 1040: Too many connections 的错误。我们可以从两个方面来解决连接数不够的问题。
1.1.1 增加服务端的可用连接数
- 修改配置参数增加可用连接数,修改 max_connections 的大小。
show variables like 'max_connections'; -- 修改最大连接数,当有多个应用连接的时候 - 及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是 28800 秒,8 小时,我们可以把这个值调小。
show global variables like 'wait_timeout'; --及时释放不活动的连接,注意不要释放连接池还在使用的连接
1.1.2 客户端减少从服务端获取的连接数
我们可以引入连接池,实现连接的重用。
我们可以在哪些层面使用连接池?ORM 层面(MyBatis 自带了一个连接池);或者使用专用的连接池工具(阿里的 Druid、Spring Boot 2.x 版本默认的连接池 Hikari、老牌的 DBCP 和 C3P0)。
当客户端改成从连接池获取连接之后,连接池的大小应该怎么设置呢?大家可能会有一个误解,觉得连接池的最大连接数越大越好,这样在高并发的情况下客户端可以获取的连接数更多,不需要排队。
实际情况并不是这样。连接池并不是越大越好,只要维护一定数量大小的连接池,其他的客户端排队等待获取连接就可以了。有的时候连接池越大,效率反而越低。Druid 的默认最大连接池大小是 8。Hikari 的默认最大连接池大小是 10。
为什么默认值都是这么小呢?
在 Hikari 的 github 文档中,给出了一个 PostgreSQL 数据库建议的设置连接池大小的公式:
github.com/brettwooldr…
它的建议是机器核数乘以 2 加 1。也就是说,4 核的机器,连接池维护 9 个连接就 够了。这个公式从一定程度上来说对其他数据库也是适用的。这里面还有一个减少连接池大小实现提升并发度和吞吐量的案例。
为什么有的情况下,减少连接数反而会提升吞吐量呢?为什么建议设置的连接池大小要跟 CPU 的核数相关呢?
每一个连接,服务端都需要创建一个线程去处理它。连接数越多,服务端创建的线程数就会越多。
问题:CPU 是怎么同时执行远远超过它的核数大小的任务的?
时间片。上下文切换。 而 CPU 的核数是有限的,频繁的上下文切换会造成比较大的性能开销。
二、架构优化
2.1 缓存
缓存这个很好理解,通过引入第三方缓存,来减轻MySQL的压力。
2.2 主从复制
主从复制能够实现读写分离。对于update、insert、delete 在主库执行,对于查询在从库执行。但是主从复制涉及到主库与从库的数据同步,会存在数据不一致的风险。
2.3 分库分表
分库分表主要有两个方向:垂直拆分和水平拆分。
2.3.1 垂直拆分
- 不同的业务模块使用不同的数据库。
- 对于字段比较多的表,可以把一些和主业务不太相关的字段单独拆分一个表来存储。
2.3.2 水平拆分
当一张表数据量特别大时,可以将表水平拆分成多张,存在在不同的表或不同的库。 水平拆分的重点是:拆分的依据。比如可以按id的奇偶、创建日期等。
- 历史表。按日期分为当天的表、昨日表、近7日的表、归档表。
- 对id取模。
三、SQL调优
SQL调优,是需要我们程序员重点掌握的。这个跟开发强相关了。
通过explain,来分析执行计划。
下面来分析执行计划每个字段的含义。
3.1 explain - id
id 是查询序列编号。
记住原理:id值不同时,先大后小;id值相同时,自上而下。
3.2 select_type
- SIMPLE
简单查询,不包含子查询,不包含关联查询 union - PRIMARY
子查询 SQL 语句中的主查询,也就是最外面的那层查询。 - SUBQUERY
子查询中所有的内层查询都是 SUBQUERY 类型的 - DERIVED
衍生查询,表示在得到最终查询结果之前会用到临时表 - UNION
用到了 UNION 查询。
3.3 type 连接类型
在常用的链接类型中:system > const > eq_ref > ref > range > index > all
这 里 并 没 有 列 举 全 部 ( 其 他 : fulltext 、 ref_or_null 、 index_merger 、unique_subquery、index_subquery)。
以上访问类型除了 all,都能用到索引。
- const 主键索引或者唯一索引,只能查到一条数据的 SQL
- eq_ref
通常出现在多表的 join 查询,表示对于前表的每一个结果,,都只能匹配到后表的一行结果。一般是唯一性索引的查询(UNIQUE 或 PRIMARY KEY)。 - ref
查询用到了非唯一性索引,或者关联操作只使用了索引的最左前缀 - range 索引范围扫描。如果 where 后面是 between and 或 <或 > 或 >= 或 <=或 in 这些都是range。
- index
Full Index Scan,查询全部索引中的数据(比不走索引要快)。其实是查询的字段只包含索引值,用到了覆盖索引,不需要回表。 - all 全表扫描。
一般来说,需要保证查询至少达到 range 级别,最好能达到 ref。
ALL(全表扫描)和 index(查询全部索引)都是需要优化的
3.4 possible_key、key
possible_key表示可能用到的索引。
key表示实际用到的索引。
3.5 rows
扫描的行数,是个预估值。越小越好。
3.6 ref
使用哪个列或者常数和索引一起从表中筛选数据。
3.7 Extra
执行计划给出的额外的信息说明
- useing index
使用到了覆盖索引,不需要回表 - useing where 使用了 where 过滤,表示存储引擎返回的记录并不是所有的都满足查询条件,需要在 server 层进行过滤
- Using index condition 索引条件下推,过滤在存储引擎进行
- using filesort 不能使用索引来排序,用到了额外的排序(跟磁盘或文件没有关系)。需要优化。
- using temporary
用到了临时表。例如(以下不是全部的情况):- distinct 非索引列
- group by 非索引列
- 使用 join 的时候,group 任意列
四、业务流程优化
除了对于代码、SQL 语句、表定义、架构、配置优化之外,业务层面的优化也不能忽视。举几个例子:
- 在某一年的双十一,为什么会做一个充值到余额宝和余额有奖金的活动(充 300送 50)? 因为使用余额或者余额宝付款是记录本地或者内部数据库,而使用银行卡付款,需要调用接口,操作内部数据库肯定更快。
- 在去年的双十一,为什么在凌晨禁止查询今天之外的账单?
这是一种降级措施,用来保证当前最核心的业务。 - 最近几年的双十一,为什么提前一个多星期就已经有双十一当天的价格了?
预售分流。
在应用层面同样有很多其他的方案来优化,达到尽量减轻数据库的压力的目的,比如限流,或者引入 MQ 削峰,等等
五、总结
本章核心是掌握SQL优化。SQL优化需要重点掌握执行计划的各个字段含义。宗旨是保证SQL使用索引。