前段時間接到一個需求,由於排期很是緊,沒有認真規劃就開幹了。sql
需求描述起來大體是這樣:門店的app首頁會展現6張門店圖片,之前只有A類門店,如今新增了B類門店,也須要首頁圖片。可是不一樣於A類門店不存在狀態,B類門店的首頁圖片須要有 上傳-->審覈\駁回
這樣一個操做流程,分別對應圖片 待審覈、已經過、已駁回
三種狀態。數據庫
以前A類門店是直接將6張圖片的url數據拼接成一個字符串,直接存放門店表中,以下圖:緩存
由於B類圖片存在狀態,因此我建立了一個新表,將app_img_url字段拆分若干條記錄,以下圖:併發
修改後,對於圖片的增刪改查操做,須要同時操做兩張表,其中有個查詢門店信息的rpc接口,是高頻調用,實現後的調用流程以下:app
可是我新增的查詢門店圖片信息實現,沒有加緩存,致使上線後的次日早上,數據訪問量一個小時內激增近千萬。數據庫設計
頭皮發麻。高併發
趕忙加上緩存,緊急部署,然鵝並無訪問量並無所有降下來。又開始排查,發現兩點緣由:性能
冷靜分析一波,忽然感受被本身蠢哭了!以前之因此將門店信息和圖片信息分紅兩個sql查詢,是由於在其餘地方實現了查詢圖片信息的接口,直接複用,當時還沾沾自喜,感受本身遵循了開閉原則。。。僞代碼以下:url
public Class ***ServiceImpl { ... // 查找門店信息 Shop shop = getShopInfo(); // 查找圖片信息 (新增代碼) List<Img> imgs = listImgInfo(); //設置圖片信息 (新增代碼) shop.setImgs(imgs); ... }
這樣只是我寫的代碼最少,可是卻不是遵循了開閉原則,而是對原有接口進行了修改,徹底能夠在SQL查詢中關聯兩張表,這樣就不會有上面的問題了。流程以下:spa
可是這樣作仍是有一個問題,那就是當查詢量大的時候,left join對性能的損耗仍是很大的。
這樣我就考慮到是否將圖片的url在保存的時候,在兩張表都保存(只針對已經過狀態的圖片,由於查詢只查找已經過狀態的圖片),這樣查詢rpc接口徹底不用作任何修改,和原來的邏輯同樣,只改變的了增刪改的邏輯,而增刪改是低頻操做。
上述bug是多方面緣由致使的:
緣由1暫且不論,着重討論一下緣由2和緣由3。
開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應儘可能在不修改原有代碼的狀況下進行擴展。
從開閉原則的定義能夠看出,個人第一次修改,很明顯沒有遵循這一原則,在工做實踐中,大部分狀況下不遵循設計原則並不會形成很大的麻煩,倒黴的是,我趕上了少部分狀況。
因此對於一些普通的業務代碼,遵循設計原則,可能會對代碼後期維護有利,可是收益通常並不明顯。
可是對於一些高頻率高併發的接口,尤爲是其餘系統提供的接口,遵循設計原則就變得尤其重要。由於這些接口通常調用頻率會很高,同時後期擴展變更的概率大。
這一點指的是,原來的數據庫設計就沒有爲後期擴展留出餘量,一個門店對應多個圖片,這種一對多的問題,通常狀況下,應該建立兩張表,兩張表之間經過主鍵id或者編號關聯,這樣若是後期,圖片數量增長了(好比由6張圖片變成100張),圖片類型變化了(好比須要區分圖片的審覈狀態、區分圖片的類型等等)······對於這些變化,兩張表的設計擴展修改起來會方便許多。