首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >线上的MySQL优化案例

线上的MySQL优化案例

作者头像
AsiaYe
发布于 2019-11-06 09:44:27
发布于 2019-11-06 09:44:27
1.3K00
代码可运行
举报
文章被收录于专栏:DBA随笔DBA随笔
运行总次数:0
代码可运行
线上的SQL优化案例(一)
今天上班的时候,业务方询问了一个问题,说是某一服务每次在查询的时候会有0.5s的延迟,让DBA帮忙查一查到底是什么原因,听到0.5s的这个数字的时候,我感觉问题倒不是很严重,我解决这个问题的方法如下:

1、先查看了一下慢日志中的内容,发现慢日志中没有具体的记录。这个问题比较好解决,其实他的本质是设定的慢日志的阈值是1s,只有超过1s的SQL语句才会被记录,这里我把参数long_query_time的值设置成为0.4,这样,就可以把查询超过0.4s的SQL都记录到慢日志里面了。

2、查看慢日志中的SQL语句,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT * FROM `account` 
WHERE  
`accountid` = 20000000528 
and 
`accounttype` = 1 
and 
`appid`=10005;

语句也比较简单,是根据某3列的值来查询一条记录,我先查看了一下结果集,结果集中只有一条记录,说明这个查询的结果是比较少的。

3、查看对应的表结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql--dba_admin 12:59:09>>show create table account\G
*************************** 1. row ***************************
       Table: account
Create Table: CREATE TABLE `account` (
  `appid` int(4) unsigned NOT NULL,
  `uid` bigint(8) unsigned NOT NULL,
  `accountid` char(32) NOT NULL DEFAULT '',
  `accounttype` int(4) unsigned NOT NULL,
  `mtime` bigint(8) unsigned NOT NULL DEFAULT '0',
  `id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `appid` (`appid`,`accountid`,`accounttype`,`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

可以看到,这个表有一个主键,一个普通索引,这个普通索引包含appid、accountid、accounttype以及uid这4个字段,是联合索引,查看了一眼表中的数据量,大概有5w多条,并不算很多。

4、初步修改意见

有了对表结构的认知和SQL语句,我当时的第一反应是这个SQL写的是否满足需求,问了一下业务方,他们说是为了查询uid写出来的SQL,那么其实这个SQL中的select *是不必要的,如果只要uid的值,则可以只查新uid即可,那么SQL语句其实可以被改写为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT uid FROM `account` 
WHERE  
`accountid` = 20000000528 
and 
`accounttype` = 1 
and 
`appid`=10005;

这样写,有一个好处,就是需要查询的uid和三个查询条件appid、accountid以及accounttype字段都在同一棵索引的B+树上,这样可以利用覆盖索引的原理,避免回表扫描。

5、这样测试了一把之后,业务方反馈说是并没有明显的降低查询时间。时间还是在0.3s左右,还是不能满足需求。这个时候,我已经开始怀疑这个表的索引创建的是否有问题了,于是首先查看了一眼表中的数据,看看appid的值都包含哪些吧。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql--dba_admin 12:02:25>>select distinct accounttype from account limit 10;
+-------------+
| accounttype |
+-------------+
|           1 |
+-------------+
1 row in set (0.02 sec)

mysql--dba_admin 12:02:25>>select distinct appid  from account limit 10;
+-------------+
|   appid     |
+-------------+
|       10005 |
+-------------+
1 row in set (0.02 sec)

经过查看,发现appid的值目前只包含一个10005,而accounttype的值也只包含数字1,而accountid和uid的值包含5w左右,这样问题已经有点明确了。

6、修改索引字段

我们再来看这个联合索引:

key appid(appid,accountid,accounttype,uid)

这样明显是不合理的,因为appid和accounttype的值基数太小,只有1,索引这个索引应该修改,具体的改法有很多,我是通过修改联合索引的先后顺序,将基数大的字段放在前面,这样扫描的时候能够过滤的更加准确一些。将索引改为:

key 'idx_accid' (accountid,uid,appid,accounttype)

这样,再来看一眼执行计划:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql--dba_admin 13:05:51>>explain SELECT uid FROM `account` WHERE  `accountid` = 20000000528;
+----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+
| id | select_type | table   | type  | possible_keys | key       | key_len | ref  | rows  | Extra                    |
+----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+
|  1 | SIMPLE      | account | index | idx_accid     | idx_accid | 112     | NULL | 57237 | Using where; Using index |
+----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+
1 row in set (0.00 sec)

这个问题比较奇怪,为什么看着走了索引,但是却扫描了这么多记录???改成单个字段的试试,这里我又把索引改成了:

KEY `idx_accid` (`accountid`)

只保留一个字段,看看执行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql--dba_admin 13:12:17>>explain SELECT uid FROM `account` WHERE  `accountid` = 20000000528 AND `accounttype` = 1 and `appid`=10005;
+----+-------------+---------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows  | Extra       |
+----+-------------+---------+------+---------------+------+---------+------+-------+-------------+
|  1 | SIMPLE      | account | ALL  | idx_accid     | NULL | NULL    | NULL | 57237 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)

我去,竟然不走索引,这就奇了怪了。

7、终于,我想了又想,猜了又猜,看到了一处细节。

建表语句如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql--dba_admin 13:16:24>>show create table account\G
*************************** 1. row ***************************
       Table: account
Create Table: CREATE TABLE `account` (
  `appid` int(4) unsigned NOT NULL,
  `uid` bigint(8) unsigned NOT NULL,
  `accountid` char(32) NOT NULL DEFAULT '',
  `accounttype` int(4) unsigned NOT NULL,
  `mtime` bigint(8) unsigned NOT NULL DEFAULT '0',
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`),
  KEY `idx_accid` (`accountid`)
) ENGINE=InnoDB AUTO_INCREMENT=57055 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

SQL语句如下:

SELECT uid FROM `account`

WHERE

`accountid` = 20000000528

AND

`accounttype` = 1

and

`appid`=10005;

到这里,问题应该已经能够看出来了,这个字段accountid在表中应该是一个char类型的,但是在SQL语句中写成了整数类型,发生了隐式类型转换,导致索引不可用。

弄清楚了这个,再来看这两个执行计划:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql--dba_admin 13:17:54>>explain SELECT id FROM `account` WHERE  `accountid` = 20000000528;
+----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+
| id | select_type | table   | type  | possible_keys | key       | key_len | ref  | rows  | Extra                    |
+----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+
|  1 | SIMPLE      | account | index | idx_accid     | idx_accid | 96      | NULL | 57237 | Using where; Using index |
+----+-------------+---------+-------+---------------+-----------+---------+------+-------+--------------------------+
1 row in set (0.00 sec)

mysql--dba_admin 13:18:25>>explain SELECT id FROM `account` WHERE  `accountid` = '20000000528';
+----+-------------+---------+------+---------------+-----------+---------+-------+------+--------------------------+
| id | select_type | table   | type | possible_keys | key       | key_len | ref   | rows | Extra                    |
+----+-------------+---------+------+---------------+-----------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | account | ref  | idx_accid     | idx_accid | 96      | const |    1 | Using where; Using index |
+----+-------------+---------+------+---------------+-----------+---------+-------+------+--------------------------+

其中扫面行数的区别最明显,一个是5w多行,一个是1行。所以,只需要将原来的SQL语句改为:

SELECT uid FROM `account`

WHERE

`accountid` = '20000000528'

AND

`accounttype` = 1

and

`appid`=10005;

就可以解决问题了。

8、到这里,问题基本上算是解决了,使用accountid作为索引的字段,这样在进行where条件过滤的时候,就可以过滤出很少的记录,然后再回表去通过id值查到想要的uid值,这样就比较快了。

最终的优化前后对比结果如图:

优化之前:

优化之后:

可以看到,效果还是很明显的。

今天的文章就到这里了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DBA随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
MySQL主从复制之半同步(semi-sync replication)
----------------------------以上,MySQL半同步复制搭建完毕-------------------------
GreatSQL社区
2022/02/21
1.1K0
MySQL主从复制之增强半同步(无损复制)、延迟复制和并行复制
MySQL有四种同步方式: 1、异步复制(Async Replication) 2、同步复制(sync Replication) 3、半同步复制(Async Replication) 4、增强半同步复制(lossless Semi-Sync Replication)、无损复制
AiDBA宝典
2023/04/26
8K0
MySQL主从复制之增强半同步(无损复制)、延迟复制和并行复制
Mysql半同步复制模式说明及配置示例 - 运维小结
MySQL主从复制包括异步模式、半同步模式、GTID模式以及多源复制模式,默认是异步模式 (如之前详细介绍的mysql主从复制)。所谓异步模式指的是MySQL 主服务器上I/O thread 线程将二进制日志写入binlog文件之后就返回客户端结果,不会考虑二进制日志是否完整传输到从服务器以及是否完整存放到从服务器上的relay日志中,这种模式一旦主服务(器)宕机,数据就可能会发生丢失。
洗尽了浮华
2019/05/25
2.9K0
mysql 半同步复制
转载:https://www.cnblogs.com/zero-gg/p/9057092.html
用户5760343
2019/11/12
2.2K0
MySQL|复制 - 原生复制的一致性探讨
明确一点,这几种结构无论怎么变,无论是用MHA还是Orchestrator,还是什么第三方工具,核心点都是通过binlog event来传递数据给其他节点。
用户1278550
2020/11/02
8240
MySQL|复制 - 原生复制的一致性探讨
MySQL 8 复制(二)——半同步复制
直到目前的最新版本为止,MySQL缺省依然使用异步复制策略。简单说所谓异步复制,指的是主库写二进制日志、从库的I/O线程读主库的二进制日志写本地中继日志、从库的SQL线程重放中继日志,这三步操作都是异步进行的。如此选择的主要理由是出于性能考虑,与同步复制相比,异步复制显然更快,同时能承载更高的吞吐量。但异步复制的缺点同样明显,不能保证主从数据实时一致,也无法控制从库的延迟时间,因此它不适于要求主从数据实时同步的场景。例如,为了分解读写压力,同一程序写主库读从库,但要求读到的数据与读主库的相同,异步复制不满足这种强数据一致性需求。异步复制的另一个问题是可能会有数据丢失,例如主库宕机时,已经提交的事务可能还没有传到从库上,如果此时强行主从切换,可能导致新主库上的数据不完整。
用户1148526
2019/05/25
5.5K0
【152期】面试官:你能说出MySQL主从复制的几种复制方式吗?
MySQL的复制默认是异步的,主从复制至少需要两个MYSQL服务,这些MySQL服务可以分布在不同的服务器上,也可以在同一台服务器上。
良月柒
2021/03/11
5220
【152期】面试官:你能说出MySQL主从复制的几种复制方式吗?
面试系列-mysql主从复制
MySQL主从复制涉及到三个线程,一个运行在主节点(log dump thread),其余两个(I/O thread, SQL thread)运行在从节点:
用户4283147
2022/10/27
1.4K0
面试系列-mysql主从复制
MySQL中的半同步复制(r11笔记第65天)
关于MySQL的复制架构,大体有下面三种方式,异步,全同步复制,半同步复制。 三种复制方式 第一种是异步复制,是比较经典的主从复制,搭建主从默认的架构方式,就是属于异步的,相对来说性能要好一些。但是还是会有丢失数据的情况。 第二种是全复制,比如说MySQL Cluster这样的方式,是属于全复制的,实际上MySQL Cluster其实发展并不大顺利,更多时候是一个实验室产品,但是时间定格在2016年12月12日,MySQL 5.7.17 GA的重大特性group replication插件
jeanron100
2018/03/21
9930
MySQL中的半同步复制(r11笔记第65天)
MySQL半同步复制原理与配置详解
  异步复制(Asynchronous replication),MySQL默认的复制是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理。原理最简单,性能最好,但是主从之间数据不一致的概率很大。
星哥玩云
2022/08/16
6.6K0
MySQL半同步复制原理与配置详解
MySQL主从复制延迟解决方案
前面一篇,我们学习到了MySQL多版本并发控制(MVCC)实现原理,这一篇我们接着学习MySQL主从复制模式下的延迟解决方案。
兔云小新LM
2023/03/16
4.9K0
这次终于把MySQL主从复制总结全面了!!!
网络上关于 MySQL 主从复制的文章很多都是讲解如何实现,以及部分实现原理,缺乏对 MySQL 主从复制的全面介绍。例如主从复制的模式(半同步模式和异步同步模式)、同步的原理(binary log+position,GTID)、主从复制的常见问题都缺乏一个全面的总结。
兔云小新LM
2021/05/12
9.4K3
这次终于把MySQL主从复制总结全面了!!!
MySQL半同步复制与刷盘策略
MySQL半同步复制是介于异步和全同步之间,主库只需要等待至少一个从节点,收到并且flush binlog到relay log文件即可。主库不需要等待所有从库给主库反馈,主库只需要收到任意一个从库反馈,而且并不是从库已经完成并提交的反馈,而是从库只应用完成io_thread内容即可反馈,无需等到sql_thread的执行完成。
Power
2025/03/01
2320
semi-sync原主库加入集群阻塞问题分析
前段时间支持客户处理问题的时候,发现一个semi-sync复制主从切换原master加入集群时,复制同步阻塞,无法继续同步数据的问题,非常有参考意义,整理一下,供大家参考。 问题现象 客户在一个一主两
沃趣科技
2018/03/26
2.9K0
semi-sync原主库加入集群阻塞问题分析
第十章· MySQL的主从复制
1)主服务器将所有数据和结构更改记录到二进制日志中。 2)从属服务器从主服务器请求该二进制日志并在本地应用其内容。 3)IO:请求主库,获取上一次执行过的新的事件,并存放到relaylog 4)SQL:从relaylog中将sql语句翻译给从库执行
DriverZeng
2022/09/26
4590
第十章· MySQL的主从复制
MySQL半同步(四)流程和相关参数
查看大图请移步 https://www.jianshu.com/p/5ebf4f4c1cf8
老叶茶馆
2021/04/22
1.2K0
MySQL半同步(四)流程和相关参数
mysql复制系列4-半同步复制
异步复制:主库将事件写入二进制日志,但不知道从库是否接收成功,也不知道从库什么时候重放二进制日志,如果主库崩溃,则在主库提交的事务可能还没有传输到从库,这种情况下如果主从故障切换,从库还没有传输到从库的事务将丢失
wangwei-dba
2021/05/13
7910
mysql复制系列4-半同步复制
MySQL半同步的配置
主库在提交事务时,在客户端接收到查询结束反馈前必须保证二进制日志已经传输到至少一台备库上。
保持热爱奔赴山海
2019/09/17
1.3K0
MySQL复制可能造成数据不一致的地方
上周在讲复制故障处理,利用DML在从主上手工造数据,导致主从复制中断,然后处理复制故障,同时给大家安利了:
阿炳数记
2021/06/07
9280
MySQL复制可能造成数据不一致的地方
MySQL是如何保证不丢数据的(二)
上篇文章我们聊了单机模式下,MySQL是如何保证数据一致性的,但是在实际的生产环境中,很少采用单机模式。现在所有的集群架构都是从MySQL的主从复制演变过来的。MySQL的主从复制是通过将主库的binlog发送至从库,从库重新提交主库的变更来实现主从数据的一致性。MySQL的主从复制主要分为三种:异步复制、半同步复制、组复制(MGR)。
MySQL数据库技术栈
2020/08/05
2.5K0
MySQL是如何保证不丢数据的(二)
推荐阅读
相关推荐
MySQL主从复制之半同步(semi-sync replication)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档