一、连接-配置优化
1.1 连接数过多问题
有时会碰到Mysql:error 1040:Too many connection的错误。原因:超过了服务端设置的最大并发连接数。
1.2 从两个方面解决问题
- 服务端,增加服务端可用连接数;
- 客户端,用连接池,设置合适的连接数;
1.2.1 服务端
1.增加服务端的连接数,修改max_connections的大小:
shouw variables like 'max_connections'; -- 修改最大连接数,当有多个应用连接的时候
2.或者,及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是28800秒,8小时,我们可用把这个值调小。
show global variables like 'wait_timeout'; -- 及时释放不活动的连接,注意不要释放连接池还在使用的连接
1.2.2 客户端
从客户端来说,可以减少从服务端获取的连接,用连接池最合适。连接池的连接数量不一定越大越好。有时候连接池越大,效率反而越低。
Druid的默认最大连接池大小是8。Hikari的默认最大连接池大小是10。
Hikari文档给的连接池大小公式:机器核数*2+1,举例:4核机器,维护9个连接。
二、慢查询日志-show query log
2.1 打开慢日志开关
因为开启慢查询日志是有代价的(更binlog、optimizer-trace一样),索引它默认是关闭的:
show variables like 'show_query%';
慢查询参数:
show variables like '%QUERY%';
参数修改两种方式:
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
2.2 慢sql日志分析
2.2.1 日志内容
less /var/lib/mysql/localhost-slow.log
有了慢查询日志,怎么去分析统计呢?比如哪条sql语句出现的慢查询次数最多,平均每次执行了多久?
2.2.3 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 线程。
- showprocesslist:效果与show full processlist等同,只能列出当前100条;
- show status服务器运行状态;
- show engine存储引擎运行信息;
3.1 show processlist效果
各个列的含义:
①.id列,用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看
②.user列,显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句
③.host列,显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
④.db列,显示这个进程目前连接的是哪个数据库
⑤.command列,显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
⑥.time列,显示这个状态持续的时间,单位是秒
⑦.state列,显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成
⑧.info列,显示这个sql语句,是判断问题语句的一个重要依据
3.2 本人解决问题实践
3.2.1 报错
启动报错:has already more than 'max_user_connections' active connections
3.2.2 方法一:
- show PROCESSLIST;
- kill id;
3.2.3 方式二:
mysql实例的连接数max_user_connections 和max_connections 配置的那些事_51CTO博客_mysql max_connections 设置多少合适
四、Explain
explain 的结果有很多的字段,我们详细地分析一下。
4.1 id
id 是查询序列编号,每张表都是单独访问的,一个 SELECT 就会有一个序号。
- id 值不同的时候,先査询 id 值大的(先大后小)
- id 值相同时,表的查询顺序是从上往下顺序执行的。(根据 explan 出来的结果)
- 如果 id 有相同也有不同, 就是 id 不同的先大后小,id 相同从上往下
连表查询,尽量查询中间结果小的表放前面;因为中间结果表会存在临时表,结果小,占有资源少;
——中心思想,小表驱动大表;小指的不是表数据量的大小,而是指过滤后的结果;
4.2 select type 查询类型
下面列举一些常见的查询类型:
- SIMPLE:简单查询,不包含子查询,不包含关联查询 union
- PRIMARY:子查询 SQL 语句中的主查询,也就是最外面那层查询
- SUBQUERY:子查询中所有的内层查询都是 SUBQUERY 类型
- DERIVED:衍生查询,表示在得到最终查询结果之前会用到临时表
- UNION:用到了 UNION 查询
- UNION RESULT:主要是显示哪些表之间存在 UNION 查询。比如 <union2,3>代表 id = 2 和 id = 3 的查询存在 UNION
4.2.1 实例
4.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 的一种特例,只有一行满足条件,对于 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;
小结1:system、const、eq_ref,都是可遇不可求的,基本很难优化到这个状态。
小结2:一般来说,需要保证查询的 type 至少打到 range 级别,最好能达到 ref。
小结3:ALL (全表扫描)和 index(查询全部索引)都是需要优化的。
4.3.1 图示
从上往下,性能越来越差;
4.4 possible_key 、key
可能用到的索引和实际用到的索引,如果是 NULL 就代表没有用到索引。
possible_key 可以有多个,可能用到索引不代表一定用到索引。
possible_key 为空,key也可能有值(覆盖索引的情况)。
4.4.1 实例
如果possible_keys是null,key有没有可能不为null?
——答,有,覆盖索引,索引下推;
4.5 key_len
索引的长度(使用的字节数),跟索引字段的类型和长度有关。
4.5.1 长度解析
索引长度:1023是怎么来的?
答:
- name长度是255,编码是utf8mb4,一个字符占4个字节:255*4=1020;
还有3个:
- name字段是变长的,需要额外两个字节存储变长的长度;+2
- 可以为null,还需要一个额外字节存储;+1
4.6 rows
MySQL 认为扫描多少行才能返回请求的数据,是一个预估值。一般来说行数越少越好。
简单来说,就说预估需要扫描多少数据。
4.7 filtered
这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,它是一个百分比。
如果比例很低,说明存储引擎层返回的数据需要经过大量过滤,这个是会消耗性能的,需要关注。
4.8 ref
使用哪个列或者常数和索引一起从表中筛选数据。
简而言之:表示过滤数据的时候,用的是常量还是什么字段?
4.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.9.1 图示
五、SQL与索引优化
5.1 mysql explain format = json 查看sql执行效率
如果要分析语句时,拿到一个更详细的值,可以用 Explain format=json ;
5.2 关于limit类语句优化
当我们的 SQL 语句比较复杂,有多个关联和子查询的时候,就要分析 SQL 语句有没有改写的方法。
5.3 一个sum语句性能提升3倍的优化案例
https://gper.club/articles/7e7e7f7ff4g5egc2g63
5.3.1 案例总结:
优化前:
SELECT
count( IF ( b.busi_status = '1', b.busi_status, NULL ) ) AS totalPaySuccessCount,
sum( IF ( b.busi_status = '1', b.sal_real_pay, 0 ) ) AS totalPaySuccessAmount,
count( IF ( b.busi_status = '2', b.busi_status, NULL ) ) AS totalPayErrorCount,
sum( IF ( b.busi_status = '2', b.sal_real_pay, 0 ) ) AS totalPayErrorAmount
FROM
sal_orders AS a
LEFT JOIN sal_orders_detail b ON a.busi_proc_id = b.order_id
WHERE
a.enable_status =1
AND a.corp_no = '2'
优化后:
SELECT
sum( b.sal_real_pay ) AS totalPaySuccessAmount
FROM
sal_orders AS a
LEFT JOIN sal_orders_detail b ON a.busi_proc_id = b.order_id
WHERE
a.enable_status =1
AND a.corp_no = '2'
and b.busi_status = '2'
union
SELECT
sum( b.sal_real_pay ) AS totalPayErrorAmount
FROM
sal_orders AS a
LEFT JOIN sal_orders_detail b ON a.busi_proc_id = b.order_id
WHERE
a.enable_status =1
AND a.corp_no = '2'
and b.busi_status = '1'
sum() 函数里不要加if,会带来额外开销。把if条件放在where后,改为union + where实现功能;
六、存储引擎
6.1 存储引擎的选择
为不同的业务表选择不同的存储引擎,例如:
- 查询插入操作多的业务表,用MylSAM
- 临时数据用Memeroy
- 常规的并发大更新多的表用InnoDB
6.2 分表或者分区
订单历史表:在年底为下一年度建立12个分区,每个月一个分区。
渠道交易表:分成:当日表、当月表、历史表,历史表再做分区。
6.3 字段的定义
原则:使用可以正确存储数据的最小数据类型。
为每一列选择合适的字段类型:
6.3.1 整数类型
INT有6种类型,不同的类型的最大存储范围是不一样的,占用的存储空间也是不一样的。
举例:存储性别字段,用 TINYINT
6.3.2 字符类型
变长情况下,varchar 更节省空间,但是对于 varchar 字段,需要一个字节来记录长 度。比如:联系地址。
固定长度的用 char ,不要用 varcharo 比如:行政区划编码。
6.3.3 非空
非空字段尽量定义成 NOT NULL,提供默认值,或者使用特殊值、空串代替 nullo NULL 类型的存储、优化、使用都会存在问题。
6.3.4 不要用外键、触发器、视图
降低了可读性;影响数据库性能,应该把把计算的事情交给程序,数据库专心做存储;数据的完整性应该在程序中检查。
6.3.5 大文件存储
图片和音频、视频怎么存储?
不要用数据库存储图片(比如base64编码)或者大文件。
把文件放在 NAS上,数据库只需要存储URI (相对路径),在应用中配置 NAS 服 务器地址。
6.3.6 表拆分或者字段冗余
表拆分:将不常用的字段拆分出去,避免列数过多和数据量过大
字段冗余:合同表的客户姓名
七、优化体系总结
7.1 你会从哪些维度优化数据库?你会怎么回答呢?
- SQL与索引
- 存储引擎与表结构
- 数据库架构
- MySQL 配置
- 硬件与操作系统
- 除了对于代码、SQL语句、表定义、架构、配置优化之外,业务层面的优化也不能忽视,比如限流,或者引入 MQ 削峰等等
7.2 业务层面
除了对于代码、 SQL 语句、表定义、架构、配置优化之外,业务层面的优化也不能忽视。
举两个例子:
- 在某一年的双十一,为什么会做一个充值到余额宝和余额有奖金的活动?现在会推荐大家用花哩支付,而不是银行卡支付?因为使用余额或者余额宝付款是记录本地或者内部数据库,而使用银行卡付款,需要调用接口,操作内部数据库肯定更快。
- 在某一年的双十一,为什么在凌晨禁止查询今天之外的账单?为什么小鸡的饲料发放延迟了?这是一种降级措施,用来保证当前最核心的业务。
- 某银行的交易记录,只能按月份查询。
- 最近几年的双十一,为什么 11 月 1 日就开始了?变成了各种定金红包模式?—— 预售分流。
7.3 应用层面
在应用层面同样有很多其他的方案来优化,达到尽量减轻数据库的压力的目的,比如限流,或者引入 MQ 削峰,等等等等。
为什么同样用 M ySQL ,有的公司可以抗住百万千万级别的并发,而有的公司几百个并发都扛不住,关键在于怎么用。所以,用数据库慢,不代表数据库本身慢,有的时候还要往上层去优化。
当然,如果关系型数据库解决不了的问题,我们可能需要用到搜索引擎或者大数据的方案了,并不是所有的数据都要放到关系型数据库存储。
八、 优化案例,一些优化建议
8.1 服务端状态分析:
- 重启!
- show processlist 查看线程状态,连接数数量、连接时间、状态;
- 查看锁的状态;
- kill 有问题的线程;
8.2 对于具体的慢SQL:
8.2.1 分析查询基本情况
涉及到的表的表结构,字段的索引情况、每张表的数据量、查询的业务含义。这个非常重要,因为有的时候你会发现 SQL 根本没必要这么写,或者表设计是有问 题的。
8.2.2 找出慢的原因
- 查看执行计划,分析SQL的执行情况,了解表访问顺序、访问类型、索引、扫描行数等信息。
- 如果总体的时间很长,不确定哪一个因素影响最大,通过条件的增减,顺序的调整,找出引起查询慢的主要原因,不断地尝试验证。
找到原因:比如是没有走索引引起的,还是关联查询引起的,还是 order by 引起的。
找到原因之后:
8.2.3 对症下药
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 引起的。
3.表结构(冗余、拆分、not null等)、架构优化(缓存读写分离分库分表)。
4.业务层优化,必须条件是否必要。
掌握正确的调优思路,才是解决数据库性能问题的根本。