热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Spring+SpringMVC+MyBatis+easyUI整合进阶篇(八)线上Mysql数据库崩溃事故的原因和处理

-由于有多个仓库,因此仓库也独立做了一张表;-一个仓库中有多个货架,tb_storehouse与tb_shelf是一对多的关系;-一个货架中有多个格子(货架规格不同,
- 由于有多个仓库,因此仓库也独立做了一张表; - 一个仓库中有多个货架,tb_storehouse与tb_shelf是一对多的关系; - 一个货架中有多个格子(货架规格不同,有的是8个有的是4个),tb_shelf与tb_shelf_grid也是一对多的关系; - 商品信息,以商品码作为主键,还有其他属性,但是与入库信息无关就没有罗列出来; - 商品的位置信息就是一件商品是在哪个格子上,表结构的设计就是四个字段:id,商品码,格子id,number(数量),存储了两个属性的主键id和数量值; - 入库记录信息,就是哪件商品在哪个时间点由哪个入库员在进行入库,涉及到的字段有:product_id,grid_id,operator_id,create_time,还有其他字段但是与入库操作无关联就不列举出来了。

前文提要

承接前文《一次线上Mysql数据库崩溃事故的记录》,在文章中讲到了一次线上数据库崩溃的事件记录,建议两篇文章结合在一起看,不至于摸不着头脑。

由于时间原因,其中只讲了当时的一些经过以及我当时的一些心理活动,至于原因和后续处理步骤并没有在文章中很清晰的写出来,以致于很多朋友说看得不清不楚的,这里向他们道个歉,主要是上周真的没有足够的时间将两篇文章同时准备好,不然也不会草草结尾了,而且上篇文章中主观因素占了较大的比重,因为回忆起这件事的时候确实有很多想法,因此显得有些个人化、日记化了。

这篇文章就不再讲述事件经过了,主要是把事件的原因和后续的处理步骤整理好。

忆往昔 1

有张图,是后来老大发给我的,能够看出当时的数据库情况:
mysql
这是数据库宕机后的实例信息,基本瘫痪了,至于事务锁相关的截图,当时没有保存,因此就无法放在文章中了,有朋友给我留言问当时为什么不直接kill掉锁住的进程,我回答是因为我不懂这个知识点,我找了一下那几天的日记确实有这方面的记录,是当时老大发我的几条命令:

show processlist;
//找到锁进程
kill id ;

应该也是做了这些操作的,但是看到上面那张图也应该知道为什么这些都不管用了,事务锁确实存在,导致了部分表无法正常操作,但是主要原因还是因为数据库资源被消耗光了,即使kill了相关的进程也无法解决。

入库功能介绍

已经定位到入库是发生这次事故的主要原因,那么为什么频繁的操作会导致这件事的发生呢?

首先来介绍一下当时的功能设计改动及涉及到的SQL语句。

表结构设计及功能设计

入库功能中涉及到的表和实体:

  • 仓库信息(tb_storehouse)
  • 货架信息(tb_shelf)
  • 格子信息(tb_shelf_grid)
  • 商品信息(tb_product)
  • 商品位置信息(tb_grid_product)
  • 入库信息(tb_store_in_record)

释义:

  • 由于有多个仓库,因此仓库也独立做了一张表;
  • 一个仓库中有多个货架,tb_storehouse与tb_shelf是一对多的关系;
  • 一个货架中有多个格子(货架规格不同,有的是8个有的是4个),tb_shelf与tb_shelf_grid也是一对多的关系;
  • 商品信息,以商品码作为主键,还有其他属性,但是与入库信息无关就没有罗列出来;
  • 商品的位置信息就是一件商品是在哪个格子上,表结构的设计就是四个字段:id,商品码,格子id,number(数量),存储了两个属性的主键id和数量值;
  • 入库记录信息,就是哪件商品在哪个时间点由哪个入库员在进行入库,涉及到的字段有:product_id,grid_id,operator_id,create_time,还有其他字段但是与入库操作无关联就不列举出来了。
    shelf

入库功能 V1.0

在最初的版本中,入库功能的设计较为简单和清晰,入库操作只做一件事,就是入库,页面逻辑也比较简单:进入后台-->仓库管理-->货架管理-->格子管理-->点击入库按钮-->入库,进入想要入库的格子页面点击入库,这时页面会弹出一个弹框,上面有一个input框,然后入库员用扫描枪扫描商品码即可完成入库,入库过程中input框为不可选中状态,成功后方可进行下一次入库,这种方式入库也挺快的,入库员找到想要入库的格子然后一直扫码就可以了。

在最初的版本中需要执行的SQL语句有:

  • 根据商品码查询商品,为空则报错并提醒需要完善商品SKU;
  • 查询格子信息,为空则报错;
  • 查询位置信息,如果已存在,则执行数量number加一,不存在则执行新增操作;
  • 添加入库记录信息,完成入库,返回。

共计4条SQL即可完成此一次入库操作,第一个版本是开发自己设计的,功能很明确,不会有其他操作和查询。

入库功能 V2.0

这个版本就是引起数据库崩溃的版本,改动原因很简单,新的"产品经理"觉得入库页面太丑,没有美感,因此要求重做,新的功能如下:进入后台-->仓库管理-->货架管理-->格子管理-->点击入库按钮-->入库-->刷新页面-->点击入库按钮-->入库,新的页面改动如下,原来的格子列表页只会显示格子的信息,但是新页面不同了,要显示格子上有什么商品,商品数量是多少,以及在此仓库***有多少此商品,分页列表显示,新的功能中要求在入库后执行页面刷新操作,让入库员可以看到变化。

看到这里,你可能觉得不妥或者不合理的地方,暂时先保留一下意见,我们来看一下新的改动执行了哪些SQL语句:

  • 根据商品码查询商品,为空则报错并提醒需要完善商品SKU;
  • 查询格子信息,为空则报错;
  • 查询位置信息,如果已存在,则执行数量number加一,不存在则执行新增操作;
  • 添加入库记录信息;
  • 查找位置信息列表("产品经理"要求一页20条数据);
  • 根据20条记录中的商品码查找对应的真实库存,完成入库,返回。

因为查询真实库存需要另外执行SQL语句,因此新的功能一次入库操作需执行的sql共计25条,除了第一个版本中的几条SQL外,这次功能改动加的SQL语句都是复杂SQL。

答疑

说明:由于牵涉到公司的一些业务,因此入库操作在文章中被简化了,实际的入库操作比文章中描述的过程要复杂一些,当时的功能改动比这个更为糟糕,本来的入库操作是执行大概6条SQL,但是在功能修改后,一次入库要执行60多条SQL。

  • Q:为什么要这么设计?
  • A:"产品经理"觉得好看。
  • Q:仓管需要这种设计吗?
  • A:"产品经理"觉得仓管是***,不用管他们的想法。
  • Q:为什么要商品所有的真实库存数据?
  • A:"产品经理"觉得需要全局统筹规划。
  • Q:页面好看了但是功能麻烦了,为什么还要这么做?
  • A:"产品经理"觉得仓管事情多一点无所谓。
  • Q:开发人员针对每个问题反驳了吗?
  • A:"产品经理"说要做,反驳没用。

这种设计其实一眼就知道多此一举,后来跟仓管私聊,他们也气得骂娘,而且在收到流程图就明确问了关于所有真实库存的问题,但是没办法,不做不行啊,原本一次入库可能也就一秒钟不到的时间,新功能一出来,有时可能需要3-4秒钟设置更长时间才行,而且还导致了这次事故。

崩溃原因

通过前文的描述,大致也能够知道是什么原因导致了数据库的崩溃,我们公司有一位女黑客!哈哈哈,这个是开玩笑的。

入库操作由原来的6条SQL执行语句增长为60条SQL执行语句,入库时长也随之增长,而且这60条SQL语句中关于查询真实库存的SQL也比较复杂,用到了多表联查及函数操作,性能也比较差,而当天的入库操作也比较密集,因此数据库承受了比原来要重n倍的负荷!资源被逐步耗尽也就不奇怪了。

keng

崩溃原因总结如下:

  • 一个业务功能执行了太多的SQL语句,此功能在短时间内又会被多次调用。
  • SQL语句中有复杂语句,比如用到了一些函数,比如多表联查,大数量的SQL语句加上复杂SQL语句无疑是雪上加霜。
  • 数据库连接池的选择和设置问题,导致出现了大量的数据库连接。
  • service层的代码不规范,select语句也加了事务,增加了一些不必要事务的开启和关闭,增加了myslq数据库的开销。
  • 部分表没有加索引,或者说索引不完整,导致了慢SQL的出现。

原因列举了这么多,事务出了问题、索引不规范导致查询出了问题、慢SQL的出现、数据库连接爆表,一环扣一环,一个问题牵连着一个问题出现,但是这些其实都不是主要的问题所在,第一环并不是这些,最主要的原因还是功能设计的极不合理,导致短时间内执行了巨量的SQL语句,进而将所有的不足之处都暴露出来,最终将问题引爆,一般情况下,慢SQL和复杂SQL语句并不会拖垮数据库,即使没有索引,也只是查询返回时间会多一些,不可能导致整个应用崩溃掉。

其实问题是多方面的,不仅仅是因为这次功能改动,虽然这次改动是导致问题的主因,但是代码不规范,表结构优化不到位,慢SQL没有处理,这些问题还是存在的,即使这次由于仓管流量的增加没有导致数据库崩溃,说不定下一次商城流量增加或者其他页面流量增加也会打垮数据库,因此,功能修改也是全方位的动刀,而不仅仅是回退版本就行了。

后续处理

让老板换女朋友是开玩笑的。

后续处理的步骤比较多,总结如下:

  • 入库功能修改,保留页面设计,功能做改动;
  • 数据库连接池更改;
  • 表结构优化;
  • 清理慢SQL;
  • 业务代码规范,减少事务开销;
  • Mysql参数修改,印象比较深的是wait_timeout参数;
  • 整合缓存功能。

虽然事故发生让人很无奈很沮丧,但是看到处理结果再去想想,如果没有类似这种事故的发生,也不会想着去优化代码,去优化数据库,去整合缓存等等一系列的操作,这些不仅让系统更加健壮,更重要的,是经验!因此不要害怕出现问题,经验就是在磕磕碰碰中增加的。

几年的工作经历,也让我渐渐明白了技术的成长离不开一个又一个的错误,失败中虽然有心酸和不甘,但是也不可否认它也带来了成长,不管是心态的强大,还是效率的提升,经验也是在一次次事故的产生和解决中积累。未来依然如此,还是会遇到一个又一个的难处。

忆往昔 2

在这次事件中,我也第一次接触到Mysql宕机,数据库竟然也能被请求到崩溃,以往遇到的是tomcat服务器被请求击垮或者服务器流量被打满,因此关于这件事的记忆比较深刻,可能细节记不太清晰,但是对我的影响还是很大的。这次事件后也是我第一次在项目中用到缓存,这也是为什么在写缓存整合文章前先写了这两篇文章。当然,一开始的整合代码是老大写的,后面又学了很久,才一点点的入门,不仅仅是入库操作,其他的功能中整合缓存对于系统来说也是极有帮助的,在这里,缓存就是负责减轻数据库的压力,转移一部分请求使得其压力不直接落在数据库上。

打个不恰当的比方,一个功能执行6条SQL会运行的很好,而执行60条SQL时,一旦操作比较密集就有可能会崩溃,而缓存就可以避免这一点,尽量的分担掉数据库的压力,不用每次请求都去访问数据库,就像这次事件中的60条SQL一样,如果后面的54条SQL语句返回的结果都放入缓存中,也就不会出现这个崩溃的事件了。

因为如今距离事故发生已经有大概两年的时间,所以可能无法回忆的特别精确,只能根据当时写的几篇日记来还原一下事件的经过,不过也仅仅是个大概,毕竟事件发生的时间点离现在有点远了。其实呢,过程的准确性倒不是特别重要,从这次事件记录里也能看出当时处理事情的不成熟和稚嫩,没想到用缓存去处理,因为压根没有这方面的概念,这件事情以现在的视角去看待的话肯定可以很快的定位到问题并且快速的处理掉,但是对于当时的我来说,还是很麻烦的,在技术角度来说甚至可以说是一件不可能的事情,一开始听到锁表的时候,整个人都蒙了,这是啥,数据库锁是什么?表锁了该怎么办?

结语

关于线上Mysql数据库崩溃事故记录的文章到这里就结束啦!如果有问题或者有一些好的创意,欢迎给我留言,也感谢向我指出项目中存在问题的朋友。

首发于我的个人博客,新的项目演示地址:perfect-ssm,登录账号:admin,密码:123456

如果你想继续了解该项目可以查看整个系列文章Spring+SpringMVC+MyBatis+easyUI整合系列文章,也可以到我的GitHub仓库或者开源中国代码仓库中查看源码及项目文档。


推荐阅读
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
author-avatar
用户rmgq8prdxo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有