前文提要

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

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

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

忆往昔 1

有张图,是后来老大发给我的,能够看出当时的数据库情况:



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

  1. show processlist;
  2. //找到锁进程
  3. 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,还有其他字段但是与入库操作无关联就不列举出来了。

入库功能 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倍的负荷!资源被逐步耗尽也就不奇怪了。

崩溃原因总结如下:

  • 一个业务功能执行了太多的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仓库或者开源中国代码仓库中查看源码及项目文档。

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

  1. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十五)阶段总结

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 一 每个阶段在结尾时都会有一个阶段总结,在<SSM整合基础篇& ...

  2. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十一)redis密码设置、安全设置

    警惕 前一篇文章<Spring+SpringMVC+MyBatis+easyUI整合进阶篇(九)Linux下安装redis及redis的常用命令和操作>主要是一个简单的介绍,针对redis ...

  3. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(六)一定要RESTful吗?

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 写在前面的话 这个问题看起来就显得有些萌,或者说类似的问题都有些不靠 ...

  4. 如约而至,Java 10 正式发布! Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十四)Redis缓存正确的使用姿势 努力的孩子运气不会太差,跌宕的人生定当更加精彩 优先队列详解(转载)

    如约而至,Java 10 正式发布!   3 月 20 日,Oracle 宣布 Java 10 正式发布. 官方已提供下载:http://www.oracle.com/technetwork/java ...

  5. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十四)Redis缓存正确的使用姿势

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 简介 这是一篇关于Redis使用的总结类型文章,会先简单的谈一下缓存 ...

  6. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API

    写在前面的话 看了一下博客目录,距离上次更新这个系列的博文已经有两个多月,并不是因为不想继续写博客,由于中间这段时间更新了几篇其他系列的文章就暂时停止了,如今已经讲述的差不多,也就继续抽时间更新< ...

  7. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(二)RESTful API实战笔记(接口设计及Java后端实现)

    写在前面的话 原计划这部分代码的更新也是上传到ssm-demo仓库中,因为如下原因并没有这么做: 有些使用了该项目的朋友建议重新创建一个仓库,因为原来仓库中的项目太多,结构多少有些乱糟糟的. 而且这次 ...

  8. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(九)Linux下安装redis及redis的常用命令和操作

    redis简介 Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis与其他key-value缓存产品有以下三个特点: Redis支持数据的持久化,可以将内存 ...

  9. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(七)一次线上Mysql数据库崩溃事故的记录

    作者:13 GitHub:https://github.com/ZHENFENG13 版权声明:本文为原创文章,未经允许不得转载. 文章简介 工作这几年,技术栈在不断更新,项目管理心得也增加了不少,写 ...

随机推荐

  1. JS字符串截取函数slice(),substring(),substr()的区别

    在JS中,slice().substring().substr()都有截取字符串的作用,那他们有哪些用法上的区别呢?如果你也有疑惑,这篇文章或许能够帮助到你. 一.substring() substr ...

  2. jmeter 压力测试

    转自: https://blog.csdn.net/cbzcbzcbzcbz/article/details/78023327 Jmeter压力测试简单教程(包括服务器状态监控) 2017年09月18 ...

  3. 洗礼灵魂,修炼python(31)--面向对象编程(1)—面向对象,对象,类的了解

    面向对象 1.什么是面向对象 (图片来自网络) 哈哈,当然不是图中的意思. 1).面向对象(Object Oriented,OO)是软件开发方法.利用各大搜索引擎得到的解释都太官方,完全看不懂啥意思对 ...

  4. 洗礼灵魂,修炼python(56)--爬虫篇—知识补充—编码之url编码

    其实在最前面的某一篇博文里,是绝对提过编码的,有ASCII,有UTF-8,有GB2312等等,这些我绝对说过的. url编码 首先,Http协议中参数的传输是"key=value" ...

  5. Python基础知识:集合

    1.集合(set)是一个存放在中括号内的无序,不重复的序列.例如:set = {'1','12','25'} 2.创建集合的两种方法: set = {1,2,3} 中括号直接创建 set = {[1, ...

  6. fedora更新

    先换源再更新,否则等的太久,如果已经开始了直接ctrl+c取消 # dnf update

  7. Beta冲刺博客集合贴

    Beta阶段第一次冲刺 Beta阶段第二次冲刺 Beta阶段第三次冲刺 Beta阶段第四次冲刺 Beta阶段第五次冲刺 Beta阶段总结博客

  8. mysql 拒绝访问的解决办法

    java.sql.SQLException: null,  message from server: "Host 'xxx' is not allowed to connect to thi ...

  9. python 在windows下的 虚拟环境

    解决 python 环境问题 windows 下安装 pip install virtualenv virtualenv的基本使用 1.1 创建虚拟环境 virtualenv venv 为环境指定Py ...

  10. eclipse 自动创建web.xml

    eclipse 自动创建web.xml 以前每次创建web项目的时候,总是从其他的项目中拷贝一个web.xml文件到新项目,然后改吧改吧就可以了,但是这终究是一件麻烦的事,要是能够通过软件自动创建,那 ...