一、优化思路
我们说到性能调优,大部分时候想要实现的目标是让我们的査询更快。一个査询的流程又是由很多个环节组成的,每个环节都会消耗时间。
二、连接-配置优化
第一个环节是客户端连接到服务端,这一块有可能会出现性能问题可能是服务端连接数不够导致应用程序获取不到连接。
比如我们遇到过的错误:
---这个是超过了服务端设置的最大并发连接数
Mysql: error 1040: Too many connections
我们可以从两个方面来解决连接数不够的问题:
1、从服务端来说,我们可以增加服务端的可用连接数。
(1) 增加可用连接数,修改max connections的大小:
---修改最大连接数,当有多个应用连接的时候
show variables like 'max_connections';
(2) 或者,或者及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是28800秒,8小时,我们可以把这个值调小。
---及时释放不活动的连接,注意不要释放连接池还在使用的连接
show global variables like 'wait_timeouf';
2、从客户端来说,可以减少从服务端获取的连接数。如果我们想要不是每一次执行 SQL都创建一个新的连接,应该怎么做?
这个时候我们可以引入连接池,实现连接的重用。
常见的数据库连接池有老牌的 DBCP 和 C3P0 、阿里的 Druid、Hikari
连接池并不是越大越好,只要维护一定数量大小的连接池,其他的客户端排队等待获取连接就可以了。有的时候连接池越大,效率反而越低。
三、架构优化
3.1 缓存
在系统里面有一些很慢的查询,要么是数据量大,要么是关联的表多,要么是计算逻辑非常复杂,这样的查询每次会占用连接很长的时间。
所以为了减轻数据库的压力,和提升查询效率,我们可以把数据放到内存缓存起来, 比如使用 Redis
3.2 集群,主从复制
集群的话必然会面临一个问题,就是不同的节点之间数据一致性的问题。如果同时 读写多台数据库节点,怎么让所有的节点数据保持一致?
这个时候我们需要用到复制技术(replication),被复制的节点称为 master ,复制 的节点称为 slave。slave本身也可以作为其他节点的数据来源,这个叫做级联复制。
MySQL的主从复制是怎么实现:
- MySQL 所有更新语句都会记录到 Server 层的
binlog
。 - 有了这个
binlog
,从服务器会不断获取主服务器的binlog
文件,然后解析里面的 SQL 语句,在从服务器上面执行一遍,保持主从的数据一致。
这里面涉及到三个线程,
- 连接到
master
获取binlog
,并且解析binlog
写入中继日志,这个线程叫做I/O线程。 - Master 节点上有一个
log dump
线程,是用来发送binlog
给slave
的。 - 从库的 SQL 线程,是用来读取
relay log
,把数据写入到数据库的。
做了主从复制配置案之后,我们只把数据写入 master 节点,而读的请求可以分担到 slave 节点。我们把这种方案叫做读写分离
对于读多写少的项目来说,读写分离对于减轻主服务器的访问压力很有用
3.3 分库分表
在集群的架构中,所有的节点存储的都是相同的数据。如果单张表存储的数据过大 的时候,比如一张表有上亿的数据,每天以百万的量级增加,单表的查询性能还是会大幅下降。这个时候我们应该把单个节点的数 据分散到多个节点存储,减少存储和访问压力,这个就是分库分表。
分库分表总体上可以分为两类:
- 垂直分库,减少并发压力。垂直分库的做法,把一个数据库按照业务拆分成不同的数据库。
- 水平分表,解决存储瓶颈。水平分库分表的做法,把单张表的数据按照一定的规则分布到多个数据库。
四、优化器 SQL 语句分析与优化
4.1 慢查询日志 slow query log
4.1.1 打开慢日志开关
因为开启慢查询日志是有代价的(跟binlog、optimizer-trace —样),所以它默认是关闭的:
Show variables like 'slow_query%';
除了这个开关,还有一个参数,控制执行超过多长时间的SQL才记录到慢日志,默认是10秒。如果改成0秒的话就是记录所有的SQL。
show variables like '%long_query%';
4.1.2 参数的两种修改方式
1、set 动态修改参数(重启后失效)
--1开启,0关闭,重启后失效
set @@global.slow_query_log=l;
--默认10秒,另开一个窗口后才会查到最新值
set @@global.long_query_time=3;
show variables like '%long_query%';
Show variables like 'slow_query%';
2、修改配置文件my.cnf
以下配置定义了慢査询日志的开关、慢査询的时间、日志文件的存放路径。
slow_query_log = ON
long_query_time= 2
slow_query_log_file =/var/lib/mysql/localhost-slow.log
4.1.3 mysqldumpslow
MySQL提供了 mysqldumpslow 的工具,在 MySQL 的 bin 目录下。
mysqldumpslow —help
例如:查询用时最多的10条慢SQL:
mysqldumpslow ・s t ・t 10 -g 'select' /var/lib/mysql/localhost-slow.log
4.1.4 其它系统命令
show full processlist;
这是很重要的一个命令,用于显示用户运行线程。可以根据 id 号 kill 线程。
show status
服务器运行状态
show engine
存储引擎运行信息
4.2 EXPLAN执行计划
explain 的结果有很多的字段,我们详细地分析一下。
4.2.1 id
id 是查询序列编号,每张表都是单独访问的,一个 SELECT 就会有一个序号。
- id 值不同的时候,先査询 id 值大的(先大后小)
- id 值相同时,表的查询顺序是从上往下顺序执行的。(根据 explan 出来的结果)
- 如果 id 有相同也有不同, 就是 id 不同的先大后小,id 相同从上往下
在连接查询中,我们要把小表放在前面查询,因为它的中间结果最少,中间结果需要存在临时表。(小表驱动大表)
4.2.2 select type 查询类型
下面列举一些常见的查询类型
- SIMPLE:简单查询,不包含子查询,不包含关联查询 union
- PRIMARY:子查询 SQL 语句中的
主查询
,也就是最外面那层查询 - SUBQUERY:子查询中所有的
内层查询
都是 SUBQUERY 类型 - 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
以上的类型除了 all ,都能用到索引。
- const:主键索引或者唯一索引,只能查到一条数据的 SQL
- system:system 是 const 的一种特例,只有一行满足条件,对于 MyISAM、 Memory 的表,只能查询到一条记录,就是 system
- eq_ref:通常出现在多表的 join 查询,被驱动表通过唯一索引 (UNIQUE 或者 PRIMARY KEY)进行访问,此时被驱动表的访问方式就是 eq_ref
- ref:查询用到了非唯一性索引,或者关联操作值使用了索引的最左前缀
- range:索引范围扫描。如果 where 后面是 between and 或 < 或 > 或 >= 或 <= 或 in 这些,type 类型就位 range
- index:Full Index Scan,查询全部索引中的数据(比不走索引要快)
- all:Full Table Scan,如果没有索引或者没用用到索引,type 就是 ALL
- NULL:不用访问表或者索引就能得到结果。例如
EXPLAN select 1 from dual where 1 = 1;
小结:一般来说,需要保证查询的 type 至少打到 range 级别,最好能达到 ref。ALL (全表扫描)和 index(查询全部索引)都是需要优化的
4.2.4 possible_key 、key
可能用到的索引和实际用到的索引,如果是 NULL 就代表没有用到索引。
possible_key 可以有多个,可能用到索引不代表一定用到索引。
possible_key 为空,key也可能有值(覆盖索引的情况)
4.2.5 key_len
索引的长度(使用的字节数),跟索引字段的类型和长度有关。
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(索引条件下推):存储引擎帮忙过滤数据,正常是在 Server 层过滤
-
using filesort:不能使用索引来排序,用到了额外的排序(跟磁盘或文件没有关系)。需要优化。
-
using temporary:用到了临时表。例如:
1、distinct 非索引列
2、group by 非索引列
3、使用 join 的时候,group任意列
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 语句的优化有很多,这里就不一一地分析了,无非就是使用 explan 查看执行计划是否命中再修改语句
五、存储引擎
5.1 存储引擎的选择
为不同的业务表选择不同的存储引擎,例如:
- 查询插入操作多的业务表,用MylSAMo
- 临时数据用Memeroyo
- 常规的并发大更新多的表用InnoDB
5.2 分表或者分区
订单历史表:在年底为下一年度建立12个分区,每个月一个分区。
渠道交易表:分成:当日表、当月表、历史表,历史表再做分区。
5.3 字段的定义
原则:使用可以正确存储数据的最小数据类型。
为每一列选择合适的字段类型:
5.3.1 整数类型
INT有6种类型,不同的类型的最大存储范围是不一样的,占用的存储空间也是不
一样的。
举例:存储性别字段,用 TINYINT
5.3.2 字符类型
变长情况下,varchar 更节省空间,但是对于 varchar 字段,需要一个字节来记录长 度。比如:联系地址。
固定长度的用 char ,不要用 varcharo 比如:行政区划编码。
5.3.3 非空
非空字段尽量定义成 NOT NULL,提供默认值,或者使用特殊值、空串代替 nullo NULL 类型的存储、优化、使用都会存在问题。
5.3.4 不要用外键、触发器、视图
降低了可读性;影响数据库性能,应该把把计算的事情交给程序,数据库专心做存储;数据的完整性应该在程序中检查。
5.3.5 大文件存储
图片和音频、视频怎么存储?
不要用数据库存储图片(比如base64编码)或者大文件。
把文件放在 NAS上,数据库只需要存储URI (相对路径),在应用中配置 NAS 服 务器地址。
5.3.6 表拆分或者字段冗余
表拆分:将不常用的字段拆分出去,避免列数过多和数据量过大
字段冗余:合同表的客户姓名
六、优化体系总结
可以从以下维度来优化数据库
- SQL与索引
- 存储引擎与表结构
- 数据库架构
- MySQL 配置
- 硬件与操作系统
- 除了对于代码、SQL语句、表定义、架构、配置优化之外,业务层面的优化也不能忽视,比如限流,或者引入 MQ 削峰等等
七、优化案例,一些优化建议
服务端状态分析:
如果出现连接变慢,查询被阻塞,无法获取连接的情况。
- 重启!
- show processlist 查看线程状态,连接数数量、连接时间、状态
- 查看锁的状态
- 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本身解决不了了,就要上升到表结构和架构了。
,不确定哪一个因素影响最大,通过条件的增减,顺序的调 整,找出引起查询慢的主要原因,不断地尝试验证。
找到原因:比如是没有走索引引起的,还是关联查询引起的,还是 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等)、架构优化(缓存读写分离分库分表)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68378.html