首页 > 自考资讯 > 自考知识

数据库慢sql,一个慢sql引起的惨案是什么

头条共创 2024-07-05

事故

一个阳光明媚的下午,程序员小七一如往常地写着bug。

8201c4dd93464915bd2c5200c5435083~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720728257&x-signature=zkScEpdIItfN4RRAAhlEnBiLgEs%3D突然收到客服消息,说收到大量用户投诉,无法再打开页面。小七最近发布了新代码,我认为部分代码存在问题。

5e7f1f3922714059806896ce5192fd3f~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720728257&x-signature=AvWpDHytkZbYOPcB8wyVivT6a8Y%3D 快速将流程切换到备用数据库并回滚代码。接下来查看错误日志,发现数据库连接池报大量超时错误。这种情况通常有两种可能:

一是数据库或者与其连接的网络出现问题,导致无法连接数据库,且已达到超时时间。另一个是有很多线程运行缓慢的查询。旧线程仍在执行查询,而新线程陷入等待状态,只能等待达到超时时间。最终发现数据库中的查询速度慢是失败的原因。由于高频查询不会命中索引,因此每次查询至少需要一秒或更长时间,因此会累积大量查询请求并超时。

复盘

吸取这次经验后,小七决定到当地调查一下这种病症。

首先,让我们使用一个非常简单的演示表创建一个不正确的索引年龄、分数。

create table Demon( id int auto_incrementprimary key, name varchar(255) null,age int null, core int null); 在demo 中创建索引idx_age_score (age, core); 启用慢速SQL 日志记录。

SET GLOBAL throw_query_log=1; 接下来,使用Python创建一个包含500万条随机数据的SQL文件。问题大概是:

import randomif __name__=='__main__': SQL_file=open('./batch_jq.SQL', 'w',encoding='utf-8') a1=['张', '金', '李', '王', '赵'] a2=['余', '明', '荣', '范', '君', '林'] a3=[', '李', '林', ', '国', ''] _len=5000 # 循环5k 次while _len=1: line='Insert into demo(name,age,score) values ' arr=[] # 每个时间范围批量插入1,000 个项目到i (1 , 1001): name=random.choice(a1)+random.choice(a2)+random.choice(a3) arr.append((name, random.randint(1, 100), random.randint(1) , 10000000) )) _SQL=line + str(arr).strip('[]') SQL_file.write(_SQL + ';\n') _len -=1PS: 这里,不是将数据一一插入,而是使用批量插入。所以运行SQL会快一点。

接下来,运行SQL插入500万条数据。

.[2020-04-19 20:05:22] 636 毫秒内影响24000 行.[2020-04-19 20:05:23] 638 毫秒内影响24000 行。 [2020-04-19 20:05:] 23] 193 毫秒内影响了8000 行[2020-04-19 20:05:23] 摘要3 分42 秒989 毫秒内执行了5000 条语句中的: 条(文件中有106742400 个符号) ,建立一个简单的使用SpringBoot + JdbcTemplate 的应用程序。

@RestControllerpublic class DemoController { private static Final Logger LOGGER=LoggerFactory.getLogger(DemoController.class); @Resource private JdbcTemplate jdbcTemplate; //触发慢查询业务的入口@GetMapping('trigger') public Stringtrigger() { long ago=System .currentTimeMillis(); jdbcTemplate.query('select * from demo.demo where Score 20 limit 50', (set) - { }); 很久之后=System.currentTimeMillis() {} ms', after - before ); return 'success' }} 我尝试调用http://localhost:8080/trigger,发现需要一秒多的时间。虽然有点慢,但还是可以接受的。

于是就去AB做了压测。

$ ab -n500 -c20 http://localhost:8080/trigger# 表示总共500 个请求,一次20 个并发请求。在压力测试中,我们发现日志打印时间基本在15秒左右。已经很慢了,但是没有报错。业务中可以正常使用,不会出现数据库查询慢的情况。

2020-04-19 20:56:21.665信息18908 --- [nio-8080-exec-3] c.e.s.controller.DemoController :调用时间: 15260 ms20:56:21.779信息18908 --- [io -8] 080-exec-10] c.e.s. 控制器.DemoController : 调用时间: 15445 ms.并增加并发量。

$ ab -n500 -c50 http://localhost:8080/trigger#表示总共500个请求,每次并发请求数为50个。您会注意到控制台中显示的通话时间开始缓慢增加。打印一些异常信息。

2020-04-19 21:02:55.277 错误17100 --- [io-8080-exec-45] o.a.c.c.C.[.[.[/].[dispatcherServlet] : servlet [dispatcherServlet] 的Servlet.service() 在路径[] 的上下文中抛出异常:[无法处理请求。嵌套异常是org.springframework.jdbc.CannotGetJdbcConnectionException:获取JDBC 连接失败。嵌套异常是java.SQL.SQLTransientConnectionException: HikariPool-1 - 连接不可用。请求在30000 毫秒后超时。 ] 根本原因存在java.SQL.SQLTransientConnectionException: HikariPool-1 - 连接不可用。 com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:689) ~[HikariCP-3.4.2.jar:na] 上的请求在30000 毫秒后超时。 zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:196) ~[HikariCP-3.4.2.jar:na] 示例代码使用SpringBoot自带的数据库连接池Hikari。默认超时为30 秒。抛出异常。无论连接池如何,功能都会略有不同,但本质上都有超时设置。

728fea2558214a988fa8462f19189c96~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720728257&x-signature=oK103POBGvTOl0gMt1srC9qs%2F9Y%3D 此时数据库中还存在一些较慢的SQL 记录。

SHOW GLOBAL STATUS LIKE '%Slow_queries%';| Slow_queries | 51 |下一步是运行以下SQL 来打印当前连接状态并查看哪些SQL 语句正在消耗时间。

如果你运行SHOW FULL processlist;f4e59c39a8bd4b1193fd91af50c4c0b7~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720728257&x-signature=DTmDf9%2FxPmnJd1pYpsgb0iS6Na0%3D,你可以很容易地看到SQL执行时间超过1秒。当我用SQL 解释这一点时,我意识到这是对整个表的扫描。

当然,实际的项目表并没有那么简单,SQL语句和索引也更加复杂。这只是为了演示目的而创建的一个简单示例。

现在有很多很棒的数据库监控工具,比如阿里巴巴的Druid,可以让你更方便、更优雅地查看日志、排查数据库问题。

调优后我还进行了50并发的本地压力测试,发现响应时间基本停留在10ms左右,完全没有压力。

调优

使用索引

慢SQL 通常可以通过使用索引来解决。

通过问题定位,您发现您对某个字段有高频查询需求,但您并没有为该字段建立索引。 MySQL中的所有索引都使用“最左匹配原则”,因此现有的连接索引已经过时,并且不会在高频查询上获得命中。

当然,创建太多索引也有缺点。根据你的业务来决定。

使用缓存

分析发现这些慢SQL执行的查询条件完全相同。这意味着查询结果可以存储在缓存中,允许后续查询直接从缓存中获取,从而显着提高性能。

事实上,目前主流的ORM框架都支持缓存,甚至可以用于多级缓存。 Spring还提供了一个缓存框架Spring Cache,您可以根据自己的需求进行配置和使用。

反思

回顾调整后,思考过去。

212c57eb96a64af6a3339d30efd9656b~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720728257&x-signature=ja27DaovYBGJfENoR9hlvNuLAXM%3D

利用好explain

在使用SQL语句之前,可以先解释一下SQL语句,看看是否命中索引。如果没有命中,考虑是否是高频语句,是否需要调优。

进行充分的压测

网上没有任何问题。不要盲目地假设您的程序没问题;将其直接部署到您的生产环境中。如果能在上线前进行压力测试,就可以尽早发现性能问题,延缓损失。

利用好日志和监控

通常在晚上用户较少的时候在线发布。如果您可以配置错误日志收集以及数据库监控和警报,您可能能够在大量用户之前发现此问题。这样我们就能尽快解决问题,减少用户和企业的损失。

公众号

如果您看到这篇文章,就表示您同意我的文章。如果这有帮助,请支持我们:

352212ddfef94098ae8f5a6ff9d5d7fe~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720728257&x-signature=f3bPNrAYesPBTW2vvcr0Be9Hmz8%3D

版权声明:本文由今日头条转载,如有侵犯您的版权,请联系本站编辑删除。

猜你喜欢