如何快速优化几千万数据量的订单表
前言
為了保證有一個更健康的身體,所以慢慢降低了更新頻率,在有了更多休息時間的前提下,思考了一下接下來準備分享的一些內容。
決定在更新一些技術干貨的同時,會穿插一些架構知識,放在單獨的專欄里面,希望大家能喜歡,里面包含了這些年工作中遇到的一些內容,以及自己充電后總結的一些知識,希望大家會喜歡。
標題做了較為詳細的劃分,大家不必一次看完,以免視覺疲勞。
場景
本篇分享以前在廣州一家互聯網公司工作時遇到的狀況及解決方案,這家公司有一個項目是SOA的架構,這個架構那幾年是很流行的,哪怕是現在我依然認為這個理念在當時比較先進。
我在這家公司待的時間不長,但因為平臺不錯,確實學習和實踐了一點東西,所以整理一下分享給大家。
當時的項目背景大概是這樣,這家公司用的是某軟提供的方案(這公司賊喜歡提供方案且要錢多,忍不住吐槽哈),項目已經運行3年多,整體穩定。
數據庫是MySQL,訂單表的數據量已經達到3000多萬條記錄,并且隨著項目的推廣,最近那一年訂單表數據量也在快速增長。
結果就是,客戶方查詢訂單相關的業務時速度越來越慢,后期不論打開還是刷新都差不多要七八秒。
可以說已經嚴重影響了客戶體驗,降低了對方日常辦事的效率,要求我們盡快解決,且敦促我們這是一件優先級非常高的事情。
在客戶和公司領導的雙重壓力下,如何快速優化幾千萬數據的訂單表,對于當時的團隊著實是一個難題擺在面前。
我依稀記得自己當時還比較青澀,更多的是一個聽眾,不敢參與深入討論哈哈。
整體方案
首先常規方案能想到的無非是這些:增加合理的數據庫索引、優化核心SQL語句、優化代碼等。
我這里可以告訴大家,一般的IT公司,但凡團隊Leader是個有經驗的人,這些基礎方案都是會提前做的,會對項目上線后可能遇到的瓶頸有個基本的評估,因為真正運營周期變長以后,數據量逐漸增多,修改生產庫是一種風險操作。
我不知道大家有沒有過給某個生產庫數據量比較大的表添加字段或索引的經歷,而且是在白天上班操作,或者說你自己見過別人這么干,我只能說……這些都是狠人,要對其常懷敬畏之心。
我目前所在的公司就比較規范,研發人員建表時一定要提交申請走流程,且附帶合理的索引,一起提交審核,最終通過了才能由主管審核執行。
至于這種流程怎么走,其實工具挺多,我這里就提一個用過的開源項目:Yearning,大家可以自己去了解下。
話題扯回來,正因前面所講,在當前的問題下,這些基礎方案實際上已經存在,在這里顯然是用不上了,加上緊急問題緊急處理,沒有那么多時間給你去對既有架構大動干戈。
因此,當時立馬能想到且有效的臨時性方案迅速在團隊討論中率先冒出來,就是數據庫分區。
1、數據庫分區
理解數據庫分區,只需要記住以下兩點:
- 數據庫分區是把一張表的數據
分在了不同的硬盤上,但仍是一張表,說硬盤可能不完全準確,但就這樣理解是最容易的。 - 不要把數據庫分區和分庫分表混淆,一個是
數據庫級別的操作,一個是代理工具的操作,前者限制較多,后者更靈活。
知道這兩點其實就足夠了,數據庫分區和分庫分表也是面試中喜歡問的,因為確實有一些類似的地方。
好了,有了基本認識,那接下來就說下數據庫分區如何操作的,先看個圖有個畫面。
接著舉個示例,我們假設有一張訂單表,那么對這張訂單表按照年份進行分區的命令如下:
-- 創建訂單表
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(20),
order_date DATE,
customer_id INT,
total_amount DECIMAL(10, 2)
);
-- 按照年份對訂單表進行分區
ALTER TABLE orders
PARTITION BY RANGE(YEAR(order_date)) (
PARTITION p2018 VALUES LESS THAN (2019),
PARTITION p2019 VALUES LESS THAN (2020),
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024)
);
這樣一來,數據庫就會將這張表的數據按照YEAR(order_date)的值分別存在 p2018 ~ p2023 這6個分區中。
如果結合本篇的問題,3000多萬條記錄,那么按照年份分區,大概一個分區是1000萬記錄左右,然后可以優化查詢語句只去掃描特定的分區,是不是一下就輕松了很多。
再深入點,按照年份一個分區1000萬還是有點多了,我們是不是可以找到一個更合理的分區字段,讓每個分區的數據更少呢?
這里就要結合實際業務了,沒有真正的通用方案。
你要先明確一點,做分區的目的,最終是為了讓某個業務環節的查詢更快,就比如本篇這里,主要是為了讓客戶查詢訂單相關業務更快,那么你就要先把這塊的查詢語句摘取出來,分析一下里面的where條件有哪些。
-
比如,客戶要查詢某個或某些狀態的訂單,可能會這樣寫:where order_status in (?);
-
比如,客戶要查詢某個特定群體的訂單,可能會這樣寫:where user_flag_id = ?;
-
比如,客戶要查詢某個或多個業務類型的訂單,可能會這樣寫:where order_type in (?);
甚至,還可能有其他的組合條件摻雜進來,你千萬別以為你去的每個公司都把表設計的很漂亮很合理,我這么多年工作下來,真見過不少奇葩設計,訂單表里面能給你塞上openId或者某些單純為了方便而加的冗余字段,完全把訂單表自身的功能性打碎。
這個時候,如果分區字段本身存在,且剛好能把分區數據分的很合理,有利于查詢,比如前面按照年份劃分,每個分區如果只有兩三百萬記錄,再結合本身的索引,查詢就會很快,那么一切安好,搞完收工。
但如果分區字段很難定位,就像上面講的,一些主要SQL語句的where條件并不包含相同的字段,那就頭大了。
而且MySQL還有一個需要注意的點,就是它的分區本身是有限制的。
MySQL分區字段必須是唯一索引的一部分。
也就是說,如果沒有其他能用的唯一索引,我們只能結合主鍵ID,和分區字段組成復合主鍵才行。
這就更難了,純粹看這表長什么樣了。
話到這里,其實大家也看出來了,數據庫分區的優缺點很明顯。
優點:遇到合適的場景,優化起來就是一個命令的事情。
缺點:限制太多,稍微復雜一點的場景你就很難定位分區字段。
那么,真的就沒法分了嗎?其實還有一個迂回的方案。
2、迂回方案
我們可以在表中新增一個專門為了分區而量身定做的字段,比如archive_flag,表示一種數據歸檔狀態,當值為1時表示已歸檔,值為0時表示未歸檔。
這個字段可以沒有業務意義,但一定要有分區意義。
我們可以把半年內的數據刷成 archive_flag=0,半年以外的數據刷成 archive_flag=1。
接下來,我們按照歸檔狀態進行分區即可,半年內的活躍數據是一個分區,其他非活躍數據是一個分區。
最后,只需要把核心的查詢語句where條件中都新增一個 archive_flag=0 就可以了,這樣就會掃描這個非歸檔狀態的分區,也就是活躍數據的分區。
試想一下,這個分區只有半年的記錄,按照本篇的場景,最多也就是500萬了,結合自身表索引,已經完全可以解決當前存在的問題。
好了,這個迂回方案其實挺不錯的,但一定有人會有疑問。
1)、加字段真的好嗎?
2)、為什么一定要半年內的數據?
首先解答第一個問題,答案是不好,在我這里的話甚至可以說非常不好,幾千萬數據量的表,為了解決一個查詢問題刻意新增一個沒有實際意義的字段,是舍本逐末的行為,如果除了這張表,還有其他表也有類似問題,難道每個都要加字段嗎?顯然是不可行,也是不安全的。
第二個問題,半年內的數據完全可以結合實際業務做修改。
舉個簡單的例子,你如果經常逛京東商城購物,一定會打開我的訂單看看,實際上給你展示的就是近3個月的訂單,你可以理解成這就是非歸檔的活躍數據。
當你想查詢以前的記錄時,就會給你一個鏈接叫歷史記錄,點擊后跳轉到歷史記錄列表,或者通過其他方式如下拉框,讓你選擇其他更早時間的訂單數據,這種其實就是已經歸檔的數據。
這些數據一般不會直接從業務表里查出來,而是從其他歸檔表,或者非關系型數據庫如mongodb、EasticSearch等查詢出來。
這種方式就類似做了分區,把你經常訪問的數據和訪問頻率較低的數據分布存儲,達到一個數據分離的目的。
這樣你就懂了,數據分區大體就是這樣的思考方式。
現在回過頭來想想前面說的優缺點,數據庫分區真的合適嗎?
實際情況下,很少有情況合適,主要原因還是前面講過的,限制真的太多了,而業務往往又是復雜的。
另外,數據庫分區對于很多程序員來說,其實是陌生的,在中小企業更是如此,有這樣的現實擺在面前,加上短期內就要解決問題,隨便使用的話對于團隊來講也是一種風險。
所以,另一種更合理的方案也就呼之欲出了,數據的冷熱分離。
3、冷熱分離
前面講了那么多,其實就是為了過渡到這里來,上面的迂回方案或多或少已經摸到了冷熱分離的邊緣,主要是為了讓大家知其然并知其所以然。
1)、基本概念
冷熱分離聽起來很高端,其實本質很簡單,就是把活躍數據和非活躍數據區分開,一熱一冷,頻率高的查詢只操作熱數據,頻率低的只操作冷數據。
2)、存儲方案
既然要分離,就要考慮清楚熱數據和冷數據分別放在哪里。
這里我提供兩種選擇:
中小企業,我推薦依然用MySQL。
一來是不需要額外成本(降本增效?哈哈),二來是中小企業相對大廠,業務復雜度低一點且數據量小很多,那么此時完全可以用MySQL新增一張表來存儲某個業務的冷數據,比如訂單。
如果需要冷熱分離的業務較多,也可以建一個單獨的冷庫,來專門存放冷數據,不過這種我也不太推薦,因為涉及到跨庫查詢,增加了維護難度,咱們程序員盡量對自己好一點哈。
一個項目里面,其實兩三張冷表的出現已經可以處理核心業務數據冷熱分離的問題了,如果真有那么多大數據的表,我覺得要從其他方面找問題了(一些老項目,設計上本身有問題,那是真的沒好辦法)。
大廠,推薦HBase。
大廠的資源較多,平臺較大,冷熱分離不單是解決這種問題的唯一方案,但大廠比較推薦更合適的數據庫來存儲這樣的冷數據。
其中HBase是我從各種資料中見過的最多的一種,當然也有其他的,但HBase應該是里面最受歡迎的一類。
當然,我個人是沒有大廠經驗的,我只能把我掌握到的訊息告訴你們。
如果有興趣的話,可以去學習下HBase,它是一種在 Hadoop 上構建的分布式、可擴展的列式數據庫。
它最大的優勢就是快速讀寫海量數據,且具有強一致性。
一般大廠對于冷數據的處理,往往都是因為冷數據在業務中也有相當的查詢體量,如果太慢也不符合大廠維護項目的標準,所以有必要專門優化。
好了,這里之所以提到HBase,主要是為了擴充大家的知識面,其實中小企業的工程師也沒啥必要特地去學,依靠自身興趣驅動即可。
3)、區分冷熱數據
既然要冷熱分離,那么一張表中,如何區分哪些是熱數據,哪些是冷數據?
要分析這張表的
字段特征,拿訂單表舉例,馬上能想到的就是:訂單狀態、創建時間。
訂單狀態的話,其實也類似于前面數據庫分區提過的歸檔狀態,你可以將狀態是已完成的數據歸類為冷數據,而待處理、處理中的都歸類為熱數據,這個要視你們自己的業務決定。
創建時間的話,就比較常見了,也是我推薦中小企業使用的方法,因為幾乎所有的核心業務表,都一定會有創建時間這個字段,我們可以把查詢頻繁的時間區間的數據歸類為熱數據,其他時間都歸類為冷數據。
比如本篇我講的案例,當時我們公司就是半年內的數據是查詢非常頻繁的,因此直接按照最近半年作為區分冷熱數據的規則。
4)、如何冷熱分離
這里有四種方案:
- 代碼中處理
這個很好理解,比如訂單表中,當狀態從處理中改為已完成時,你就可以將這條記錄歸類為冷數據,放到冷表或冷庫中。
優點是很靈活,而且實時性高。
缺點是相關的代碼位置你都要做修改,另外如果是按照時間做冷熱分離,這個方案基本就不可取。
你想想,你怎么判斷呢?我們按照半年內的數據作為熱數據,那么你在哪個方法哪個事件觸發時將這筆訂單歸類為冷數據?可以說做不到。
- 任務調度處理
這種就是定時任務去掃描數據庫,比如xxl-job,新建一個調度任務,定時去掃描數據庫,判斷哪些是冷數據,然后歸檔到冷表或冷庫中去。
這種的優點,一來是不用大量修改代碼,二來就是非常適合按照時間劃分冷熱數據的場景。因為它是一種延遲處理方式,你可以設置為半夜去運行。
比如我之前的那家公司,就是設置為凌晨以后執行,因為那個時候很少有用戶在使用了,沒有什么新的訂單產生,哪怕有新的訂單,也屬于誤差范圍內,可以接受。
- 監聽binlog
這種方案我是從書本上獲取到的,給我漲了點知識。
監聽binlog的目的說白了,就是判斷訂單狀態是否變化,和代碼中處理很類似,唯一的區別在于,
如果你維護的這個項目又老又復雜,代碼很難改也改不全,監聽binlog就是很好的方案了,你可以不改代碼,監聽數據庫變更日志然后做相應處理即可。當然,缺點和前面一樣,當按照時間來劃分冷熱數據時,這種方案也不可取,因為你不知道如何監聽。
- 人工遷移
冷熱分離操作的最終還是數據,分離實質上也就是一種數據遷移,因此,人工干預其實是很靠譜的選擇。
上面每種方案都有自己的優勢,但也有各自的局限性。
代碼處理,你只能處理發布上線以后的新數據。
任務調度,當數據量龐大的情況下,你一次可能根本無法完成分離,對于緊急的要快速優化的場景顯然不適合。
監聽binlog,除了前面提到的缺點,還需要工程師對其比較熟悉,否則短時間內上手容易帶來不確定性。
此時,DBA或集成工程師(俗稱打雜工程師)的優勢就體現出來了,備份后,抽某天晚上,直接把半年以外的數據遷移到冷庫即可。
這樣不僅簡單,也避免了其他技術方案可能存在的問題及風險。專業的人,做專業的事,才是最靠譜的。
4、最終方案
通過上面簡述的幾種方案,我們已經有了較為清晰的認知。
現在我可以告訴大家,當初的公司所采用的方案是其中兩種方案的結合:
人工遷移 + 任務調度。
人工遷移用于一次遷移完成冷數據到冷庫,任務調度用于對后續新產生的數據進行解耦且延遲的冷熱分離。
思維導圖大概是這樣:
基本步驟如下:
-
1)、定位冷熱分離的規則,比如本篇,就是按照訂單交易完成時間,以半年內和半年外作為分離的基準;
-
2)、冷數據遷移,由公司的DBA或集成工程師對數據進行備份,然后在發布當晚將冷數據遷移到冷庫中去;
-
3)、開發人員新建一個調度任務,并實現任務調用的接口,專門掃描數據庫,將超過半年的訂單數據通過程序邏輯遷移到冷庫,保證熱數據一直維持在半年內,任務可以每天凌晨執行一次,或根據自身業務決定調度頻率。
這樣一來,既解決了冷熱分離規則的問題,不管是什么規則,你最終都可以通過人工遷移數據來做到分離。
也解決了時間上的緊迫性,你只需要開發一個用于調度的接口,不再需要考慮其他任何技術層面的影響,時間成倍縮短。
這在中小企業算是比較適合的方案了,當初我們在一周內就優化完成了,研發工程師用了1天完成調度接口的實現,剩下的時間都是集成工程師進行數據遷移的演練。
最終客戶還是很滿意的,核心業務的查詢速度一下就提升了近10倍。
優缺點
好了,臨近尾聲,我們來說一下冷熱分離方案整體的優缺點吧。
1、優點
優點我歸納了3點:
1)、提高性能
很明顯,冷熱分離后,將更多計算資源集中在了熱數據上,將查詢性能最大化。
2)、降低成本
對于千萬級的數據表,冷熱分離方案不需要額外的第三方中間件,極大地節約了成本。尤其是在中小公司,老板對成本還是很在意的。
3)、簡化維護
冷熱分離之后,對于數據的維護更直觀,可以把更多精力放在熱數據的處理上。
比如備份策略,冷熱數據可以分別采用不同的策略維護,更關注熱數據備份,簡化冷數據備份。
2、缺點
缺點我歸納了2點:
1)、場景限制多
冷熱分離并不是萬能的,一定要根據業務來分析,查詢的復雜度較高,很可能你冷熱分離后,熱數據的查詢依然沒有得到明顯優化。
比如你有一張表,查詢的語句關聯很多,表數據量也挺大,那么這個時候冷熱分離一點作用都沒有,因為你分離完了,查詢語句還是關聯那么多,速度依然很慢。
這個時候,類似的場景就無法使用冷熱分離方案了,而是要考慮其他方案,比如讀寫分離,比如查詢分離,這樣才能從根源上解決查詢慢的問題。
2)、統計效率低
這種也是冷熱分離方案比較明顯的一個缺點,當你們的業務中,需要對數據做一些復雜的統計分析,甚至要求一定的實時性。
那么這個時候,因為已經冷熱分離,冷數據的統計分析效率會非常低,對于客戶提出的一些五花八門的統計分析就難以操作了。
因此,又需要引入其他方案來配合,比如ElasticSearch,這樣又增加了額外的成本,不僅要考慮ES的資源成本,還要考慮諸如部署方案、維護方案、安全性問題等等。
今年我們內部就公布了一個小道消息,某家業內還挺不錯的互聯網公司因為ElasticSearch的未授權漏洞導致千萬用戶敏感信息被泄露,直接被行業除名了。
所以,在實際工作中,中間件的引入是個需要審慎考慮的問題,而不是你想當然了就可以使用。
總結
通篇寫的還是挺長的,主要是一開始列出了大綱,但在寫的過程中又想起了新的知識點,就一起加進來了。
前面講的數據庫分區等方案,主要是為了過渡,因為這是一個線性的思維,展現出來讓大家知道一個方案最終落地的脈絡是怎樣的。
今后還會繼續寫一些架構相關的知識,放在單獨的專欄里面,希望大家支持和喜歡。
如果喜歡,請點贊+關注↓↓↓,持續分享工作經驗及各種干貨哦!
總結
以上是生活随笔為你收集整理的如何快速优化几千万数据量的订单表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 琵琶行并序翻译对照原文
- 下一篇: 库表分离