史上最全MySQL性能优化总结

1. 优化思路

作为架构师或者开发人员,说到数据库性能优化,你的思路是什么样的?
如果在面试的时候遇到这个问题:你会从哪些维度来优化数据库,你会怎么回答?

说到性能调优,大部分时候想要实现的目标是让我们的査询更快。一个査询的流程又是由很多个环节组成的,每个环节都会消耗时间。

mysql架构图:
在这里插入图片描述
我们要减少查询所消耗的时间,就要从每一个环节入手。
先确认一下环境:

select version();
show variables like '%engine%';

2. 连接一一配置优化

第一个环节是客户端连接到服务端,连接这一块有可能会出现什么样的性能问题?有可能是服务端连接数不够导致应用程序获取不到连接。
比如我们遇到过的

Mysql: error 1040: Too many connections的错误。

这个是超过了服务端设置的最大并发连接数。

可以从两个方面来解决连接数不够的问题:

  1. 从服务端来说,我们可以增加服务端的可用连接数。如果有多个应用或者很多请求同时访问数据库,连接数不够的时候,我们可以:

    • 增加可用连接数,修改max_connections的大小:
      show variables like 'max_connections';-- 修改最大连接数,当有多个应用连接的时候
      
    • 及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是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是怎么同时执行远远超过它的核数大小的任务的?分配时间片。上下文切换。
而CPU的核数是有限的,频繁的上下文切换会造成比较大的性能开销。

我们这里说到了从数据库配置的层面去优化数据库。不管是数据库本身的配置,还是安装这个数据库服务的操作系统的配置,对于配置进行优化,最终的目标都是为了更好地发挥硬件本身的性能,包括CPU、内存、磁盘、网络。
在不同的硬件环境下,操作系统和MySQL的参数的配置是不同的,没有标准的配置。

很多的MySQL和InnoDB的配置参数,包括各种开关和数值的配置,大多数参数都提供了一个默认值,比如默认的页大小,连接数,日志文件大小等等。这些默认配置可以满足大部分情况的需求,除非有特殊的需求,在清楚参数的含义的情况下再去修改它。修改配置的工作一般由专业的DBA完成。也有一些工具可以给出推荐值。

至于硬件本身的选择,比如使用固态硬盘,搭建磁盘阵列,选择特定的CPU型号这些,更不是我们开发人员关注的重点,这个我们就不做过多的介绍了。回到连接的这个话题。

3. 架构优化

3.1 缓存

除了合理设置服务端的连接数和客户端的连接池大小之外,我们还有哪些减少客户端使用的连接数的方案呢?
我们可以引入缓存。

在系统里面有一些很慢的查询,要么是数据量大,要么是关联的表多,要么是计算逻辑非常复杂,这样的查询每次会占用连接很长的时间。

所以为了减轻数据库的压力,和提升查询效率,我们可以把数据放到内存缓存起来, 比如使用Redis

缓存适用于实时性不是特别高的业务,例如报表数据,一次查询要2分钟,但是一天只需要更新一次。

运行独立的缓存服务,属于架构层面的优化。

3.2 Mysql集群,主从复制

有一种提升可用性的手段,叫做冗余,也就是创建集群。
集群的话必然会面临一个问题,就是不同的节点之间数据一致性的问题。如果同时读写多台数据库节点,怎么让所有的节点数据保持一致?
这个时候我们需要用到复制技术(replication),被复制的节点称为master,复制的节点称为slave。slave本身也可以作为其他节点的数据来源,这个叫做级联复制。

在这里插入图片描述
MySQL的主从复制是怎么实现的呢?
MySQL所有更新语句都会记录到Server层的binlog。
有了这个binlog,从服务器会不断获取主服务器的binlog文件,然后解析里面的SQL语句,在从服务器上面执行一遍,保持主从的数据一致。

在这里插入图片描述
这里面涉及到三个线程:

  • I/O线程: 连接到master获取bin log,并且解析bin log写入中继日志。
  • log dump线程: 在Master节点上,是用来发送bin log给slave的。
  • 从库的SQL线程: 是用来读取relay log,把数据写入到数据库的。

注:(主从同步配置和原理,详细内容见Mycat专栏)
做了主从复制配置案之后,我们只把数据写入master节点,而读的请求可以分担到slave节点。我们把这种方案叫做读写分离。

对于读多写少的项目来说,读写分离对于减轻主服务器的访问压力很有用。在集群的架构中,所有的节点存储的都是相同的数据。如果单张表存储的数据过大的时候,比如一张表有上亿的数据,每天以百万的量级增加,单表的查询性能还是会大幅下降。这个时候我们应该怎么办呢?
这个时候就要用到分布式架构中的第二个重要的手段,叫做分片。把单个节点的数据分散到多个节点存储,减少存储和访问压力,这个就是分库分表。

3.3 分库分表

分库分表总体上可以分为两类。

  • 垂直分库,减少并发压力。
  • 水平分表,解决存储瓶颈。

垂直分库的做法,把一个数据库按照业务拆分成不同的数据库:
在这里插入图片描述
水平分库分表的做法,把单张表的数据按照一定的规则分布到多个数据库。

在这里插入图片描述

以上是架构层面的优化,可以用缓存,读写分离,分库分表。
这些措施都可以减轻服务端的访问压力,提升客户端的响应效率。

第三个环节:
解析器,词法和语法分析,主要保证语句的正确性,语句不出错就没问题。跳过。
第四步:优化器

4 SQL语句优化

4.1 SQL语句优化的配置

优化器的作用就是对我们的SQL语句进行优化分析,生成执行计划。
问题: 在我们做项目的时候,有时会收到DBA的邮件,里面列出了我们项目上几个耗时比较长的查询语句,让我们去优化,这些语句是从哪里来的呢?

我们的服务层每天执行了这么多SQL语句,它怎么知道哪些SQL语句比较慢呢?

第一步,我们要把SQL执行情况记录下来,用到服务端的慢查询日志。

4.1.1 慢查询日志slow query log

打开慢日志开关
因为开启慢查询日志是有代价的(跟binlog、optimizer-trace —样),所以它默认是关闭的:

show variables like 'slow_query%';

在这里插入图片描述
除了这个开关,还有一个参数,控制执行超过多长时间的SQL才记录到慢日志,默认是10秒。如果改成0秒的话就是记录所有的SQL。

show variables like '%long_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.cnf
    以下配置定义了慢査询日志的开关、慢査询的时间、日志文件的存放路径。

    slowquerylog = ON 
    long_query_time=2 
    slow_query_log_file =/var/lib/mysql/localhost-slow.log
    

模拟慢査询:

select sleep(10);

査询user_innodb表的500万数据,不使用索引。

SELECT * FROM user_innodb where phone = '136';

慢日志分析

  1. 日志内容
less /var/lib/mysql/localhost-slow.log
#Time^ 2020-05-12T05:59:59.373938Z
#User@Host: root [root] @	[192.168.44.1] Id:	30
# Query_time: 229.418907 Lock_time: 0.000275 Rows_sent: 0
SET timestamp=1589263199;
DELETE from test_data;

有了慢查询日志,怎么去分析统计呢?比如哪条SQL语句的出现的慢查询次数最多,平均每次执行了多久?人工肉眼分析显然不可能。

  1. mysqldumpslow
    MySQL提供了 mysqldumpslow的工具,在MySQL的bin目录下。
    	mysqldumpslow —-help
    	```
    
    

例如:查询用时最多的10条慢SQL:

mysqldumpslow -s t -t 10 -g 'select' /var/lib/mysql/localhost-slow.log
  • Count代表这个SQL执行了多少次;
  • Time代表执行的时间,括号里面是累计时间;
  • Lock表示锁定的时间,括号是累计;
  • Rows表示返回的记录数,括号是累计。

当然,有的时候查询慢,不一定是SQL语句的问题,也有可能是服务器状态的问题。 所以我们也要掌握一些查看服务器和存储引擎状态的命令。

其他系统命令

  1. show processlist 运行线程
show full processlist;

这是很重要的一个命令,用于显示用户运行线程。可以根据id号kill线程。也可以查表,效果一样:

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 STATUS 用于查看MySQL服务器运行状态(重启后会清空)。

SHOW GLOBAL STATUS;

有session和global两种作用域,格式:参数-值。
可以用like带通配符过滤。

SHOW GLOBAL STATUS LIKE 'com_select';--查看 select 次数

show engine 存储引擎运行信息

show engine用来显示存储引擎的当前运行信息,包括事务持有的表锁、行锁信息;事务的锁等待情况;线程信号量等待;文件IO请求;buffer pool统计信息。

例如

show engine innodb status;

开启InnoDB监控:
-开启标准监控和锁监控

set GLOBAL innodb_status_output=ON;
set GLOBAL innodb_status_output_locks=ON;	

很多开源的MySQL监控工具,其实他们的原理也都是读取的服务器、操作系统、MySQL服务的状态和变量。
现在我们已经知道哪些SQL慢了,为什么慢呢?慢在哪里?
MySQL提供了一个执行计划的工具。通过EXPLAIN我们可以模拟服务端执行SQL 查询语句的过程。通过这种方式我们可以分析语句或者表的性能瓶颈。

explain 可以分析 update、delete、insert 么?
MySQL 5.6.3 以前只能分析 SELECT; MySQL5.6.3 以后就可以分析 update、delete, insert 了。

4.2 EXPLAIN执行计划

我们先创建三张表。一张课程表,一张老师表,一张老师联系方式表(没有任何索引)。

DROP TABLE IF EXISTS course;
CREATE TABLE `course` ( `cid` int(3) DEFAULT NULL, `cname` varchar(20) DEFAULT NULL, `tid` int(3) DEFAULT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS teacher;

CREATE TABLE `teacher` ( `tid` int(3) DEFAULT NULL, `tname` varchar(20) DEFAULT NULL, `tcid` int(3) DEFAULT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS teacher contact;

CREATE TABLE `teacher_contact` (
`tcid` int(3) DEFAULT NULL, `phone` varchar(200) DEFAULT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO 'course' VALUES (1, ‘mysql’,'1');
INSERT INTO 'course VALUES (2, jvm', '1');
INSERT INTO 'course VALUES (3, juc', '2');
INSERT INTO 'course VALUES (4, 'spring', '3');

INSERT INTO 'teacher' VALUES (1, 'xiaoming', '1');
INSERT INTO 'teacher VALUES (2, 'laowang' '2');
INSERT INTO 'teacher VALUES (3, 'tom' '3');

INSERT INTO 'teacher contact' VALUES ('1', '13688888888');
INSERT INTO 'teacher_contact' VALUES ('2', '18166669999');
INSERT INTO 'teacher contact' VALUES ('3','17722225555');

explain的结果有很多的字段,我们详细地分析一下。

4.2.1 id

id是查询序列编号,每张表都是单独访问的,一个SELECT就会有一个序号。
1. id值不同的时候,先査询id值大的 (先大后小)

-- 查询 mysql课程的老师手机号 
EXPLAIN
SELECT tc.phone
FROM teacher_contact tc
WHERE tcid = (SELECT tcid
              FROM teacher t
              WHERE t.tid = (SELECT c.tid
                             FROM course c
                             WHERE c.cname = 'mysql'));

查询顺序:course c—> teacher t -----> teacher_contact tc
在这里插入图片描述
先查课程表,再査老师表,最后查老师联系方式表。子査询只能以这种方式进行, 只有拿到内层的结果之后才能进行外层的查询。

2. id值相同时

-- 查询课程ID为2,或者联系表ID为3的老师 
EXPLAIN
SELECT t.tname, c.cname, tc.phone
FROM teacher t,
     course c,
     teacher_contact tc
WHERE tid = c.tid
  AND t.tcid = tc.tcid
  AND (c.cid = 2 OR tc.tcid = 3);

在这里插入图片描述
id值相同时:表的查询顺序是从上往下顺序执行。例如这次查询的id都是1 (说明子査询被优化器转换成了连接査询)
査询的顺序是teacher —(3条)— course c (4条)— teacher contact tc (3 条)。

在连接查询中,先查询的叫做驱动表,后查询的叫做被驱动表
应该先查小表(得到结果少的表)还是大表(得到结果多的表)? 我们肯定要把小表放在前面查询,因为它的中间结果最少。(小标驱动大表的思想)

3. 既有相同也有不同
如果ID有相同也有不同,就是ID不同的先大后小,ID相同的从上往下。

4.2.2 select type (查询类型)

这里并没有列举全部(其它:DEPENDENT UNION. DEPENDENT SUBQUERY.MATERIALIZED. UNCACHEABLE SUBQUERY. UNCACHEABLE UNION)。

下面列举了一些常见的查询类型:

  • SIMPLE:简单査询,不包含子查询,不包含关联査询union。
EXPLAIN SELECT * FROM teacher;

再看一个包含子査询的案例:

-- 查询mysql课程的老师手机号
EXPLAIN
SELECT tc.phone
FROM teacher_contact tc
WHERE tcid = (
    SELECT tcid
    FROM teacher t
    WHERE t.tid = (
        SELECT c.tid
        FROM course c
        WHERE c.cname = 'mysql')
);
  • PRIMARY: 子查询SQL语句中的主查询,也就是最外面的那层查询。
  • SUBQUERY: 子査询中所有的内层查询都是SUBQUERY类型的。
  • DERIVED: 衍生査询,表示在得到最终查询结果之前会用到临时表。例如:
-- 查询ID为1或2的老师教授的课程
EXPLAIN SELECT cr.cname
FROM(
SELECT * FROM course WHERE tid = 1
UNION
SELECT * FROM course WHERE tid = 2
)cr;

在这里插入图片描述
对于关联査询,先执行右边的table (UNION),再执行左边的table,类型是DERIVED

  • UNION: 用到了 UNION查询。同上例。
  • UNION RESULT: 主要是显示哪些表之间存在UNION査询。<union2,3>代表id=2和id=3的査询存在UNION。同上例。

4.2.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的一种特例,只有一行满足条件,对于MylSAM、Memory的表, 只查询到一条记录,也是system。比如系统库的这张表(8.0的版本中系统表全部变成 InnoDB存储引擎了):
  • eq_ref: 通常出现在多表的join査询,被驱动表通过唯一性索引(UNIQUE或PRIMARY
    KEY)进行访问,此时被驱动表的访问方式就是eq_ref。eqref是除const之外最好的访问类型。

小结: 以上三种system, const, eq_ref,都是可遇而不可求的,基本上很难优化到这个状态。

  • ref: 查询用到了非唯一性索引,或者关联操作只使用了索引的最左前缀。
  • range: 索引范围扫描。如果where后面是between and 或 < 或 > 或 >= 或 <= 或 in 这些,type类型就为range。不走索引一定是全表扫描(ALL),所以先加上普通索引。
  • index: Full Index Scan,查询全部索引中的数据(比不走索引要快)。
  • all: Full Table Scan,如果没有索引或者没有用到索引,type就是ALL。代表全表扫描。
  • NULL: 不用访问表或者索引就能得到结果,例如:
EXPLAIN select 1 from dual where 1=1;

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

4.2.4 possible_key 、 key

可能用到的索引和实际用到的索引。如果是NULL就代表没有用到索引。 possible_key可以有一个或者多个,可能用到索引不代表一定用到索引。 反过来,possible_key为空,key可能有值吗?

表上创建联合索引:

ALTER TABLE user innodb DROP INDEX comidx_name_phone;
ALTER TABLE user innodb add INDEX comidx_name_phone (name,phone);

执行计划(改成select name也能用到索引):

explain select phone from user innodb where phone='126';

在这里插入图片描述
结论:是有可能的(这里是覆盖索引的情况)。
如果通过分析发现没有用到索引,就要检査SQL或者创建索引。

4. 2. 5 key_len

索引的长度(使用的字节数)。跟索引字段的类型、长度有关。
表上有联合索引 : KEY ‘comidx_name_phone’ (‘name’,‘phone’)

explain select * from user innodb where name ='赵四';

key_len =1023,为什么不是 255 + 11=266 呢?
这里的索弓I只用到了 name字段,定义长度255。
utf8mb4编码1个字符4个字节。所以是255*4=1020。
使用变长字段varchar需要额外增加2个字节,允许NULL需要额外增加1个字节。所以一共是1023。

4.2.6 rows

MySQL认为扫描多少行才能返回请求的数据,是一个预估值。一般来说行数越少越好。

4.2.7 filtered

这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,它是一个百分比
在这里插入图片描述
如果比例很低,说明存储引擎层返回的数据需要经过大量过滤,这个是会消耗性能的,需要关注。

4.2.8 ref

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

4.2.9 Extra

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

  • using index: 用到了覆盖索引,不需要回表。

  • using where: 使用了where过滤,表示存储引擎返回的记录并不是所有的都满足查询条件,需要 在server层进行过滤(跟是否使用索引没有关系)。

  • Using index condition (索引条件下推)

  • using filesort: 不能使用索引来排序,用到了额外的排序(跟磁盘或文件没有关系)。需要优化。

  • using temporary: 用到了临时表。例如(以下不是全部的情况):

    1、distinct非索引列

    EXPLAIN select DISTINCT(tid) from teacher t;
    

    2、group by非索引列

    EXPLAIN select tname from teacher group by tname;
    

    3、使用join的时候,group任意列(t表的结果)

    EXPLAIN select t.tid 
    from teacher t 
    join course c on t.tid = c.tid 
    group by t.tid;
    

    需要优化,例如创建复合索引。

如果需要具体的cost信息,可以用:

EXPLAIN FORMAT=JSON。

如果觉得EXPLAIN还不够详细/可以用开启optimizer trace。

SHOW VARIABLES LIKE 'optimizer_trace'; 
set optimizer_trace='enabled=on';
select * from information_schema.optimizer_trace\G

总结: explain模拟优化器执行SQL查询语句的过程,来知道MySQL是怎么处理一条SQL语句的。

通过这种方式我们可以分析语句或者表的性能瓶颈。
分析出问题之后,就是对SQL语句的具体优化。
比如怎么用到索引,怎么减少锁的阻塞等待。

4.3 SQL与索引优化

当我们的SQL语句比较复杂,有多个关联和子査询的时候,就要分析SQL语句有没有改写的方法。

举个简单的例子,一模一样的数据:

-- 大偏移量的limit
select * from user_innodb limit 900000,10;
-- 改成先过滤ID,再limit
SELECT * FROM user_innodb WHERE id > 900000 LIMIT 10;

对于具体的SQL语句的优化,MySQL官网也提供了很多建议,这个是我们在分析具体的SQL语句的时候需要注意的,在以后的工作里面要去慢慢地积累的。
这一步是SQL语句的优化,目的是让SQL语句的的cost更小。

5 存储引擎

5.1 存储引擎的选择

为不同的业务表选择不同的存储引擎,例如:查询插入操作多的业务表,用MylSAMo。临时数据用Memeroy。常规的并发大更新多的表用InnoDB。

5.2 分表或者分区

交易历史表:在年底为下一年度建立12个分区,每个月一个分区。
渠道交易表:分成:当日表、当月表、历史表,历史表再做分区。

5.3字段定义

原则:使用可以正确存储数据的最小数据类型。
为每一列选择合适的字段类型:

5.3.1 整数类型

INT有6种类型,(tinyint,smallint,mediumint,int,integer,bigint,bit)不同的类型的最大存储范围是不一样的,占用的存储空间也是不
一样的。
举例:存储性别字段?用TINYINT,因为ENUM也是整数存储。

5.3.2 字符类型

变长情况下,varchar更节省空间,但是对于varchar字段,需要一个字节来记录长度。比如:联系地址。

固定长度的用char,不要用varchar。比如:行政区划编码。

5.3.3 非空

非空字段尽量定义成NOT NULL,提供默认值,或者使用特殊值、空串代替null。 NULL类型的存储、优化、使用都会存在问题。

5.3.4 不要用外键、触发器、视图

降低了可读性;影响数据库性能,应该把把计算的事情交给程序,数据库专心做存储;数据的完整性应该在程序中检查。

5.3.5 大文件存储

图片和音频、视频怎么存储?
不要用数据库存储图片(比如base64编码)或者用二进制编码,或者大文件。
把文件放在NAS上,数据库只需要存储URI (相对路径),在应用中配置NAS服务器地址。

5.3.6 表拆分或者字段冗余

表拆分:
将不常用的字段拆分出去,避免列数过多和数据量过大。
淘宝的商品表。商户信息表。
比如在业务系统中,要记录所有接收和发送的消息,这个消息是XML格式的,用 blob或者text存储,用来追踪和判断重复,可以建立一张表专门用来存储报文。

字段冗余:
合同表的客户姓名。

6.总结:优化体系

所以,如果在面试的时候再问到这个问题"你会从哪些维度来优化数据库",你会怎么回答?

优化数据库的方面
SQL与索引
存储引擎与表结构
数据库架构
Mysql配置
硬件与操作系统

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

  1. 在某一年的双十一,为什么会做一个充值到余额宝和余额有奖金的活动?现在会推荐大家用花呗支付,而不是银行卡支付?
    因为使用余额或者余额宝付款是记录本地或者内部数据库,而使用银行卡付款,需要调用接口,操作内部数据库肯定更快。
  2. 在某一年的双十一,为什么在凌晨禁止查询今天之外的账单?为什么小鸡的饲料发放延迟了?
    这是一种降级措施,用来保证当前最核心的业务。
  3. 某银行的交易记录,只能按月份查询。
  4. 最近几年的双十一,为什么11月1日就开始了?变成了各种定金红包模式?
    预售分流。

在应用层面同样有很多其他的方案来优化,达到尽量减轻数据库的压力的目的,比如限流,或者引入MQ削峰,等等。

为什么同样用MySQL,有的公司可以抗住百万千万级别的并发,而有的公司几百个并发都扛不住,关键在于怎么用。所以,用数据库慢,不代表数据库本身慢,有的时候还要往上层去优化。

当然,如果关系型数据库解决不了的问题,我们可能需要用到搜索引擎或者大数据的方案了,并不是所有的数据都要放到关系型数据库存储。

7. 优化案例

服务端状态分析:
如果出现连接变慢,查询被阻塞,无法获取连接的情况。

  1. 重启!
  2. show processlist查看线程状态,连接数数量、连接时间、状态
  3. 查看锁的状态
  4. kill有问题的线程

对于具体的慢SQL:

一、分析查询基本情况
涉及到的表的表结构,字段的索引情况、每张表的数据量、查询的业务含义。
这个非常重要,因为有的时候你会发现SQL根本没必要这么写,或者表设计是有问题的。

二、 找出慢的原因

  1. 查看执行计划,分析SQL的执行情况,了解表访问顺序、访问类型、索引、扫描行数等信息。
  2. 如果总体的时间很长,不确定哪一个因素影响最大,通过条件的增减,顺序的调整,找出引起查询慢的主要原因,不断地尝试验证。

找到原因:比如是没有走索引引起的,还是关联查询引起的,还是。order by引起的。
找到原因之后:

三、 对症下药

  1. 创建索引或者联合索引
  2. 改写SQL,这里需要平时积累经验,例如:
    1) 使用小表驱动大表
    2) 用join来代替子查询
    3) not exist 转换为 left join IS NULL
    4) or 改成 union
    5) 如果结果集允许重复的话,使用UNION ALL代替UNION
    6) 大偏移的limit,先过滤再排序。
    如果SQL本身解决不了了,就要上升到表结构和架构了。
  3. 表结构(冗余、拆分、not null等)、架构优化(缓存、读写分离、分库分表)。
  4. 业务层的优化,必须条件是否必要。

掌握正确的调优思路,才是解决数据库性能问题的根本。

猜你喜欢

转载自blog.csdn.net/nonage_bread/article/details/113180454