多方面进行MySQL性能优化(详解篇)

文章目录

    • 1. 连接配置优化
      • 1.1、最大连接数
      • 1.2、数据库连接池大小
      • 1.3、连接超时时间
      • 1.4、查询缓存大小
    • 2、架构优化
      • 2.1、使用缓存
      • 2.2、读写分离(集群、主从复制)
      • 2.4、消息队列削峰
      • 2.5、分布式架构
      • 2.6、避免大事务
      • 2.7、连接池使用
    • 3、存储引擎与表结构
      • 3.1、选择合适的存储引擎
      • 3.2、表结构设计(字段优化)
        • 3.2.1 整数类型
        • 3.2.2 字符类型
        • 3.2.3 非空
        • 3.2.4 不要用外键、触发器和视图功能
        • 3.2.5 图片、音频、视频存储
        • 3.2.6 大字段拆分和数据冗余
    • 4、SQL分析与优化
      • 4.1、SQL优化
        • 4.1.1、查询优化
        • 4.1.2、插入、更新和删除优化
        • 4.1.3、案例
      • 4.2、索引优化
        • 4.2.1、选择合适的索引列
        • 4.2.2、索引类型选择
      • 4.3、慢查询
        • 4.3.1、打开慢日志
        • 4.3.2、慢日志分析
      • 4.4、查看运行中的线程
      • 4.5、查看服务器运行状态
      • 4.6、查看存储引擎运行信息
      • 4.7、EXPLAIN执行计划
    • 5、硬件优化
    • 6、业务优化

MySQL性能优化是一个综合性的过程,涉及多个方面,以下是对MySQL性能优化的详细解析:

在这里插入图片描述

1. 连接配置优化

1.1、最大连接数

如果遇到error 1040: Too many connections的错误?可以通过增加可用连接数来解决;
修改环境变量max_connections,默认情况下服务端的最大连接数为151个mysql

max_connections:设置MySQL服务器允许的最大连接数。应根据服务器硬件和网络连接数量进行调整。

show variables like 'max_connections';

在这里插入图片描述

1.2、数据库连接池大小

innodb_buffer_pool_size:这是InnoDB存储引擎最重要的参数之一,用于管理索引和数据文件缓存的主内存池。应将其设置为足够大以容纳所需的索引和表数据。

尽量减少和服务端建立连接的次数,不要每次执行个SQL语句都创建个新连接。

常见的数据库连接池有DBCP、C3P0、阿里的Druid、Hikari,前两者用得很少了,后两者目前是比较主流的。

连接池并不是越大越好,比如Druid的默认最大连接池大小是8,Hikari默认最大连接池大小是10,盲目地加大连接池的大小,系统执行效率反而有可能降低。

对于每一个连接,服务端会创建一个单独的线程去处理,连接数越多,服务端创建的线程自然也就越多。而线程数超过CPU个数的情况下,CPU势必要通过分配时间片的方式进行线程的上下文切换,频繁的上下文切换会造成很大的性能开销。

Hikari官方给出了一个PostgreSQL数据库连接池大小的建议值公式,CPU核心数*2+1。假设服务器的CPU核心数是4,把连接池设置成9就可以了。这种公式在一定程度上对其他数据库也是适用的,

数据库连接池大小的建议值公式=CPU核心数*2+1

1.3、连接超时时间

wait_timeout:设置连接的超时时间,当一个连接在指定时间内没有活动时,将被自动关闭。

及时释放不活动的连接,系统默认的客户端超时时间是28800秒(8小时),我们可以把这个值调小一点

show variables like 'wait_timeout';

在这里插入图片描述

1.4、查询缓存大小

query_cache_size:启用查询缓存可以提高查询性能,但对于更新频繁的数据库,启用查询缓存可能会降低性能。

2、架构优化

2.1、使用缓存

系统中难免会出现一些比较慢的查询,这些查询要么是数据量大,要么是查询复杂(关联的表多或者是计算复杂),使得查询会长时间占用连接。
在这里插入图片描述

如果这种数据的实效性不是特别强(不是每时每刻都会变化,例如每日报表),我们可以把此类数据放入缓存系统中,在数据的缓存有效期内,直接从缓存系统中获取数据,这样就可以减轻数据库的压力并提升查询效率。

2.2、读写分离(集群、主从复制)

如果应用程序的读操作和写操作比例较大,可以考虑使用读写分离的架构。将读操作和写操作分布到不同的数据库服务器上,主数据库负责写操作,从数据库负责读操作,通过复制机制保持数据同步。通过主从复制实现读写分离,可以分散读压力,提高系统性能。
在这里插入图片描述

可以同时使用多台数据库服务器,将其中一台设置为master节点,其余节点作为slave。用户写数据只往master节点写,而读的请求分摊到各个slave节点上,这样组成读写分离集群。

为保证master节点的写操作也同步到各个slave节点上,主从节点的数据一致性,采用主从复制策略:

binlog日志是实现MySQL主从复制功能的核心组件。master节点会将所有的写操作记录到binlog中,slave节点会有专门的I/O线程读取master节点的binlog,将写操作同步到当前所在的slave节点。
在这里插入图片描述
这种集群的架构对减轻主数据库服务器的压力有非常好的效果,但是随着业务数据越来越多,如果某张表的数据量急剧增加,单表的查询性能就会大幅下降,而这个问题是读写分离也无法解决的,毕竟所有节点存放的是一模一样的数据啊,单表查询性能差,说的自然也是所有节点性能都差。这时我们可以把单个节点的数据分散到多个节点上进行存储,这就是分库分表。

水平分,主要是为了解决存储的瓶颈;
垂直分,主要是为了减轻并发压力。

2.4、消息队列削峰

通常情况下,用户的请求会直接访问数据库,如果同一时刻在线用户数量非常庞大,极有可能压垮数据库(参考明星出轨或公布恋情时微博的状态)。
在这里插入图片描述

这种情况下可以通过使用消息队列降低数据库的压力,不管同时有多少个用户请求,先存入消息队列,然后系统有条不紊地从消息队列中消费请求。

2.5、分布式架构

对于高并发场景,使用分布式架构可以有效地提高系统的性能和扩展性。

2.6、避免大事务

大事务会占用大量资源,并可能导致死锁,应尽量避免。

2.7、连接池使用

使用连接池可以管理数据库连接,避免频繁地创建和销毁连接。连接池在应用程序启动时创建一定数量的连接,并在需要时将连接分配给请求,使用完后将连接归还到池中,提高连接的复用率。

3、存储引擎与表结构

3.1、选择合适的存储引擎

MySQL支持多种存储引擎,如InnoDB、MyISAM等,每种存储引擎都有其特点和适用场景,应根据实际需求选择合适的存储引擎。

  1. nnoDB 存储引擎:InnoDB 是 MySQL 默认的存储引擎,具有良好的事务支持、行级锁定和外键约束等特性。 调整innodb_log_file_size参数可以控制 InnoDB 日志文件的大小,较大的日志文件可以减少日志切换的频率,提高性能。
  2. MyISAM 存储引擎:MyISAM 存储引擎在某些特定情况下仍然有使用价值,比如对于只读操作非常频繁且不需要事务支持的场景。

一般情况下,我们会选择MySQL默认的存储引擎存储引擎InnoDB,但是当对数据库性能要求精益求精的时候,存储引擎的选择也成为一个关键的影响因素。

建议根据不同的业务选择不同的存储引擎,

  • 例如:查询操作、插入操作多的业务表,推荐使用MyISAM;
  • 临时表使用Memory;
  • 并发数量大、更新多的业务选择使用InnoDB;
  • 不知道选啥直接默认

3.2、表结构设计(字段优化)

  1. 避免过多列:尽量保持表的列数适中,过多的列可能导致数据存储和检索效率低下。
  2. 合理选择数据类型:使用最小的数据类型满足存储需求,例如,如果一个整数列的值范围在 0 - 255 之间,使用 TINYINT 类型而不是 INT 类型。 对于字符串列,根据实际字符串长度选择合适的数据类型,如 VARCHAR 或 CHAR。VARCHAR 适用于长度可变的字符串,而 CHAR 适用于长度固定的字符串。
  3. 使用适当的范式:在设计表结构时,遵循数据库范式可以减少数据冗余,但过度的范式化可能导致过多的关联查询,影响性能。在某些情况下,可以适当反范式化,将一些经常一起查询的数据合并到一张表中。
  4. 垂直分割:将表中的字段按照访问频率、是否经常一起查询等因素进行分割,减少不必要的重复数据。

字段优化的最终原则是:使用可以正确存储数据的最小的数据类型。

3.2.1 整数类型

MySQL提供了6种整数类型,分别是

  • tinyint
  • smallint
  • mediumint
  • int
  • integer
  • bigint
    不同的存储类型的最大存储范围不同,占用的存储的空间自然也不同。
    例如,是否被删除的标识,建议选用tinyint,而不是bigint。
3.2.2 字符类型

你是不是直接把所有字符串的字段都设置为varchar格式了?甚至怕不够,还会直接设置成varchar(1024)的长度?

如果不确定字段的长度,肯定是要选择varchar,但是varchar需要额外的空间来记录该字段目前占用的长度;因此如果字段的长度是固定的,尽量选用char,这会给你节约不少的内存空间。

3.2.3 非空

非空字段尽量设置成NOT NULL,并提供默认值,或者使用特殊值代替NULL。因为NULL类型的存储和优化都会存在性能不佳的问题,具体原因在这里就不展开了。

3.2.4 不要用外键、触发器和视图功能

这也是「阿里巴巴开发手册」中提到的原则。原因有三个:

  1. 降低了可读性,检查代码的同时还得查看数据库的代码;
  2. 把计算的工作交给程序,数据库只做好存储的工作,并把这件事情做好;
  3. 数据的完整性校验的工作应该由开发者完成,而不是依赖于外键,一旦用了外键,你会发现测试的时候随便删点垃圾数据都变得异常艰难。
3.2.5 图片、音频、视频存储

不要直接存储大文件,而是要存储大文件的访问地址。

3.2.6 大字段拆分和数据冗余
  • 大字段拆分其实就是前面说过的垂直分表,把不常用的字段或者数据量较大的字段拆分出去,避免列数过多和数据量过大,尤其是习惯编写SELECT *的情况下,列数多和数据量大导致的问题会被严重放大!
  • 字段冗余原则上不符合数据库设计范式,但是却非常有利于快速检索。比如,合同表中存储客户id的同时可以冗余存储客户姓名,这样查询时就不需要再根据客户id获取用户姓名了。因此针对业务逻辑适当做一定程度的冗余也是一种比较好的优化技巧。

4、SQL分析与优化

4.1、SQL优化

4.1.1、查询优化
  1. 避免使用SELECT *:尽量只选择需要的列,避免使用SELECT *,以减少数据传输量和处理时间。
  2. 合理使用LIMIT:当查询结果集较大时,使用LIMIT限制返回的行数,避免一次性返回大量数据。
  3. 优化JOIN和子查询:尽量使用JOIN来代替子查询,因为JOIN通常比子查询更高效。
  4. 优化WHERE子句,避免全表扫描:确保WHERE子句中的条件能够使用索引,避免全表扫描。
  5. 避免使用OR和NOT IN:这些操作可能会导致全表扫描,影响查询性能。
  6. 使用EXPLAIN分析查询:通过EXPLAIN命令查看查询的执行计划,了解MySQL是如何执行查询的,从而找出性能瓶颈。
4.1.2、插入、更新和删除优化
  1. 批量操作:当需要插入、更新或删除大量数据时,尽量使用批量操作,减少与数据库的交互次数。例如,在插入数据时,使用多条 VALUES 语句一次性插入多行数据。
  2. 避免频繁更新索引列:因为每次更新索引列都会导致索引的重新构建,影响性能。如果可能,将经常更新的列放在索引之外
4.1.3、案例

SQL优化指的是SQL本身语法没有问题,但是有实现相同目的的更好的写法。比如:

  • 使用小表驱动大表;用join改写子查询;or改成union
  • 连接查询中,尽量减少驱动表的扇出(记录数),访问被驱动表的成本要尽量低,尽量在被驱动表的连接列上建立索引,降低访问成本;被驱动表的连接列最好是该表的主键或者是唯一二级索引列,这样被驱动表的成本会降到更低
  • 大偏移量的limit,先过滤再排序

针对最后一条举个简单的例子,下面两条语句能实现同样的目的,但是第二条的执行效率比第一条执行效率要高得多(存储引擎使用的是InnoDB),大家感受一下:

-- 1. 大偏移量的查询
mysql> SELECT * FROM user_innodb LIMIT 9000000,10;
Empty set (8.18 sec)

-- 2.先过滤ID(因为ID使用的是索引),再limit
mysql> SELECT * FROM user_innodb WHERE id > 9000000 LIMIT 10;
Empty set (0.02 sec)

4.2、索引优化

4.2.1、选择合适的索引列
  1. 创建合适的索引:根据查询条件创建合适的索引,可以显著提高查询效率。经常在 WHERE、JOIN 和 ORDER BY 子句中使用的列应该创建索引。
  2. 避免过多索引:虽然索引可以提高查询效率,但过多的索引会占用磁盘空间,并降低写入性能,同时过多的索引会增加数据插入、更新和删除的成本,因为每次数据变更都需要更新相关的索引。
  3. 使用复合索引:对于经常一起查询的列,可以创建复合索引以提高查询速度。
  4. 覆盖索引:如果查询的列都在索引中,MySQL可以直接通过索引获取数据,而不需要回表查询,这称为覆盖索引。
  5. 避免在数据值分布非常不均匀的列上创建索引:例如,一个列的大部分值都是相同的,索引的效果可能不明显。
  6. 使用复合索引:对于经常一起查询的列,可以创建复合索引以提高查询速度。
4.2.2、索引类型选择
  1. B - Tree 索引:是 MySQL 中最常用的索引类型,适用于大多数情况,包括等值查询、范围查询和排序操作。
  2. Hash 索引:适用于等值查询,但不支持范围查询和排序操作。在内存表(MEMORY 存储引擎)中,Hash 索引可以提供非常快的查询速度。

4.3、慢查询

慢查询就是执行地很慢的查询(这句话说得跟废话似的。。。),只有知道MySQL中有哪些慢查询我们才能针对性地进行优化。

因为开启慢查询日志是有性能代价的,因此MySQL默认是关闭慢查询日志功能,使用以下命令查看当前慢查询状态

mysql> show variables like 'slow_query%';
+---------------------+--------------------------------------+
| Variable_name       | Value                                |
+---------------------+--------------------------------------+
| slow_query_log      | OFF                                  |
| slow_query_log_file | /var/lib/mysql/9e74f9251f6c-slow.log |
+---------------------+--------------------------------------+
2 rows in set (0.00 sec)

slow_query_log表示当前慢查询日志是否开启,slow_query_log_file表示慢查询日志的保存位置。

除了上面两个变量,我们还需要确定“慢”的指标是什么,即执行超过多长时间才算是慢查询,默认是10S,如果改成0的话就是记录所有的SQL。

mysql> show variables like '%long_query%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)
4.3.1、打开慢日志

有两种打开慢日志的方式

  1. 修改配置文件my.cnf
    此种修改方式系统重启后依然有效
# 是否开启慢查询日志
slow_query_log=ON
# 
long_query_time=2
slow_query_log_file=/var/lib/mysql/slow.log
  1. 动态修改参数(重启后失效)
mysql> set @@global.slow_query_log=1;
Query OK, 0 rows affected (0.06 sec)

mysql> set @@global.long_query_time=2;
Query OK, 0 rows affected (0.00 sec)
4.3.2、慢日志分析

mySQL不仅为我们保存了慢日志文件,还为我们提供了慢日志查询的工具mysqldumpslow,为了演示这个工具,我们先构造一条慢查询:

mysql> SELECT sleep(5);

然后我们查询用时最多的1条慢查询:

[root@aaa ~]# mysqldumpslow -s t -t 1 -g 'select' /var/lib/mysql/aaa-slow.log

Reading mysql slow query log from /var/lib/mysql/aaa-slow.log
Count: 1  Time=10.00s (10s)  Lock=0.00s (0s)  Rows=1.0 (1), root[root]@localhost
  SELECT sleep(N)

其中,

  • Count:表示这个SQL执行的次数
  • Time:表示执行的时间,括号中的是累积时间
  • Locks:表示锁定的时间,括号中的是累积时间
  • Rows:表示返回的记录数,括号中的是累积数
    更多关于mysqldumpslow的使用方式,可以查阅官方文档,或者执行mysqldumpslow --help寻求帮助。

4.4、查看运行中的线程

我们可以运行show full processlist查看MySQL中运行的所有线程,查看其状态和运行时间,找到不顺眼的,直接kill。
在这里插入图片描述
其中,

  • Id:线程的唯一标志,可以使用Id杀死指定线程
  • User:启动这个线程的用户,普通账户只能查看自己的线程
  • Host:哪个ip和端口发起的连接
  • db:线程操作的数据库
  • Command:线程的命令
  • Time:操作持续时间,单位秒
  • State:线程的状态
  • Info:SQL语句的前100个字符

4.5、查看服务器运行状态

使用SHOW STATUS查看MySQL服务器的运行状态,有session和global两种作用域,一般使用like+通配符进行过滤。

-- 查看select的次数
mysql> SHOW GLOBAL STATUS LIKE 'com_select';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Com_select    | 168241 |
+---------------+--------+
1 row in set (0.05 sec)

4.6、查看存储引擎运行信息

SHOW ENGINE用来展示存储引擎的当前运行信息,包括事务持有的表锁、行锁信息;事务的锁等待情况;线程信号量等待;文件IO请求;Buffer pool统计信息等等数据。
例如:

在这里插入图片描述
上面这条语句可以展示innodb存储引擎的当前运行的各种信息,大家可以据此找到MySQL当前的问题,限于篇幅不在此意义说明其中信息的含义,大家只要知道MySQL提供了这样一个监控工具就行了,等到需要的时候再来用就好。

4.7、EXPLAIN执行计划

通过慢查询日志我们可以知道哪些SQL语句执行慢了,可是为什么慢?慢在哪里呢?

MySQL提供了一个执行计划的查询命令EXPLAIN,通过此命令我们可以查看SQL执行的计划,所谓执行计划就是:优化器会不会优化我们自己书写的SQL语句(比如外连接改内连接查询,子查询优化为连接查询…)、优化器针对此条SQL的执行对哪些索引进行了成本估算,并最终决定采用哪个索引(或者最终选择不用索引,而是全表扫描)、优化器对单表执行的策略是什么,等等等等。

EXPLAIN在MySQL5.6.3之后也可以针对UPDATE、DELETE和INSERT语句进行分析,但是通常情况下我们还是用在SELECT查询上。

5、硬件优化

  1. 足够的内存:MySQL使用缓冲区来缓存数据和索引文件,因此需要足够的RAM来确保所有数据都能被缓存。
  2. 快速的存储设备:使用更快的存储设备(如SSD)可以提高IO吞吐量,减少延迟。
  3. 使用RAID:将数据分布在多个磁盘上以提高磁盘读写性能和可靠性。

6、业务优化

严格来说,业务方面的优化已经不算是MySQL调优的手段了,但是业务的优化却能非常有效地减轻数据库访问压力,这方面一个典型例子就是淘宝,下面举几个简单例子给大家提供一下思路:

  1. 以往都是双11当晚开始买买买的模式,最近几年双11的预售战线越拉越长,提前半个多月就开始了,而且各种定金红包模式丛出不穷,这种方式叫做预售分流。这样做可以分流客户的服务请求,不必等到双十一的凌晨一股脑地集体下单;
  2. 双十一的凌晨你或许想查询当天之外的订单,但是却查询失败;甚至支付宝里的小鸡的口粮都被延迟发放了,这是一种降级策略,集结不重要的服务的计算资源,用来保证当前最核心的业务;
  3. 双十一的时候支付宝极力推荐使用花呗支付,而不是银行卡支付,虽然一部分考量是提高软件粘性,但是另一方面,使用余额宝实际使用的阿里内部服务器,访问速度快,而使用银行卡,需要调用银行接口,相比之下操作要慢了许多。

综上所述,MySQL性能优化是一个综合性的过程,需要从多个方面入手进行优化。可以显著提高MySQL的性能和稳定性。

在这里插入图片描述


每一次的跌倒,都是对未来的一次深情拥抱。