1. 优化思路
作为架构师或者开发人员,说到数据库性能优化,你的思路是什么样的?
如果在面试的时候遇到这个问题:你会从哪些维度来优化数据库,你会怎么回答?
说到性能调优,大部分时候想要实现的目标是让我们的査询更快。一个査询的流程又是由很多个环节组成的,每个环节都会消耗时间。
mysql架构图:
我们要减少查询所消耗的时间,就要从每一个环节入手。
先确认一下环境:
select version();
show variables like '%engine%';
2. 连接一一配置优化
第一个环节是客户端连接到服务端,连接这一块有可能会出现什么样的性能问题?有可能是服务端连接数不够导致应用程序获取不到连接。
比如我们遇到过的
Mysql: error 1040: Too many connections的错误。
这个是超过了服务端设置的最大并发连接数。
可以从两个方面来解决连接数不够的问题:
-
从服务端来说,我们可以增加服务端的可用连接数。如果有多个应用或者很多请求同时访问数据库,连接数不够的时候,我们可以:
- 增加可用连接数,修改max_connections的大小:
show variables like 'max_connections';-- 修改最大连接数,当有多个应用连接的时候
- 及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是28800秒,8小时,我们可以把这个值调小。
show global variables like 'wait_timeout; -- 及时释放不活动的连接,注意不要释放连接池还在使用的连接
- 增加可用连接数,修改max_connections的大小:
-
从客户端来说,可以减少从服务端获取的连接数。如果我们想要不是每一次执行 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%';
参数的两种修改方式:
-
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%';
-
修改配置文件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';
慢日志分析
- 日志内容
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语句的出现的慢查询次数最多,平均每次执行了多久?人工肉眼分析显然不可能。
- 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语句的问题,也有可能是服务器状态的问题。 所以我们也要掌握一些查看服务器和存储引擎状态的命令。
其他系统命令
- 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语句、表定义、架构、配置优化之外,业务层面的优化也不能忽视。举两个例子:
- 在某一年的双十一,为什么会做一个充值到余额宝和余额有奖金的活动?现在会推荐大家用花呗支付,而不是银行卡支付?
因为使用余额或者余额宝付款是记录本地或者内部数据库,而使用银行卡付款,需要调用接口,操作内部数据库肯定更快。 - 在某一年的双十一,为什么在凌晨禁止查询今天之外的账单?为什么小鸡的饲料发放延迟了?
这是一种降级措施,用来保证当前最核心的业务。 - 某银行的交易记录,只能按月份查询。
- 最近几年的双十一,为什么11月1日就开始了?变成了各种定金红包模式?
预售分流。
在应用层面同样有很多其他的方案来优化,达到尽量减轻数据库的压力的目的,比如限流,或者引入MQ削峰,等等。
为什么同样用MySQL,有的公司可以抗住百万千万级别的并发,而有的公司几百个并发都扛不住,关键在于怎么用。所以,用数据库慢,不代表数据库本身慢,有的时候还要往上层去优化。
当然,如果关系型数据库解决不了的问题,我们可能需要用到搜索引擎或者大数据的方案了,并不是所有的数据都要放到关系型数据库存储。
7. 优化案例
服务端状态分析:
如果出现连接变慢,查询被阻塞,无法获取连接的情况。
- 重启!
- show processlist查看线程状态,连接数数量、连接时间、状态
- 查看锁的状态
- kill有问题的线程
对于具体的慢SQL:
一、分析查询基本情况
涉及到的表的表结构,字段的索引情况、每张表的数据量、查询的业务含义。
这个非常重要,因为有的时候你会发现SQL根本没必要这么写,或者表设计是有问题的。
二、 找出慢的原因
- 查看执行计划,分析SQL的执行情况,了解表访问顺序、访问类型、索引、扫描行数等信息。
- 如果总体的时间很长,不确定哪一个因素影响最大,通过条件的增减,顺序的调整,找出引起查询慢的主要原因,不断地尝试验证。
找到原因:比如是没有走索引引起的,还是关联查询引起的,还是。order by引起的。
找到原因之后:
三、 对症下药
- 创建索引或者联合索引
- 改写SQL,这里需要平时积累经验,例如:
1) 使用小表驱动大表
2) 用join来代替子查询
3) not exist 转换为 left join IS NULL
4) or 改成 union
5) 如果结果集允许重复的话,使用UNION ALL代替UNION
6) 大偏移的limit,先过滤再排序。
如果SQL本身解决不了了,就要上升到表结构和架构了。 - 表结构(冗余、拆分、not null等)、架构优化(缓存、读写分离、分库分表)。
- 业务层的优化,必须条件是否必要。
掌握正确的调优思路,才是解决数据库性能问题的根本。