前文提要
承接前文《一次線上Mysql數據庫崩潰事故的記錄》沃呢,在文章中講到了一次線上數據庫崩潰的事件記錄气堕,建議兩篇文章結合在一起看,不至于摸不著頭腦畔师。
由于時間原因娶靡,其中只講了當時的一些經過以及我當時的一些心理活動,至于原因和后續(xù)處理步驟并沒有在文章中很清晰的寫出來看锉,以致于很多朋友說看得不清不楚的姿锭,這里向他們道個歉,主要是上周真的沒有足夠的時間將兩篇文章同時準備好伯铣,不然也不會草草結尾了呻此,而且上篇文章中主觀因素占了較大的比重,因為回憶起這件事的時候確實有很多想法腔寡,因此顯得有些個人化趾诗、日記化了。
這篇文章就不再講述事件經過了蹬蚁,主要是把事件的原因和后續(xù)的處理步驟整理好恃泪。
憶往昔 1
有張圖,是后來老大發(fā)給我的犀斋,能夠看出當時的數據庫情況:
這是數據庫宕機后的實例信息贝乎,基本癱瘓了,至于事務鎖相關的截圖叽粹,當時沒有保存览效,因此就無法放在文章中了,有朋友給我留言問當時為什么不直接kill掉鎖住的進程虫几,我回答是因為我不懂這個知識點锤灿,我找了一下那幾天的日記確實有這方面的記錄,是當時老大發(fā)我的幾條命令:
show processlist;
//找到鎖進程
kill id ;
應該也是做了這些操作的辆脸,但是看到上面那張圖也應該知道為什么這些都不管用了但校,事務鎖確實存在,導致了部分表無法正常操作啡氢,但是主要原因還是因為數據庫資源被消耗光了状囱,即使kill了相關的進程也無法解決术裸。
入庫功能介紹
已經定位到入庫是發(fā)生這次事故的主要原因,那么為什么頻繁的操作會導致這件事的發(fā)生呢亭枷?
首先來介紹一下當時的功能設計改動及涉及到的SQL語句袭艺。
表結構設計及功能設計
入庫功能中涉及到的表和實體:
- 倉庫信息(tb_storehouse)
- 貨架信息(tb_shelf)
- 格子信息(tb_shelf_grid)
- 商品信息(tb_product)
- 商品位置信息(tb_grid_product)
- 入庫信息(tb_store_in_record)
釋義:
- 由于有多個倉庫,因此倉庫也獨立做了一張表;
- 一個倉庫中有多個貨架叨粘,tb_storehouse與tb_shelf是一對多的關系;
- 一個貨架中有多個格子(貨架規(guī)格不同猾编,有的是8個有的是4個),tb_shelf與tb_shelf_grid也是一對多的關系;
- 商品信息升敲,以商品碼作為主鍵袍镀,還有其他屬性,但是與入庫信息無關就沒有羅列出來;
- 商品的位置信息就是一件商品是在哪個格子上冻晤,表結構的設計就是四個字段:id,商品碼绸吸,格子id鼻弧,number(數量),存儲了兩個屬性的主鍵id和數量值;
-
入庫記錄信息锦茁,就是哪件商品在哪個時間點由哪個入庫員在進行入庫攘轩,涉及到的字段有:product_id,grid_id,operator_id,create_time,還有其他字段但是與入庫操作無關聯就不列舉出來了。
入庫功能 V1.0
在最初的版本中码俩,入庫功能的設計較為簡單和清晰度帮,入庫操作只做一件事,就是入庫稿存,頁面邏輯也比較簡單:進入后臺-->倉庫管理-->貨架管理-->格子管理-->點擊入庫按鈕-->入庫笨篷,進入想要入庫的格子頁面點擊入庫,這時頁面會彈出一個彈框瓣履,上面有一個input框率翅,然后入庫員用掃描槍掃描商品碼即可完成入庫,入庫過程中input框為不可選中狀態(tài)袖迎,成功后方可進行下一次入庫冕臭,這種方式入庫也挺快的,入庫員找到想要入庫的格子然后一直掃碼就可以了燕锥。
在最初的版本中需要執(zhí)行的SQL語句有:
- 根據商品碼查詢商品辜贵,為空則報錯并提醒需要完善商品SKU;
- 查詢格子信息,為空則報錯;
- 查詢位置信息归形,如果已存在托慨,則執(zhí)行數量number加一,不存在則執(zhí)行新增操作;
- 添加入庫記錄信息暇榴,完成入庫榴芳,返回嗡靡。
共計4條SQL即可完成此一次入庫操作,第一個版本是開發(fā)自己設計的窟感,功能很明確讨彼,不會有其他操作和查詢。
入庫功能 V2.0
這個版本就是引起數據庫崩潰的版本柿祈,改動原因很簡單哈误,新的"產品經理"覺得入庫頁面太丑,沒有美感躏嚎,因此要求重做蜜自,新的功能如下:進入后臺-->倉庫管理-->貨架管理-->格子管理-->點擊入庫按鈕-->入庫-->刷新頁面-->點擊入庫按鈕-->入庫,新的頁面改動如下卢佣,原來的格子列表頁只會顯示格子的信息重荠,但是新頁面不同了,要顯示格子上有什么商品虚茶,商品數量是多少戈鲁,以及在此倉庫中共有多少此商品,分頁列表顯示嘹叫,新的功能中要求在入庫后執(zhí)行頁面刷新操作婆殿,讓入庫員可以看到變化。
看到這里罩扇,你可能覺得不妥或者不合理的地方婆芦,暫時先保留一下意見,我們來看一下新的改動執(zhí)行了哪些SQL語句:
- 根據商品碼查詢商品喂饥,為空則報錯并提醒需要完善商品SKU;
- 查詢格子信息消约,為空則報錯;
- 查詢位置信息,如果已存在员帮,則執(zhí)行數量number加一荆陆,不存在則執(zhí)行新增操作;
- 添加入庫記錄信息;
- 查找位置信息列表("產品經理"要求一頁20條數據);
- 根據20條記錄中的商品碼查找對應的真實庫存,完成入庫集侯,返回被啼。
因為查詢真實庫存需要另外執(zhí)行SQL語句,因此新的功能一次入庫操作需執(zhí)行的sql共計25條棠枉,除了第一個版本中的幾條SQL外浓体,這次功能改動加的SQL語句都是復雜SQL。
答疑
說明:由于牽涉到公司的一些業(yè)務辈讶,因此入庫操作在文章中被簡化了命浴,實際的入庫操作比文章中描述的過程要復雜一些,當時的功能改動比這個更為糟糕,本來的入庫操作是執(zhí)行大概6條SQL生闲,但是在功能修改后媳溺,一次入庫要執(zhí)行60多條SQL。
- Q:為什么要這么設計碍讯?
- A:"產品經理"覺得好看悬蔽。
- Q:倉管需要這種設計嗎?
- A:"產品經理"覺得倉管是傻子捉兴,不用管他們的想法蝎困。
- Q:為什么要商品所有的真實庫存數據?
- A:"產品經理"覺得需要全局統籌規(guī)劃倍啥。
- Q:頁面好看了但是功能麻煩了禾乘,為什么還要這么做?
- A:"產品經理"覺得倉管事情多一點無所謂虽缕。
- Q:開發(fā)人員針對每個問題反駁了嗎始藕?
- A:"產品經理"說要做,反駁沒用氮趋。
- Q:"產品經理"是誰伍派?
- A:老板的女朋友。
解答完畢凭峡!
這種設計其實一眼就知道多此一舉,后來跟倉管私聊决记,他們也氣得罵娘摧冀,而且在收到流程圖就明確問了關于所有真實庫存的問題,但是沒辦法系宫,不做不行啊索昂,原本一次入庫可能也就一秒鐘不到的時間,新功能一出來扩借,有時可能需要3-4秒鐘設置更長時間才行椒惨,而且還導致了這次事故。
崩潰原因
通過前文的描述潮罪,大致也能夠知道是什么原因導致了數據庫的崩潰康谆,我們公司有一位女黑客!哈哈哈嫉到,這個是開玩笑的沃暗。
入庫操作由原來的6條SQL執(zhí)行語句增長為60條SQL執(zhí)行語句,入庫時長也隨之增長何恶,而且這60條SQL語句中關于查詢真實庫存的SQL也比較復雜孽锥,用到了多表聯查及函數操作,性能也比較差,而當天的入庫操作也比較密集惜辑,因此數據庫承受了比原來要重n倍的負荷!資源被逐步耗盡也就不奇怪了。
崩潰原因總結如下:
- 一個業(yè)務功能執(zhí)行了太多的SQL語句禽炬,此功能在短時間內又會被多次調用曾我。
- SQL語句中有復雜語句,比如用到了一些函數撵彻,比如多表聯查钓株,大數量的SQL語句加上復雜SQL語句無疑是雪上加霜。
- 數據庫連接池的選擇和設置問題陌僵,導致出現了大量的數據庫連接轴合。
- service層的代碼不規(guī)范,select語句也加了事務碗短,增加了一些不必要事務的開啟和關閉受葛,增加了myslq數據庫的開銷。
- 部分表沒有加索引偎谁,或者說索引不完整总滩,導致了慢SQL的出現。
原因列舉了這么多巡雨,事務出了問題闰渔、索引不規(guī)范導致查詢出了問題、慢SQL的出現铐望、數據庫連接爆表冈涧,一環(huán)扣一環(huán),一個問題牽連著一個問題出現正蛙,但是這些其實都不是主要的問題所在督弓,第一環(huán)并不是這些,最主要的原因還是功能設計的極不合理乒验,導致短時間內執(zhí)行了巨量的SQL語句愚隧,進而將所有的不足之處都暴露出來,最終將問題引爆锻全,一般情況下狂塘,慢SQL和復雜SQL語句并不會拖垮數據庫,即使沒有索引鳄厌,也只是查詢返回時間會多一些睹耐,不可能導致整個應用崩潰掉。
有人可能會說部翘,讓老板換個女朋友不就好了嗎硝训?
噓~小聲一點。
還有人可能會說,到這里不就簡單了嗎窖梁?直接回退版本就好了吧赘风。
其實問題是多方面的,不僅僅是因為這次功能改動纵刘,雖然這次改動是導致問題的主因邀窃,但是代碼不規(guī)范,表結構優(yōu)化不到位假哎,慢SQL沒有處理瞬捕,這些問題還是存在的,即使這次由于倉管流量的增加沒有導致數據庫崩潰舵抹,說不定下一次商城流量增加或者其他頁面流量增加也會打垮數據庫肪虎,因此,功能修改也是全方位的動刀惧蛹,而不僅僅是回退版本就行了扇救。
后續(xù)處理
讓老板換女朋友是開玩笑的。
后續(xù)處理的步驟比較多香嗓,總結如下:
- 入庫功能修改迅腔,保留頁面設計,功能做改動;
- 數據庫連接池更改;
- 表結構優(yōu)化;
- 清理慢SQL;
- 業(yè)務代碼規(guī)范靠娱,減少事務開銷;
-
Mysql參數修改沧烈,印象比較深的是
wait_timeout
參數; - 整合緩存功能。
雖然事故發(fā)生讓人很無奈很沮喪像云,但是看到處理結果再去想想锌雀,如果沒有類似這種事故的發(fā)生,也不會想著去優(yōu)化代碼苫费,去優(yōu)化數據庫汤锨,去整合緩存等等一系列的操作双抽,這些不僅讓系統更加健壯百框,更重要的,是經驗牍汹!因此不要害怕出現問題铐维,經驗就是在磕磕碰碰中增加的。
幾年的工作經歷慎菲,也讓我漸漸明白了技術的成長離不開一個又一個的錯誤嫁蛇,失敗中雖然有心酸和不甘,但是也不可否認它也帶來了成長露该,不管是心態(tài)的強大睬棚,還是效率的提升,經驗也是在一次次事故的產生和解決中積累。未來依然如此抑党,還是會遇到一個又一個的難處包警。
憶往昔 2
在這次事件中,我也第一次接觸到Mysql宕機底靠,數據庫竟然也能被請求到崩潰害晦,以往遇到的是tomcat服務器被請求擊垮或者服務器流量被打滿,因此關于這件事的記憶比較深刻暑中,可能細節(jié)記不太清晰壹瘟,但是對我的影響還是很大的。這次事件后也是我第一次在項目中用到緩存鳄逾,這也是為什么在寫緩存整合文章前先寫了這兩篇文章稻轨。當然,一開始的整合代碼是老大寫的严衬,后面又學了很久澄者,才一點點的入門,不僅僅是入庫操作请琳,其他的功能中整合緩存對于系統來說也是極有幫助的粱挡,在這里,緩存就是負責減輕數據庫的壓力俄精,轉移一部分請求使得其壓力不直接落在數據庫上询筏。
打個不恰當的比方,一個功能執(zhí)行6條SQL會運行的很好竖慧,而執(zhí)行60條SQL時嫌套,一旦操作比較密集就有可能會崩潰,而緩存就可以避免這一點圾旨,盡量的分擔掉數據庫的壓力踱讨,不用每次請求都去訪問數據庫,就像這次事件中的60條SQL一樣砍的,如果后面的54條SQL語句返回的結果都放入緩存中痹筛,也就不會出現這個崩潰的事件了。
因為如今距離事故發(fā)生已經有大概兩年的時間廓鞠,所以可能無法回憶的特別精確帚稠,只能根據當時寫的幾篇日記來還原一下事件的經過,不過也僅僅是個大概床佳,畢竟事件發(fā)生的時間點離現在有點遠了滋早。其實呢,過程的準確性倒不是特別重要砌们,從這次事件記錄里也能看出當時處理事情的不成熟和稚嫩杆麸,沒想到用緩存去處理搁进,因為壓根沒有這方面的概念,這件事情以現在的視角去看待的話肯定可以很快的定位到問題并且快速的處理掉昔头,但是對于當時的我來說拷获,還是很麻煩的,在技術角度來說甚至可以說是一件不可能的事情减细,一開始聽到鎖表的時候匆瓜,整個人都蒙了,這是啥未蝌,數據庫鎖是什么驮吱?表鎖了該怎么辦?
結語
關于線上Mysql數據庫崩潰事故記錄的文章到這里就結束啦萧吠!如果有問題或者有一些好的創(chuàng)意左冬,歡迎給我留言,也感謝向我指出項目中存在問題的朋友纸型。
首發(fā)于我的個人博客拇砰,新的項目演示地址:perfect-ssm,登錄賬號:admin,密碼:123456
如果你想繼續(xù)了解該項目可以查看整個系列文章Spring+SpringMVC+MyBatis+easyUI整合系列文章,也可以到我的GitHub倉庫或者開源中國代碼倉庫中查看源碼及項目文檔。