一则优化案例

简介:

昨晚收到客服MM电话,一用户反馈数据库响应非常慢,手机收到load异常报警,登上主机后发现大量sql执行非常慢,有的执行时间超过了10s
优化点一:
SELECT * FROM `sitevipdb`.`game_shares_buy_list` WHERE price>=’2.00′ ORDER BY tran_id DESC LIMIT 10;
表结构为:
CREATE TABLE `game_shares_buy_list` (
`tran_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`………..’
PRIMARY KEY (`tran_id`),
KEY `ind_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=3144200 DEFAULT CHARSET=utf8;
执行计划:
root@127.0.0.1 : sitevipdb 09:10:22> explain SELECT * FROM `sitevipdb`.`game_shares_buy_list` WHERE price>=’2.00′ ORDER BY tran_id DESC LIMIT 10;
+—-+————-+———————-+——-+—————+———+———+——+——+————-+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+———————-+——-+—————+———+———+——+——+————-+
| 1 | SIMPLE | game_shares_buy_list | index | NULL | PRIMARY | 4 | NULL | 10 | Using where |
+—-+————-+———————-+——-+—————+———+———+——+——+————-+
1 row in set (0.00 sec)
分析该sql的执行计划,由于tran_id是表的主键,所以查询根据主键降序顺序扫描,这样就可以不用排序,
然后在过滤条件price>2.00的记录,看上去这个执行计划貌似非常好,如果查询扫描到了满足条件的10条记录,就会停止扫描;
但是这里有个问题,如果表中有大量的记录是不符合2.00的,意味查询就需要扫描非常多的记录,才能找到符合条件的10条:
root@127.0.0.1 : sitevipdb 09:17:23> select price,count(*) as cnt from `game_shares_buy_list` group by price order by cnt desc limit 10;
+——-+——-+
| price | cnt |
+——-+——-+
| 1.75 | 39101 |
| 1.68 | 38477 |
| 1.71 | 34869 |
| 1.66 | 34849 |
| 1.72 | 34718 |
| 1.70 | 33996 |
| 1.76 | 32527 |
| 1.69 | 27189 |
| 1.61 | 25694 |
| 1.25 | 25450 |
可以看到表中有大量的记录不是2.00的,所以这个时候不能在根据主键顺序扫描,在过滤记录;
那么是否需要在price建立一个索引:
root@127.0.0.1 : sitevipdb 09:09:01> select count(*) from `game_shares_buy_list` where price>’2′;
+———-+
| count(*) |
+———-+
| 4087 |
+———-+
root@127.0.0.1 : sitevipdb 09:17:31> select count(*) from `game_shares_buy_list` ;
+———-+
| count(*) |
+———-+
| 1572100 |
从上面price的数据分布可以看出,price的分布相对还是比较集中的,如果在price建立索引,mysql也有可能认为由于需要回表的记录过多,
同时需要额外的排序,而不选择在price上的索引:
root@127.0.0.1 : sitevipdb 09:24:53> alter table game_shares_buy_list add index ind_game_shares_buy_list_price(price);
Query OK, 0 rows affected (5.79 sec)

可以看到优化器虽然注意到了我们新加的索引,但是最终还是选择了primary来扫描;
所以这个时候我们加上去的索引没有产生效果,数据库负载依然很高,如果强制走price上的索引,效果会这样:
root@127.0.0.1 : sitevipdb 09:35:38> SELECT * FROM `sitevipdb`.`game_shares_buy_list` WHERE price>=’2.0′ ORDER BY tran_id DESC LIMIT 10;
。。。。。
10 rows in set (7.06 sec)

root@127.0.0.1 : sitevipdb 09:36:00> SELECT * FROM `sitevipdb`.`game_shares_buy_list` force index(ind_game_shares_buy_list_price) WHERE price>=’2.0′ ORDER BY tran_id DESC LIMIT 10;
。。。。
10 rows in set (1.01 sec)
可以看到如果强制走索引,时间已经明显下降了,但是还是有些慢,能不能在快一点?其实我们需要扫描的记录只有10条,但查询在取得这10条记录的时候需要扫描大量无效的记录

怎么降低这个数据:其实只要改写一下sql就可以,我们先从索引中得到满足条件的10个id,在回表进行关联:
root@127.0.0.1 : sitevipdb 09:44:45> select * from game_shares_buy_list t1,
-> ( SELECT tran_id FROM sitevipdb.game_shares_buy_list WHERE price>=’2.0′ ORDER BY tran_id DESC LIMIT 10) t2
-> where t1.tran_id=t2.tran_id;
10 rows in set (0.00 sec)
可以看到执行时间已经不在秒级别了,和客户电话沟通后,很愿意这样改写sql。

—这里看到是order by tran_id是要额外排序的,索引也可以这样来建立消除排序(tran_id,price)这样可以消除排序,同时可以利用order by desc/asc +limit M,N的优化。

优化点二:
CREATE TABLE `game_session` (
`session_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ,
`session_expires` int(10) unsigned NOT NULL DEFAULT ‘0’,
`client_ip` varchar(16) DEFAULT NULL,
`session_data` text,
…………………….
PRIMARY KEY (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

查询为select `session_data`, `session_expires` from `game_session` where session_id=’xxx’出现大量等待情况
同时该表的insert,也有等待的现象;
可以看到这个表结构设计是有些问题的,咨询了客户后,可以改为下面结构:
CREATE TABLE `game_session` (
id int auto_increment,
`session_id` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT ,
`session_expires` int(10) unsigned NOT NULL DEFAULT ‘0’,
`client_ip` varchar(16) DEFAULT NULL,
`session_data` varchar(200),
PRIMARY KEY (id),
key ind_session_id(session_id,session_data, session_expires)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-新增自增主键id作为表的主键,这样对插入的性能提升是很好的,同时也降低了表主键的大小;
-将session_data由text改为了varchar(200),咨询了客户后,这个字段可以不用大字段存储,同时有text改为了varchar,就可以冗余到索引中;
由于查询可以使用覆盖索引来完成,所以将查询的3个字段冗余到索引中,查询通过索引完成,不用回表;

目录
相关文章
|
9月前
|
SQL JSON 关系型数据库
selenuim实战优化
该文介绍了如何使用Selenium将数据直接存入MySQL数据库,以苏宁易购网站为例。首先,优化了JSON数据写入,通过pymysql连接数据库,创建`books`表并读取JSON文件插入数据。接着,整合代码实现直接从网页抓取价格和标题,使用Selenium自动化滚动加载页面,定位元素,清洗数据并使用参数化查询插入到`books`表。主程序循环遍历多页数据,最后关闭数据库连接。
67 1
|
9月前
|
弹性计算 关系型数据库 数据库
利用阿里云进行性能优化:实践案例分享
在开发在线教育平台过程中,我们遇到了由于用户访问量增加而导致的性能瓶颈问题。通过使用阿里云的多种服务,包括RDS数据库、ECS弹性扩展、SLB负载均衡、OSS存储和CDN加速,我们对数据库、应用服务器和静态资源加载进行了全面优化。优化后的系统性能显著提升,数据库查询速度提高了60%,服务器负载下降了40%,静态资源加载时间减少了70%,从而极大改善了用户体验。本文详细介绍了问题分析、具体解决方案及其实施效果,旨在为其他开发者提供有价值的参考。
331 3
|
9月前
|
存储 缓存 前端开发
前端如何利用indexDB进行数据优化
使用IndexedDB作为浏览器内置的客户端数据库,用于存储大量数据和实现离线支持。它能缓存常用数据,减少服务器请求,提高用户体验。IndexedDB支持数据索引、复杂查询及版本管理,允许离线操作并同步到服务器。但需熟悉其异步API,可借助Dexie.js、localForage等库简化使用。
|
9月前
|
存储 网络协议 Java
服务优化实践
v服务优化实践
60 2
|
9月前
|
设计模式 监控 安全
Java多线程编程优化实践
本文将探讨在Java多线程编程中如何进行优化实践,通过合理的设计和技巧,提高程序性能、避免常见的问题。从线程池的使用、锁的选择到并发数据结构的应用,全面介绍优化多线程编程的方法与技巧。
|
9月前
|
存储 缓存 安全
【C/C++ 项目优化实战】 分享几种基础且高效的策略优化和提升代码性能
【C/C++ 项目优化实战】 分享几种基础且高效的策略优化和提升代码性能
438 0
优化if-else代码的几种方案
优化if-else代码的几种方案
|
前端开发
前端学习案例1-if...else..优化
前端学习案例1-if...else..优化
92 0
前端学习案例1-if...else..优化
|
前端开发
前端学习案例2-if...else..优化
前端学习案例2-if...else..优化
86 0
前端学习案例2-if...else..优化