原文地址前端
對於數據庫而言,在平常開發中咱們主要的關注點有兩塊,一個是schema的結構設計,另外一個就是索引的優化,這兩塊是影響咱們最終系統結構和性能的關鍵部分,天然也是咱們花費精力最多的部分;mysql
本文主要介紹數據庫設計中的通常原則和優化手段,包括數據庫的一半範式、反範式設計、數據切分、數據路由與合併等等算法
範式理論是關係型數據庫設計的黃金法則,它提供了數據結構化的理論基礎,有效地保證了數據的一致性,應該說,關係型數據庫就是在範式的基礎上才成長起來的。sql
數據庫的範式有不少種,可是咱們通常經常使用的只有第1、2、三範式和BC範式,這些範式直接在咱們的數據庫schema設計中獲得體現,雖然有時咱們根本就沒有意識到。數據庫
在關係型數據結構的實體-關係模型中,是容許實體集和關係集的屬性具備某種程度的子結構的,好比多值屬性和組合屬性;而第一範式則限制了這種存在,他要求全部的字段都是不可再分的、原子的,不然就違反了第一範式;第一範式主要是爲了不數據表結構過於複雜多樣,使得上層操做的可抽象性和數據一致性遭到破壞。編程
第二範式簡單的說,則是要求數據庫中的每條記錄都要有其對應的主鍵id存在,這樣要求的主要目的是爲了可以知足上層業務要求惟一標識每條記錄的需求;其實數據庫管理系統自己也有這種需求,部分數據庫的索引結構就是基於此的,只不過這不是數據範式(data normal form)應該關心的東西;安全
第三範式要求在在一個實體集中,不能存在一個非主屬性能夠做爲該實體集中某個子集的候選主鍵,還能夠表述爲,不一樣的關係集中不能存在除了主鍵字段外的其餘相同字段;這二者是等價的。服務器
簡單的說,第三範式主要是要求將實體集儘可能拆分,將不一樣的業務單元屬性字段拆分到不一樣的表裏,而後經過關係表進行關聯。網絡
第三範式必定程度上減小了數據的冗餘,下降了數據不一致的風險;一般狀況下,大部分的schema都應達到第三範式的要求。數據結構
BC範式是在第三範式的一個子集,它在第三範式之上作了更強的約束,即實體集中的任何子集都只能依賴於主鍵(注意,不是主屬性,這一點是BC範式和第三方範式的差異所在),不能存在一個非主屬性或非主屬性集能夠做爲某個子集的主鍵。
BC範式在定義上和第三方是差很少,他最大程度的減小了數據冗餘,不過在實際應用中,兩者基本是同樣的,只有在表的主鍵包含多個字段時,纔會產生差別。
這裏介紹一個很經典的例子。咱們所常見的大部分網站都會有會員系統,用戶須要註冊會員帳號,而後登陸才能夠享受進一步的功能或服務。對於這張會員表,咱們不妨命名爲user_info表,通常會包括會員id,會員暱稱,真實姓名,登陸密碼,性別,教育經歷、工做經歷、電話、電子郵箱、我的簡介、興趣愛好等信息,其餘可能還包括跟該網站會員業務相關的一些字段(好比積分、經驗值、充值帳戶餘額等),通常的schema設計是這樣的:
表名 |
user_info |
數據量 |
100w |
|
功能描述 |
主要存儲用戶的基本信息,如id、姓名、年齡、聯繫方式等; |
|||
字段名(新增) |
數據類型(精度範圍) |
空/非空 |
缺省值 |
字段含義 |
id |
BIGINT(20) |
N |
用戶id |
|
name |
VARCHAR(32) |
N |
姓名 |
|
gender |
TINYINT(4) |
N |
性別 |
|
age |
INT(8) |
N |
年齡 |
|
tel |
VARCHAR(16) |
Y |
聯繫電話 |
|
|
VARCHAR(64) |
Y |
電子郵箱 |
|
school |
VARCHAR(32) |
Y |
就讀學校 |
|
company |
VARCHAR(32) |
Y |
供職機構 |
|
interest |
VARCHAR(512) |
Y |
興趣愛好 |
|
gmt_create |
DATETIME |
N |
記錄建立時間 |
|
gmt_modified |
DATETIME |
N |
記錄修改時間 |
這張表結構看起來是沒什麼問題的,並且在初期業務簡單,用戶數量少、訪問量低的狀況下也確實都ok的;
可是隨着網站訪問量不斷增大,天天登陸的用戶愈來愈多,user_info表的訪問量愈來愈大,瓶頸慢慢出現;
咱們通過進一步分析發現,在處理用戶登陸的過程當中,咱們須要的操做很簡單,就是根據用戶輸入的會員暱稱查詢相應記錄,進而判斷密碼是否正確;而會員登陸後,也僅會顯示用戶的暱稱或真實姓名。
整個過程先後所涉及到的字段,只有會員id、暱稱、密碼和真實姓名等三、4個,而user_info表中其餘絕大部分字段,諸如教育經歷、興趣愛好等,在這個過程當中是根本不須要的,但他們的體積卻比較大,每次用戶登陸都要讀取,白白浪費時間和帶寬。
那麼咱們爲何不把這部分字段單獨拿出來放到一張新的表裏呢?咱們不妨建一個login表,裏邊只有會員id、會員暱稱、登陸密碼以及會員真實姓名,每次用戶登陸,都只讀取這張login表,這樣不但數據庫讀取記錄的時間會縮短,也能夠將應用和數據庫之間網絡傳輸量降到最低,徹底符合咱們前一篇文章裏所提到的將結果集縮減到最小的原則。
表名 |
login |
數據量 |
100w |
|
功能描述 |
主要存儲用戶的登陸信息; |
|||
字段名(新增) |
數據類型(精度範圍) |
空/非空 |
缺省值 |
字段含義 |
id |
BIGINT(20) |
N |
用戶id |
|
name |
VARCHAR(32) |
N |
姓名 |
|
password |
VARCHAR(128) |
N |
登陸密碼的md5值 |
可是有人不由要問,這樣不是產生數據冗餘了嗎?是的,同一份數據在user_info表和login表中各存了一份,確實有可能存在不一致的狀況,應用程序中每次新增或修改記錄,均可能須要訪問兩張表,這無形中增長寫操做的成本;因此這種優化方式也不是絕對的,要根據具體的場景區別對待,好比在上述這種讀寫比例很是高的場景中,咱們這樣處理就是合適的;而在一個一樣讀寫比例比較高,可是對數據一致性要求也很是高的系統中,對數據進行冗餘存儲就須要花費額外的精力去處理可能存在的數據不一致狀況。
Join是咱們在數據庫操做時常常會使用的一個關鍵字,其做用就是將兩張表拼接起來,而後過濾出符合條件的記錄;可是在拼接的過程中,是採用的是笛卡爾積的方式,其原理圖以下:
在MySQL中,Join的實現方式爲Nested Loop Join ,主要以驅動表結果集做爲基礎數據進行循環,有點相似編程語言中的雙層for循環嵌套;這種方式實現最最簡單,性能也基本能夠接受;其餘數據庫還提供Hash Join、Sort Merge Join等Join方式,都是針對不一樣場景有性能提高,很難簡單的說誰好誰壞。
從笛卡爾積的基本原理上來看,無論採用何種實現算法,表間join都是很是耗時耗力的操做,尤爲是當其中一張表或兩張表的數據量都很是大時更是如此!
因此咱們在作schema設計的時候,應該儘可能避免join的出現,經過必定的字段合併和數據冗餘將這種需求降到最低。
在傳統應用中,數據一致性是很重要的一點,可是在不少互聯網應用對數據的一致性要求並非很高;好比說數據庫的外鍵約束、惟一性約束等等,當記錄增刪時,數據庫都會去檢查相關的條件是否知足,這些都是須要額外開銷的,有時候其開銷甚至會超過當前操做自己。
在數據庫層去掉這些約束並不意味着系統中就不須要,咱們其實能夠將這部分約束校驗提早到應用程序中;前面咱們提到過,應用程序的擴展相對來講是比較容易的,因此咱們未嘗不充分利用一下來爲數據庫減負呢?
咱們使用的關係型數據庫雖然提供了表、字段、索引、sql等種種特性,可是歸根到底,其最底層的數據存儲仍然是<k,v>格式,好比MySQL,其數據的存儲都是有由下層存儲引擎來負責的,雖然在邏輯結構上咱們看到的是一張張表和其中一個個分散的字段,可是實際上每條記錄在物理存儲上都是一個總體;所謂的表結構、字段這些只是上層來維護的元數據,對底層來講,其實沒有字段的概念,就是一條條的物理記錄;
好比說咱們前邊的user_info表,咱們若是執行相似下邊的一條sql:
select user_id,user_name from user_info where age =8;
雖然我只想返回user_id和user_name兩個字段,可是存儲引擎仍是要將全部知足的age=8的記錄取出來,而後在分別掃描每條記錄,取出咱們須要的兩個字段數據返回;
因此咱們能夠看到,所謂的關係數據庫只不過是在<k,v>系統的上層作了進一步的封裝,好比說權限驗證、sql解析、二級索引等等;
MySQL雖然是個開源數據庫,體積也遠小於DB二、Oracle這些商業數據庫,可是一個RDBMS必備的結構MySQL卻同樣也很多,正所謂「麻雀雖小,五臟俱全」。
下邊咱們就來看下MySQL到底有怎樣的層次結構;
MySQL從上到下,能夠分爲三層,第一部分爲客戶端,中間部分爲數據庫管理系統(DBMS),最底層爲存儲引擎層;MySQL使用獨立的存儲引擎,能夠方便切換,這一點和其餘數據庫有所不一樣;
前面給出了MySQL數據庫的層次結構,其餘數據庫結構也是相似的;那麼不妨從上到下好好想想,整個體系中哪些東西其實不是咱們必需的?
首先,Client端確定是須要的,咱們總要鏈接數據庫的,那麼來看第二層DBMS層,其中的鏈接管理是須要的,無論什麼樣的數據庫總要處理客戶端鏈接;若是一個數據庫在安全網絡環境中,而且只有咱們本身在用的話,那麼就不須要用戶權限驗證了,這一層能夠去掉;爲了減輕數據庫負擔,咱們也不使用sql,須要的邏輯能夠直接使用API在應用層實現,那麼這一層也能夠省掉;而對於訪問控制模塊,由於數據庫爲咱們本身所獨享,或者都是可信任的使用者,因此這一層也不那麼重要了,或者說是能夠進一步簡化的;
咱們再來往下看存儲引擎層。索引的話能夠很好的提升數據的查詢效率,這一點無論在什麼類型的存儲系統中都適用,那麼這個功能咱們須要保留,可是若是咱們只想要個<k,v>存儲的話,那就能夠只保留主鍵索引好了;事務管理呢,最多咱們不用好了,不麻煩數據庫了;鎖的話即便咱們本身使用也會有不少的併發訪問控制,那麼須要保留;
通過這樣的精簡以後,咱們的數據庫還剩如下這些東西;
前面咱們提到了去關聯化、去一致性約束以及數據冗餘等等;3.1中咱們討論了關係型數據庫的本質,其實就是在<k,v>存儲之上又封裝和增添了諸多的功能,而3.3中咱們又對關係型數據庫進行了裁剪,去掉了一些咱們沒必要需的;通過全部的這麼步驟,剩下的部分(圖1-1所示),基本上就是如今NoSQL存儲了;
因此,NoSQL不是什麼高級的東東,而是關係型數據庫作了退化,迴歸到了其基礎本源;而分佈式的特性,也並非NoSQL獨有的,關係型數據之因此難有分佈式的架構,本質就是由於其選擇了「向上生長」,上層的複雜特性制約了其「橫向」生長的能力;而NoSQL只不過是在<k,v>之上,選擇了另一個生長方向而已。
因爲去掉了上層的「高級」特性,NoSQL系統的性能有了比較大的提高,同時因爲橫向生長,其存儲能力也有了很大的加強;
因此當咱們的系統受制於關係型數據庫的性能時,不妨放棄schema模式,來嘗試一下「自由」的NoSQL數據庫。
Scale up主要是指加強數據庫的單機處理能力,好比說提升CPU、內存、硬盤、網卡等硬件配置;其實scaleup這個概念包括咱們後便將要提到的scale out,不光對於數據庫,對於大部分的軟件系統都適用的,只不過具體的實施方案會有所差別;
由於scale up主要是增長機器硬件配置,相對來講比較簡單,也不須要遷移數據,對於技術人員來講沒有新的東西,因此對於中小型系統來講很是合適;
scale out是指橫向的擴展,就是增長數據庫實例或節點來增長總體的處理能力,這裏邊還包括兩種方式,一種常見的數據複製,好比MySQL的Replication,Oracle rac等;另外一種橫向擴展的方式是進行數據切分,也就是說把本應該放在一臺機器上的數據切成幾部分,分別放在不一樣的節點上(並非相同數據的備份),這樣訪問的壓力就會分散到多個節點。
scale out的優勢是成本低,若是整個系統都使用PC Server的話,能夠用很低的成原本支撐海量的數據和高併發;並且通常來講,這種擴展是線性的,即有多少機器,就能支撐多少的數據和多大的訪問量,但一般這須要有個比較好的數據系統架構或中間件系統來支持;
以淘寶的交易庫來講,原來使用的都是IOE(I指的是IBM的小型機,O指Oracle數據庫,E即EMC的高端存儲),採用主備雙機方式,用Orcle和高端存儲來支撐天天的巨大訪問量,可是整個系統的成本也很是高(聽說一套下來要2000多萬),而通過去IOE之後,經過使用MySQL和廉價的PC Server(線上16主16備,雙11的時候擴展爲32主32備),經過數據切分和Replication機制,不只整個數據庫的在線處理能力提升了4倍,成本也降爲原來的1 / 8不到,同時數據的安全性和容災能力也獲得了保證;
可是數據庫技術不是本文關注的重點,下邊將主要介紹和咱們平常開發聯繫更爲緊密的數據切分知識。
對於這個問題,可能有人會以爲的是廢話:你前邊不是已經說過了嗎,幹嗎還問?沒錯,擴展系統,支持更大訪問和併發和替換商業數據庫,下降成本這兩個確實咱們進行數據切分的主要緣由;但這樣來講太籠統了
對於數據庫來說,無論是商業的仍是開源的,其單庫和單表的承載能力都是有限的;在一般的業務場景下(寫操做和讀操做比較均衡),普通的pc server上,MySQL數據庫單表數據記錄的承載能力在千萬級(數據量在TB級別)左右,TPS大概在千這個級別(具體測試環境和數據可參考另外一篇文檔);固然,咱們在這裏沒有必要苛求具體的數據,由於這和具體業務場景、實際讀寫比、服務器硬件配置、具體的數據引擎、MySQL的配置參數等相關,好比說,若是隻是將MySQL做爲日誌數據庫(基本只有寫操做,不須要建索引),單表的支撐能力可達到上億甚至是十億的級別;但這畢竟只是種極限場景,不能拿來用做通常場景的參考。
另外須要說明的是,前面說的單庫單表的承載能力有限,並非說當數據量超過這個上限時,數據庫就會立刻崩潰或者拒絕服務,而是在這種狀況下,數據庫的總體的讀寫性能就會急劇降低,甚至於一條很簡單的sql查詢也可能會超時,若是原本就是負載很重的一張表,那與崩潰無異了!
數據切分所要遵循的原則主要有兩點:
第一就是將數據均衡分散在多個處理節點上,其實這裏主要強調的是均衡,但這個均衡並不可簡單的當作是每一個節點上庫(表)數量相等或是記錄條數同樣;而更可能是要從數據訪問和處理能力的均衡上去考慮,主要原則有如下幾條。
a、 不一樣節點業務關聯度要低
b、 同一節點業務類型儘可能一致
c、 數據(訪問量)要均衡
d、 數據的一致性和安全性
垂直切分主要是根據表中數據的業務類型,將不一樣業務的數據放在不一樣的表或者數據庫中;
在系統結構設計中咱們會常常提到一個原則,叫作高內聚、低耦合,其實這個原則在數據庫的垂直切分中一樣適用;因此在作水平切分的時候要儘可能作到不一樣功能的表關聯儘量少,這不但能夠減小SQL語句中的join出現的概率,同時後續表結構變動時也更容易;
對於中小型系統來說,不少人喜歡各類複雜功能一個sql搞定,甚至有些還使用存儲過程,這樣對前端程序來說確實方便了許多;可是這種方便也每每會給咱們帶來傷害,尤爲是當數據量和訪問量都增加比較快的狀況下,你會發現幾條慢查詢可能讓你的整個系統直接失去響應!
垂直切分主要是按照系統功能來切分的,因此一樣是有瓶頸存在的,好比說,某一項功能比其餘的要複雜許多,或者數據量要大不少,很難再進一步拆分,這樣就不適合垂直切分了;這在實際的業務場景中是存在的;
好比說淘寶的訂單系統,雖然功能看起來簡單,可是因爲交易類型複雜,中間狀態繁多,耦合性很是強,加之訂單數量巨大,其系統是很難再進行功能上的剝離的;可是這個系統屬於底層基礎系統,爲了支撐巨大的訪問量,只能採起水平數據切分方式來解決高併發問題。
水平切分就是咱們一般所說的分庫分表,主要是將一張表中的數據按照某個字段(好比說用戶id、商品id、訂單id等)分散存儲在多張結構相同的表中,這樣訪問的壓力就會分散到多張表上;
在平時的開發中,數據的水平切分比垂直切分應用的更多,由於通常來說,須要進行垂直數據切分時,一般系統的規模和負載都已經很大了(尤爲是使用oracle的時候),這時候咱們最早實施的,每每是經過RPC或者服務化將應用分紅多個系統,底層數據表之間的依賴,很天然的會轉化成系統間的接口依賴,因此這個時候,數據庫固然也會跟着分開了,不須要太刻意其考慮垂直切分這個概念了。
成本固定;只要在系統設計之初就指定好分表數量和分表字段,無論是要分紅8個庫1024張表,仍是16個庫4096張表,其成本都是同樣的;
解決了單表瓶頸問題;水平切分方式很好的解決了垂直切分時可能存在的單表瓶頸問題,只要在開始時作好容量預估,設定適當的切分數量,基本能夠知足業務很長一段時期內的存儲需求;
對事務透明;分表對於數據庫來講是透明的,因此原來的事務該怎麼作還怎麼作。
水平切分的雖然頗有效,可是其缺點也很多,主要以下:
sql路由變得複雜;每次在作了切分的庫或表上執行sql時,都必需要明確指出目標分庫或分表;這無形中增長業務方的成本。
分表字段單一;水平切分只能使用單一的分表字段;若是業務中有需求按照非分表字段進行查詢,則會變得很困難,只能掃描全部的表;一個解決方案就是,按照不一樣查詢字段作多份分表;可是又要花費精力去解決數據冗餘問題。
join操做變得困難;顯而易見,之前單表間的join放到多表上是沒法執行的,這時候咱們最好仍是選擇放棄;
二次擴展比較麻煩;若是分表以後,咱們數據增加太快,又達到存儲瓶頸了,就面臨着二次拆分的命運;但由於路由規則發生了改變,遷移數據的麻煩是避免不了的;因此要有必要的手段去保證遷移數據時系統依然可以對外提供服務。
在作水平切分後,咱們的部分業務實現方式或是開發方式可能須要隨着改變;如下是咱們再作水平切分時須要注意的點,主要是針對水平切分的弱點而言的:
根據業務場景肯定切分字段;業務中根據什麼字段去查詢,就用什麼字段去分表;
避免熱點數據問題;一般切分時採用的hash算法理論上能夠保證數據的分散性,但在實際應用中,仍可能遇到數據熱點問題;理論是理論,實際歸實際,沒有絕對的,不要覺得分了表就萬事大吉了。
分表宜多不宜少;這樣作主要是爲了儘可能避免後期可能遇到的二次拆分,由於前面咱們說過,拆成1024張表和拆成4096張表的操做成本是同樣的。
避免分表上的join操做;在分表的缺點中咱們就提到過,join在水平切分場景下會很困難,因此在業務實現中,對這種狀況能避免就避免,哪怕犧牲一些簡潔性,多繞幾步。
避免非分表字段查詢;道理也是同樣的,切分後只能按照切分字段進行查詢;若是非要按其餘字段查詢,那就冗餘數據吧。
上邊咱們提到的是咱們最多見的切分方式,其餘還有一些切分方式不太「規矩」,它們具備部分水平切分或垂直切分的特色,但又很難直接納入到某一分類中;正如那句老話所言:山無定勢,水無定形。
邏輯切分相似於垂直切分,也是將數據按照不一樣業務邏輯拆分到不一樣的表中;但這種方式追求的不是單純的負載均衡,而是不一樣業務的數據隔離;好比說某部分數據讀多些少,某部分數據則可能讀少寫多;這樣進行隔離後,能夠充分的利用不一樣的業務特色進行優化,好比說建立不一樣的索引結構,使用不一樣的查詢方案等等;前面咱們提到的數據冗餘其實就屬於這類切分方式。
另外一個典型的案例就是數據傾斜;好比說對於淘寶上的賣家來講,有些賣家比較小,可能他的店鋪中的寶貝只有幾十數百個,可是有些品牌賣家或熱門店鋪,他們的寶貝可能有上萬甚至是數十萬,再加上未上架的或是已下架的歷史寶貝,數量會更多!這兩類用戶的處理,若是使用相同的邏輯,極可能會產生問題;可是若是咱們將這部分大賣家提出來放在另外的節點上來處理,效果可能會好不少。
時間的切分主要是將記錄按照建立時間的前後順序,放在不一樣的表中;mysql的分區表便提供了這樣一種切分的機制;分區表對外邏輯上表現爲一張表,可是實際物理存儲上是多張表,不一樣的表對應不一樣的時間區段;用戶能夠設定建立新分區表的週期,好比說一天或者是一週,在某個時間點插入的記錄便會寫入對應的分區表;讀取時,會從最近一個分區開始掃描,直到找到目標記錄。
冷熱切分主要是按照數據的訪問頻率對數據進行隔離,有點相似於前邊咱們提到的邏輯切分。
淘寶的交易歷史庫就是典型的表明。淘寶的訂單總量巨大,天天產生的訂單數量也很是多;若是咱們爲用戶提供訂單查詢服務,那麼巨大的訂單量會使咱們的服務性能降低不少;可是實際中咱們會發現,用戶不多會去查詢本身三個月之前的訂單,那咱們不妨將用戶三個月之前的訂單拿出來單獨提供存儲和查詢服務,這樣就可使用戶訪問頻繁的訂單表數據量變得很小,從而能夠提供更高的處理性能。
按體積切分主要是按照數據表的尺寸或是記錄條數進行切分,這種切分通常適用於業務類型單一,對錶的體積能夠很好預估的狀況,主要是爲了不表的尺寸過大而是性能降低。
通常來講,只有日誌類型的表適用於這種切分方式,一般是以自增id做爲判斷標識,由於基本不存在刪除和修改操做,因此能夠很好的控制體積;不過這種狀況下大部分系統更傾向於使用按時間切分的方式,因此按體積切分的實際應用不多。
當咱們進行了分庫分表以後,一個咱們不得不面對的問題就是sql的路由。當咱們將數據分散存儲在諸如名爲test_0000、test_0001的分表中時,咱們會發現,必需要對原來的程序代碼或數據庫進行相應的改造,不然程序將找不到正確的庫或表;這種狀況下,一般的解決方案有三種:
這種方案,只需將程序裏涉及到數據庫讀寫的代碼按照分表邏輯進行改造便可,技術上比較簡單,不須要額外的軟件或者是技術的支持;缺點是對業務代碼侵入性強,可能涉及到多個地方的修改,工做量較大,並且後期的修改和維護成本也比較高。
這種方法對程序透明,是的上層業務邏輯不須要考慮分庫分表的讀寫規則,應用代碼能夠保持不變;
缺點是須要修改數據庫系統,或者以模塊、插件等形式對數據庫進行加強,開發成本和後期維護成本都很高,部分商業數據庫根本沒法自行修改。
這個應該是目前採用最多的方式;其優勢是對上層業務邏輯和底層數據庫都透明,只須要對應用的訪問層進行簡單改造,便可快速切換到拆分以後的數據庫,無論是開發人員仍是數據庫管理人員都不須要增長多少工做成本;
其缺點是技術門檻較高,須要專門的人員來開發和維護;功能受限,部分在單庫單表下的常見操做在這種中間層代理的方式下會變得麻煩,好比說跨庫、跨表join,全局數據分組與排序等。
對於以上幾種數據路由方案,不一樣的場景可能會有不一樣的選擇,具體要看自身的業務需求,技術儲備以及實施成本等。
以上通篇幾乎都在介紹如何對數據庫進行水平擴展和數據切分,其實有時候咱們沒必要搞得如此複雜,若是僅經過簡單的硬件升級就能知足業務增加對數據庫的要求,咱們何樂而不爲呢?
咱們都知道,普通的服務器,甚至是不少高端存儲,都是基於機械硬盤存放數據的;而機械硬盤最大的缺點就是速度上存在物理上限;
通常的機械硬盤讀寫數據都要通過尋道操做,主要由兩個步驟組成:一是移動磁頭,二是轉動盤片;這二者都屬於機械操做,前者通常在2ms~4ms,然後者,對於如今經常使用的SATA盤或SAS盤來講,通常會在1ms之內,因此,一次尋道操做要耗費大約5ms左右的時間,而對於一些老式硬盤來講,這個時間可能要達到10ms甚至更多!也就是說,單塊磁盤隨機讀寫的iops只有100左右,這簡直低的不可忍受,再牛B的系統也被拖死了!
對於一個初級團隊,再沒有不少技術積累的狀況下,盲目的進行水平擴容每每會給本身帶來更高的維護成本和更差的系統穩定性,因此這個時候咱們不妨嘗試一下數據庫性能提高的又一利器——閃存存儲;畢竟咱們不斷對系統進行優化的目標就是爲了得到更高的性能!
對於數據庫而言,在平常開發中咱們主要的關注點有兩塊,一個是schema的結構設計,另外一個就是索引的優化,這兩塊是影響咱們最終系統結構和性能的關鍵部分,天然也是咱們花費精力最多的部分;
本文主要介紹數據庫設計中的通常原則和優化手段,包括數據庫的一半範式、反範式設計、數據切分、數據路由與合併等等
範式理論是關係型數據庫設計的黃金法則,它提供了數據結構化的理論基礎,有效地保證了數據的一致性,應該說,關係型數據庫就是在範式的基礎上才成長起來的。
數據庫的範式有不少種,可是咱們通常經常使用的只有第1、2、三範式和BC範式,這些範式直接在咱們的數據庫schema設計中獲得體現,雖然有時咱們根本就沒有意識到。
在關係型數據結構的實體-關係模型中,是容許實體集和關係集的屬性具備某種程度的子結構的,好比多值屬性和組合屬性;而第一範式則限制了這種存在,他要求全部的字段都是不可再分的、原子的,不然就違反了第一範式;第一範式主要是爲了不數據表結構過於複雜多樣,使得上層操做的可抽象性和數據一致性遭到破壞。
第二範式簡單的說,則是要求數據庫中的每條記錄都要有其對應的主鍵id存在,這樣要求的主要目的是爲了可以知足上層業務要求惟一標識每條記錄的需求;其實數據庫管理系統自己也有這種需求,部分數據庫的索引結構就是基於此的,只不過這不是數據範式(data normal form)應該關心的東西;
第三範式要求在在一個實體集中,不能存在一個非主屬性能夠做爲該實體集中某個子集的候選主鍵,還能夠表述爲,不一樣的關係集中不能存在除了主鍵字段外的其餘相同字段;這二者是等價的。
簡單的說,第三範式主要是要求將實體集儘可能拆分,將不一樣的業務單元屬性字段拆分到不一樣的表裏,而後經過關係表進行關聯。
第三範式必定程度上減小了數據的冗餘,下降了數據不一致的風險;一般狀況下,大部分的schema都應達到第三範式的要求。
BC範式是在第三範式的一個子集,它在第三範式之上作了更強的約束,即實體集中的任何子集都只能依賴於主鍵(注意,不是主屬性,這一點是BC範式和第三方範式的差異所在),不能存在一個非主屬性或非主屬性集能夠做爲某個子集的主鍵。
BC範式在定義上和第三方是差很少,他最大程度的減小了數據冗餘,不過在實際應用中,兩者基本是同樣的,只有在表的主鍵包含多個字段時,纔會產生差別。
這裏介紹一個很經典的例子。咱們所常見的大部分網站都會有會員系統,用戶須要註冊會員帳號,而後登陸才能夠享受進一步的功能或服務。對於這張會員表,咱們不妨命名爲user_info表,通常會包括會員id,會員暱稱,真實姓名,登陸密碼,性別,教育經歷、工做經歷、電話、電子郵箱、我的簡介、興趣愛好等信息,其餘可能還包括跟該網站會員業務相關的一些字段(好比積分、經驗值、充值帳戶餘額等),通常的schema設計是這樣的:
表名 |
user_info |
數據量 |
100w |
|
功能描述 |
主要存儲用戶的基本信息,如id、姓名、年齡、聯繫方式等; |
|||
字段名(新增) |
數據類型(精度範圍) |
空/非空 |
缺省值 |
字段含義 |
id |
BIGINT(20) |
N |
用戶id |
|
name |
VARCHAR(32) |
N |
姓名 |
|
gender |
TINYINT(4) |
N |
性別 |
|
age |
INT(8) |
N |
年齡 |
|
tel |
VARCHAR(16) |
Y |
聯繫電話 |
|
|
VARCHAR(64) |
Y |
電子郵箱 |
|
school |
VARCHAR(32) |
Y |
就讀學校 |
|
company |
VARCHAR(32) |
Y |
供職機構 |
|
interest |
VARCHAR(512) |
Y |
興趣愛好 |
|
gmt_create |
DATETIME |
N |
記錄建立時間 |
|
gmt_modified |
DATETIME |
N |
記錄修改時間 |
這張表結構看起來是沒什麼問題的,並且在初期業務簡單,用戶數量少、訪問量低的狀況下也確實都ok的;
可是隨着網站訪問量不斷增大,天天登陸的用戶愈來愈多,user_info表的訪問量愈來愈大,瓶頸慢慢出現;
咱們通過進一步分析發現,在處理用戶登陸的過程當中,咱們須要的操做很簡單,就是根據用戶輸入的會員暱稱查詢相應記錄,進而判斷密碼是否正確;而會員登陸後,也僅會顯示用戶的暱稱或真實姓名。
整個過程先後所涉及到的字段,只有會員id、暱稱、密碼和真實姓名等3、4個,而user_info表中其餘絕大部分字段,諸如教育經歷、興趣愛好等,在這個過程當中是根本不須要的,但他們的體積卻比較大,每次用戶登陸都要讀取,白白浪費時間和帶寬。
那麼咱們爲何不把這部分字段單獨拿出來放到一張新的表裏呢?咱們不妨建一個login表,裏邊只有會員id、會員暱稱、登陸密碼以及會員真實姓名,每次用戶登陸,都只讀取這張login表,這樣不但數據庫讀取記錄的時間會縮短,也能夠將應用和數據庫之間網絡傳輸量降到最低,徹底符合咱們前一篇文章裏所提到的將結果集縮減到最小的原則。
表名 |
login |
數據量 |
100w |
|
功能描述 |
主要存儲用戶的登陸信息; |
|||
字段名(新增) |
數據類型(精度範圍) |
空/非空 |
缺省值 |
字段含義 |
id |
BIGINT(20) |
N |
用戶id |
|
name |
VARCHAR(32) |
N |
姓名 |
|
password |
VARCHAR(128) |
N |
登陸密碼的md5值 |
可是有人不由要問,這樣不是產生數據冗餘了嗎?是的,同一份數據在user_info表和login表中各存了一份,確實有可能存在不一致的狀況,應用程序中每次新增或修改記錄,均可能須要訪問兩張表,這無形中增長寫操做的成本;因此這種優化方式也不是絕對的,要根據具體的場景區別對待,好比在上述這種讀寫比例很是高的場景中,咱們這樣處理就是合適的;而在一個一樣讀寫比例比較高,可是對數據一致性要求也很是高的系統中,對數據進行冗餘存儲就須要花費額外的精力去處理可能存在的數據不一致狀況。
Join是咱們在數據庫操做時常常會使用的一個關鍵字,其做用就是將兩張表拼接起來,而後過濾出符合條件的記錄;可是在拼接的過程中,是採用的是笛卡爾積的方式,其原理圖以下:
在MySQL中,Join的實現方式爲Nested Loop Join ,主要以驅動表結果集做爲基礎數據進行循環,有點相似編程語言中的雙層for循環嵌套;這種方式實現最最簡單,性能也基本能夠接受;其餘數據庫還提供Hash Join、Sort Merge Join等Join方式,都是針對不一樣場景有性能提高,很難簡單的說誰好誰壞。
從笛卡爾積的基本原理上來看,無論採用何種實現算法,表間join都是很是耗時耗力的操做,尤爲是當其中一張表或兩張表的數據量都很是大時更是如此!
因此咱們在作schema設計的時候,應該儘可能避免join的出現,經過必定的字段合併和數據冗餘將這種需求降到最低。
在傳統應用中,數據一致性是很重要的一點,可是在不少互聯網應用對數據的一致性要求並非很高;好比說數據庫的外鍵約束、惟一性約束等等,當記錄增刪時,數據庫都會去檢查相關的條件是否知足,這些都是須要額外開銷的,有時候其開銷甚至會超過當前操做自己。
在數據庫層去掉這些約束並不意味着系統中就不須要,咱們其實能夠將這部分約束校驗提早到應用程序中;前面咱們提到過,應用程序的擴展相對來講是比較容易的,因此咱們未嘗不充分利用一下來爲數據庫減負呢?
咱們使用的關係型數據庫雖然提供了表、字段、索引、sql等種種特性,可是歸根到底,其最底層的數據存儲仍然是<k,v>格式,好比MySQL,其數據的存儲都是有由下層存儲引擎來負責的,雖然在邏輯結構上咱們看到的是一張張表和其中一個個分散的字段,可是實際上每條記錄在物理存儲上都是一個總體;所謂的表結構、字段這些只是上層來維護的元數據,對底層來講,其實沒有字段的概念,就是一條條的物理記錄;
好比說咱們前邊的user_info表,咱們若是執行相似下邊的一條sql:
select user_id,user_name from user_info where age =8;
雖然我只想返回user_id和user_name兩個字段,可是存儲引擎仍是要將全部知足的age=8的記錄取出來,而後在分別掃描每條記錄,取出咱們須要的兩個字段數據返回;
因此咱們能夠看到,所謂的關係數據庫只不過是在<k,v>系統的上層作了進一步的封裝,好比說權限驗證、sql解析、二級索引等等;
MySQL雖然是個開源數據庫,體積也遠小於DB2、Oracle這些商業數據庫,可是一個RDBMS必備的結構MySQL卻同樣也很多,正所謂「麻雀雖小,五臟俱全」。
下邊咱們就來看下MySQL到底有怎樣的層次結構;
MySQL從上到下,能夠分爲三層,第一部分爲客戶端,中間部分爲數據庫管理系統(DBMS),最底層爲存儲引擎層;MySQL使用獨立的存儲引擎,能夠方便切換,這一點和其餘數據庫有所不一樣;
前面給出了MySQL數據庫的層次結構,其餘數據庫結構也是相似的;那麼不妨從上到下好好想想,整個體系中哪些東西其實不是咱們必需的?
首先,Client端確定是須要的,咱們總要鏈接數據庫的,那麼來看第二層DBMS層,其中的鏈接管理是須要的,無論什麼樣的數據庫總要處理客戶端鏈接;若是一個數據庫在安全網絡環境中,而且只有咱們本身在用的話,那麼就不須要用戶權限驗證了,這一層能夠去掉;爲了減輕數據庫負擔,咱們也不使用sql,須要的邏輯能夠直接使用API在應用層實現,那麼這一層也能夠省掉;而對於訪問控制模塊,由於數據庫爲咱們本身所獨享,或者都是可信任的使用者,因此這一層也不那麼重要了,或者說是能夠進一步簡化的;
咱們再來往下看存儲引擎層。索引的話能夠很好的提升數據的查詢效率,這一點無論在什麼類型的存儲系統中都適用,那麼這個功能咱們須要保留,可是若是咱們只想要個<k,v>存儲的話,那就能夠只保留主鍵索引好了;事務管理呢,最多咱們不用好了,不麻煩數據庫了;鎖的話即便咱們本身使用也會有不少的併發訪問控制,那麼須要保留;
通過這樣的精簡以後,咱們的數據庫還剩如下這些東西;
前面咱們提到了去關聯化、去一致性約束以及數據冗餘等等;3.1中咱們討論了關係型數據庫的本質,其實就是在<k,v>存儲之上又封裝和增添了諸多的功能,而3.3中咱們又對關係型數據庫進行了裁剪,去掉了一些咱們沒必要需的;通過全部的這麼步驟,剩下的部分(圖1-1所示),基本上就是如今NoSQL存儲了;
因此,NoSQL不是什麼高級的東東,而是關係型數據庫作了退化,迴歸到了其基礎本源;而分佈式的特性,也並非NoSQL獨有的,關係型數據之因此難有分佈式的架構,本質就是由於其選擇了「向上生長」,上層的複雜特性制約了其「橫向」生長的能力;而NoSQL只不過是在<k,v>之上,選擇了另一個生長方向而已。
因爲去掉了上層的「高級」特性,NoSQL系統的性能有了比較大的提高,同時因爲橫向生長,其存儲能力也有了很大的加強;
因此當咱們的系統受制於關係型數據庫的性能時,不妨放棄schema模式,來嘗試一下「自由」的NoSQL數據庫。
Scale up主要是指加強數據庫的單機處理能力,好比說提升CPU、內存、硬盤、網卡等硬件配置;其實scaleup這個概念包括咱們後便將要提到的scale out,不光對於數據庫,對於大部分的軟件系統都適用的,只不過具體的實施方案會有所差別;
由於scale up主要是增長機器硬件配置,相對來講比較簡單,也不須要遷移數據,對於技術人員來講沒有新的東西,因此對於中小型系統來講很是合適;
scale out是指橫向的擴展,就是增長數據庫實例或節點來增長總體的處理能力,這裏邊還包括兩種方式,一種常見的數據複製,好比MySQL的Replication,Oracle rac等;另外一種橫向擴展的方式是進行數據切分,也就是說把本應該放在一臺機器上的數據切成幾部分,分別放在不一樣的節點上(並非相同數據的備份),這樣訪問的壓力就會分散到多個節點。
scale out的優勢是成本低,若是整個系統都使用PC Server的話,能夠用很低的成原本支撐海量的數據和高併發;並且通常來講,這種擴展是線性的,即有多少機器,就能支撐多少的數據和多大的訪問量,但一般這須要有個比較好的數據系統架構或中間件系統來支持;
以淘寶的交易庫來講,原來使用的都是IOE(I指的是IBM的小型機,O指Oracle數據庫,E即EMC的高端存儲),採用主備雙機方式,用Orcle和高端存儲來支撐天天的巨大訪問量,可是整個系統的成本也很是高(聽說一套下來要2000多萬),而通過去IOE之後,經過使用MySQL和廉價的PC Server(線上16主16備,雙11的時候擴展爲32主32備),經過數據切分和Replication機制,不只整個數據庫的在線處理能力提升了4倍,成本也降爲原來的1 / 8不到,同時數據的安全性和容災能力也獲得了保證;
可是數據庫技術不是本文關注的重點,下邊將主要介紹和咱們平常開發聯繫更爲緊密的數據切分知識。
對於這個問題,可能有人會以爲的是廢話:你前邊不是已經說過了嗎,幹嗎還問?沒錯,擴展系統,支持更大訪問和併發和替換商業數據庫,下降成本這兩個確實咱們進行數據切分的主要緣由;但這樣來講太籠統了
對於數據庫來說,無論是商業的仍是開源的,其單庫和單表的承載能力都是有限的;在一般的業務場景下(寫操做和讀操做比較均衡),普通的pc server上,MySQL數據庫單表數據記錄的承載能力在千萬級(數據量在TB級別)左右,TPS大概在千這個級別(具體測試環境和數據可參考另外一篇文檔);固然,咱們在這裏沒有必要苛求具體的數據,由於這和具體業務場景、實際讀寫比、服務器硬件配置、具體的數據引擎、MySQL的配置參數等相關,好比說,若是隻是將MySQL做爲日誌數據庫(基本只有寫操做,不須要建索引),單表的支撐能力可達到上億甚至是十億的級別;但這畢竟只是種極限場景,不能拿來用做通常場景的參考。
另外須要說明的是,前面說的單庫單表的承載能力有限,並非說當數據量超過這個上限時,數據庫就會立刻崩潰或者拒絕服務,而是在這種狀況下,數據庫的總體的讀寫性能就會急劇降低,甚至於一條很簡單的sql查詢也可能會超時,若是原本就是負載很重的一張表,那與崩潰無異了!
數據切分所要遵循的原則主要有兩點:
第一就是將數據均衡分散在多個處理節點上,其實這裏主要強調的是均衡,但這個均衡並不可簡單的當作是每一個節點上庫(表)數量相等或是記錄條數同樣;而更可能是要從數據訪問和處理能力的均衡上去考慮,主要原則有如下幾條。
a、不一樣節點業務關聯度要低
b、同一節點業務類型儘可能一致
c、數據(訪問量)要均衡
d、數據的一致性和安全性
垂直切分主要是根據表中數據的業務類型,將不一樣業務的數據放在不一樣的表或者數據庫中;
在系統結構設計中咱們會常常提到一個原則,叫作高內聚、低耦合,其實這個原則在數據庫的垂直切分中一樣適用;因此在作水平切分的時候要儘可能作到不一樣功能的表關聯儘量少,這不但能夠減小SQL語句中的join出現的概率,同時後續表結構變動時也更容易;
對於中小型系統來說,不少人喜歡各類複雜功能一個sql搞定,甚至有些還使用存儲過程,這樣對前端程序來說確實方便了許多;可是這種方便也每每會給咱們帶來傷害,尤爲是當數據量和訪問量都增加比較快的狀況下,你會發現幾條慢查詢可能讓你的整個系統直接失去響應!
垂直切分主要是按照系統功能來切分的,因此一樣是有瓶頸存在的,好比說,某一項功能比其餘的要複雜許多,或者數據量要大不少,很難再進一步拆分,這樣就不適合垂直切分了;這在實際的業務場景中是存在的;
好比說淘寶的訂單系統,雖然功能看起來簡單,可是因爲交易類型複雜,中間狀態繁多,耦合性很是強,加之訂單數量巨大,其系統是很難再進行功能上的剝離的;可是這個系統屬於底層基礎系統,爲了支撐巨大的訪問量,只能採起水平數據切分方式來解決高併發問題。
水平切分就是咱們一般所說的分庫分表,主要是將一張表中的數據按照某個字段(好比說用戶id、商品id、訂單id等)分散存儲在多張結構相同的表中,這樣訪問的壓力就會分散到多張表上;
在平時的開發中,數據的水平切分比垂直切分應用的更多,由於通常來說,須要進行垂直數據切分時,一般系統的規模和負載都已經很大了(尤爲是使用oracle的時候),這時候咱們最早實施的,每每是經過RPC或者服務化將應用分紅多個系統,底層數據表之間的依賴,很天然的會轉化成系統間的接口依賴,因此這個時候,數據庫固然也會跟着分開了,不須要太刻意其考慮垂直切分這個概念了。
成本固定;只要在系統設計之初就指定好分表數量和分表字段,無論是要分紅8個庫1024張表,仍是16個庫4096張表,其成本都是同樣的;
解決了單表瓶頸問題;水平切分方式很好的解決了垂直切分時可能存在的單表瓶頸問題,只要在開始時作好容量預估,設定適當的切分數量,基本能夠知足業務很長一段時期內的存儲需求;
對事務透明;分表對於數據庫來講是透明的,因此原來的事務該怎麼作還怎麼作。
水平切分的雖然頗有效,可是其缺點也很多,主要以下:
sql路由變得複雜;每次在作了切分的庫或表上執行sql時,都必需要明確指出目標分庫或分表;這無形中增長業務方的成本。
分表字段單一;水平切分只能使用單一的分表字段;若是業務中有需求按照非分表字段進行查詢,則會變得很困難,只能掃描全部的表;一個解決方案就是,按照不一樣查詢字段作多份分表;可是又要花費精力去解決數據冗餘問題。
join操做變得困難;顯而易見,之前單表間的join放到多表上是沒法執行的,這時候咱們最好仍是選擇放棄;
二次擴展比較麻煩;若是分表以後,咱們數據增加太快,又達到存儲瓶頸了,就面臨着二次拆分的命運;但由於路由規則發生了改變,遷移數據的麻煩是避免不了的;因此要有必要的手段去保證遷移數據時系統依然可以對外提供服務。
在作水平切分後,咱們的部分業務實現方式或是開發方式可能須要隨着改變;如下是咱們再作水平切分時須要注意的點,主要是針對水平切分的弱點而言的:
根據業務場景肯定切分字段;業務中根據什麼字段去查詢,就用什麼字段去分表;
避免熱點數據問題;一般切分時採用的hash算法理論上能夠保證數據的分散性,但在實際應用中,仍可能遇到數據熱點問題;理論是理論,實際歸實際,沒有絕對的,不要覺得分了表就萬事大吉了。
分表宜多不宜少;這樣作主要是爲了儘可能避免後期可能遇到的二次拆分,由於前面咱們說過,拆成1024張表和拆成4096張表的操做成本是同樣的。
避免分表上的join操做;在分表的缺點中咱們就提到過,join在水平切分場景下會很困難,因此在業務實現中,對這種狀況能避免就避免,哪怕犧牲一些簡潔性,多繞幾步。
避免非分表字段查詢;道理也是同樣的,切分後只能按照切分字段進行查詢;若是非要按其餘字段查詢,那就冗餘數據吧。
上邊咱們提到的是咱們最多見的切分方式,其餘還有一些切分方式不太「規矩」,它們具備部分水平切分或垂直切分的特色,但又很難直接納入到某一分類中;正如那句老話所言:山無定勢,水無定形。
邏輯切分相似於垂直切分,也是將數據按照不一樣業務邏輯拆分到不一樣的表中;但這種方式追求的不是單純的負載均衡,而是不一樣業務的數據隔離;好比說某部分數據讀多些少,某部分數據則可能讀少寫多;這樣進行隔離後,能夠充分的利用不一樣的業務特色進行優化,好比說建立不一樣的索引結構,使用不一樣的查詢方案等等;前面咱們提到的數據冗餘其實就屬於這類切分方式。
另外一個典型的案例就是數據傾斜;好比說對於淘寶上的賣家來講,有些賣家比較小,可能他的店鋪中的寶貝只有幾十數百個,可是有些品牌賣家或熱門店鋪,他們的寶貝可能有上萬甚至是數十萬,再加上未上架的或是已下架的歷史寶貝,數量會更多!這兩類用戶的處理,若是使用相同的邏輯,極可能會產生問題;可是若是咱們將這部分大賣家提出來放在另外的節點上來處理,效果可能會好不少。
時間的切分主要是將記錄按照建立時間的前後順序,放在不一樣的表中;mysql的分區表便提供了這樣一種切分的機制;分區表對外邏輯上表現爲一張表,可是實際物理存儲上是多張表,不一樣的表對應不一樣的時間區段;用戶能夠設定建立新分區表的週期,好比說一天或者是一週,在某個時間點插入的記錄便會寫入對應的分區表;讀取時,會從最近一個分區開始掃描,直到找到目標記錄。
冷熱切分主要是按照數據的訪問頻率對數據進行隔離,有點相似於前邊咱們提到的邏輯切分。
淘寶的交易歷史庫就是典型的表明。淘寶的訂單總量巨大,天天產生的訂單數量也很是多;若是咱們爲用戶提供訂單查詢服務,那麼巨大的訂單量會使咱們的服務性能降低不少;可是實際中咱們會發現,用戶不多會去查詢本身三個月之前的訂單,那咱們不妨將用戶三個月之前的訂單拿出來單獨提供存儲和查詢服務,這樣就可使用戶訪問頻繁的訂單表數據量變得很小,從而能夠提供更高的處理性能。
按體積切分主要是按照數據表的尺寸或是記錄條數進行切分,這種切分通常適用於業務類型單一,對錶的體積能夠很好預估的狀況,主要是爲了不表的尺寸過大而是性能降低。
通常來講,只有日誌類型的表適用於這種切分方式,一般是以自增id做爲判斷標識,由於基本不存在刪除和修改操做,因此能夠很好的控制體積;不過這種狀況下大部分系統更傾向於使用按時間切分的方式,因此按體積切分的實際應用不多。
當咱們進行了分庫分表以後,一個咱們不得不面對的問題就是sql的路由。當咱們將數據分散存儲在諸如名爲test_0000、test_0001的分表中時,咱們會發現,必需要對原來的程序代碼或數據庫進行相應的改造,不然程序將找不到正確的庫或表;這種狀況下,一般的解決方案有三種:
這種方案,只需將程序裏涉及到數據庫讀寫的代碼按照分表邏輯進行改造便可,技術上比較簡單,不須要額外的軟件或者是技術的支持;缺點是對業務代碼侵入性強,可能涉及到多個地方的修改,工做量較大,並且後期的修改和維護成本也比較高。
這種方法對程序透明,是的上層業務邏輯不須要考慮分庫分表的讀寫規則,應用代碼能夠保持不變;
缺點是須要修改數據庫系統,或者以模塊、插件等形式對數據庫進行加強,開發成本和後期維護成本都很高,部分商業數據庫根本沒法自行修改。
這個應該是目前採用最多的方式;其優勢是對上層業務邏輯和底層數據庫都透明,只須要對應用的訪問層進行簡單改造,便可快速切換到拆分以後的數據庫,無論是開發人員仍是數據庫管理人員都不須要增長多少工做成本;
其缺點是技術門檻較高,須要專門的人員來開發和維護;功能受限,部分在單庫單表下的常見操做在這種中間層代理的方式下會變得麻煩,好比說跨庫、跨表join,全局數據分組與排序等。
對於以上幾種數據路由方案,不一樣的場景可能會有不一樣的選擇,具體要看自身的業務需求,技術儲備以及實施成本等。
以上通篇幾乎都在介紹如何對數據庫進行水平擴展和數據切分,其實有時候咱們沒必要搞得如此複雜,若是僅經過簡單的硬件升級就能知足業務增加對數據庫的要求,咱們何樂而不爲呢?
咱們都知道,普通的服務器,甚至是不少高端存儲,都是基於機械硬盤存放數據的;而機械硬盤最大的缺點就是速度上存在物理上限;
通常的機械硬盤讀寫數據都要通過尋道操做,主要由兩個步驟組成:一是移動磁頭,二是轉動盤片;這二者都屬於機械操做,前者通常在2ms~4ms,然後者,對於如今經常使用的SATA盤或SAS盤來講,通常會在1ms之內,因此,一次尋道操做要耗費大約5ms左右的時間,而對於一些老式硬盤來講,這個時間可能要達到10ms甚至更多!也就是說,單塊磁盤隨機讀寫的iops只有100左右,這簡直低的不可忍受,再牛B的系統也被拖死了!
對於一個初級團隊,再沒有不少技術積累的狀況下,盲目的進行水平擴容每每會給本身帶來更高的維護成本和更差的系統穩定性,因此這個時候咱們不妨嘗試一下數據庫性能提高的又一利器——閃存存儲;畢竟咱們不斷對系統進行優化的目標就是爲了得到更高的性能!