【MySQL】四、MySQL性能优化

278 阅读7分钟

性能优化是一个老生常谈的问题,今天讲讲MySQL的性能优化有哪些手段。

一、配置优化

1.1 连接配置优化

SQL执行第一步是客户端与服务端建立连接。对于服务端来说,当并发特别大的时候,可能出现服务端连接数不够的情况,这时会出现:Mysql: error 1040: Too many connections 的错误。我们可以从两个方面来解决连接数不够的问题。

1.1.1 增加服务端的可用连接数

  1. 修改配置参数增加可用连接数,修改 max_connections 的大小。
    show variables like 'max_connections'; -- 修改最大连接数,当有多个应用连接的时候
  2. 及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是 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 垂直拆分

  1. 不同的业务模块使用不同的数据库。
  2. 对于字段比较多的表,可以把一些和主业务不太相关的字段单独拆分一个表来存储。

2.3.2 水平拆分

当一张表数据量特别大时,可以将表水平拆分成多张,存在在不同的表或不同的库。 水平拆分的重点是:拆分的依据。比如可以按id的奇偶、创建日期等。

  1. 历史表。按日期分为当天的表、昨日表、近7日的表、归档表。
  2. 对id取模。

三、SQL调优

SQL调优,是需要我们程序员重点掌握的。这个跟开发强相关了。
通过explain,来分析执行计划。
image.png
下面来分析执行计划每个字段的含义。

3.1 explain - id

id 是查询序列编号。
记住原理:id值不同时,先大后小;id值相同时,自上而下。

3.2 select_type

  1. SIMPLE
    简单查询,不包含子查询,不包含关联查询 union
  2. PRIMARY
    子查询 SQL 语句中的主查询,也就是最外面的那层查询。
  3. SUBQUERY
    子查询中所有的内层查询都是 SUBQUERY 类型的
  4. DERIVED
    衍生查询,表示在得到最终查询结果之前会用到临时表
  5. UNION
    用到了 UNION 查询。

3.3 type 连接类型

在常用的链接类型中:system > const > eq_ref > ref > range > index > all
这 里 并 没 有 列 举 全 部 ( 其 他 : fulltext 、 ref_or_null 、 index_merger 、unique_subquery、index_subquery)。
以上访问类型除了 all,都能用到索引。

  1. const 主键索引或者唯一索引,只能查到一条数据的 SQL
  2. eq_ref
    通常出现在多表的 join 查询,表示对于前表的每一个结果,,都只能匹配到后表的一行结果。一般是唯一性索引的查询(UNIQUE 或 PRIMARY KEY)。
  3. ref
    查询用到了非唯一性索引,或者关联操作只使用了索引的最左前缀
  4. range 索引范围扫描。如果 where 后面是 between and 或 <或 > 或 >= 或 <=或 in 这些都是range。
  5. index
    Full Index Scan,查询全部索引中的数据(比不走索引要快)。其实是查询的字段只包含索引值,用到了覆盖索引,不需要回表。
  6. all 全表扫描。

一般来说,需要保证查询至少达到 range 级别,最好能达到 ref。
ALL(全表扫描)和 index(查询全部索引)都是需要优化的

3.4 possible_key、key

possible_key表示可能用到的索引。
key表示实际用到的索引。

3.5 rows

扫描的行数,是个预估值。越小越好。

3.6 ref

使用哪个列或者常数和索引一起从表中筛选数据。

3.7 Extra

执行计划给出的额外的信息说明

  1. useing index
    使用到了覆盖索引,不需要回表
  2. useing where 使用了 where 过滤,表示存储引擎返回的记录并不是所有的都满足查询条件,需要在 server 层进行过滤
  3. Using index condition 索引条件下推,过滤在存储引擎进行
  4. using filesort 不能使用索引来排序,用到了额外的排序(跟磁盘或文件没有关系)。需要优化。
  5. using temporary
    用到了临时表。例如(以下不是全部的情况):
    • distinct 非索引列
    • group by 非索引列
    • 使用 join 的时候,group 任意列

四、业务流程优化

除了对于代码、SQL 语句、表定义、架构、配置优化之外,业务层面的优化也不能忽视。举几个例子:

  1. 在某一年的双十一,为什么会做一个充值到余额宝和余额有奖金的活动(充 300送 50)? 因为使用余额或者余额宝付款是记录本地或者内部数据库,而使用银行卡付款,需要调用接口,操作内部数据库肯定更快。
  2. 在去年的双十一,为什么在凌晨禁止查询今天之外的账单?
    这是一种降级措施,用来保证当前最核心的业务。
  3. 最近几年的双十一,为什么提前一个多星期就已经有双十一当天的价格了?
    预售分流。
    在应用层面同样有很多其他的方案来优化,达到尽量减轻数据库的压力的目的,比如限流,或者引入 MQ 削峰,等等

五、总结

本章核心是掌握SQL优化。SQL优化需要重点掌握执行计划的各个字段含义。宗旨是保证SQL使用索引。