當數據庫中表的數據達到必定級別後,就須要考慮解決方案。事實上MySQL單表能夠存儲10億級數據,只是這時候性能比較差,業界公認MySQL單表容量在1KW如下是最佳狀態,由於這時它的BTREE索引樹高在3~5之間。既然一張表沒法搞定,那麼就想辦法將數據放到多個地方,目前比較廣泛的方案可能有下列幾種:java
歸檔適用場景:最新的數據會被常用,舊數據不多被使用。mysql
爲何不用NoSQL/NewSQLsql
首先,爲何不選擇第三種方案NoSQL/NewSQL,RDBMS主要有如下幾個優勢:數據庫
NoSQL/NewSQL做爲新生兒,在咱們把可靠性當作首要考察對象時,它是沒法與RDBMS相提並論的。RDBMS發展幾十年,只要有軟件的地方,它都是核心存儲的首選。
目前絕大部分公司的核心數據都是:**以RDBMS存儲爲主,NoSQL/NewSQL存儲爲輔**!
目前互聯網行業處理海量數據的通用方法:**分庫分表**。
典型的數據庫中間件設計方案有2種:proxy、smart-client。下圖演示了這兩種方案的架構
apache
功能點 | 代理模式 | 客戶端模式 |
---|---|---|
代理方式 | 服務端代理(proxy:代理數據庫)中: 咱們獨立部署一個代理服務,這個代理服務背後管理多個數據庫實例。而在應用中,咱們經過一個普通的數據源(c3p0、druid、dbcp等)與代理服務器創建鏈接,全部的sql操做語句都是發送給這個代理,由這個代理去操做底層數據庫,獲得結果並返回給應用。在這種方案下,分庫分表和讀寫分離的邏輯對開發人員是徹底透明的。 | 客戶端代理(datasource:代理數據源): 應用程序須要使用一個特定的數據源,其做用是代理,內部管理了多個普通的數據源(c3p0、druid、dbcp等),每一個普通數據源各自與不一樣的庫創建鏈接。應用程序產生的sql交給數據源代理進行處理,數據源內部對sql進行必要的操做,如sql改寫等,而後交給各個普通的數據源去執行,將獲得的結果進行合併,返回給應用。數據源代理一般也實現了JDBC規範定義的API,所以可以直接與orm框架整合。在這種方案下,用戶的代碼須要修改,使用這個代理的數據源,而不是直接使用c3p0、druid、dbcp這樣的鏈接池。 |
實現區別 | 複寫MySql協議 | 給予JDBC擴展,以Jar包形式提供輕量級服務 |
優勢 | 一、支持多語言,以mysql爲例,若是proxy實現了mysql通訊協議,能夠將其堪稱一個mysql服務器 二、對業務透明 | 一、實現簡單 2自然去中心化 |
缺點 | 一、實現複雜:須要實現被代理的數據庫server端的通訊協議,也許只能代理某一種數據庫,如mysql 二、proxy自己須要保證高可用:不能掛 三、租戶隔離:多個應用都訪問proxy代理的底層數據庫時 | 一、一般僅支持某一種語言:例如tddl需使用java語言開發 二、版本升級困難:多個應用都依賴某版本jar包,有bug都要升級;而proxy只要升級代理服務器 |
業界實現的產品 | 一、阿里開源cobar 二、阿里雲drds 三、奇虎360在mysql-proxy基礎上開發的atlas | 一、 阿里開源tddl 二、大衆點評開源zebra 三、螞蟻金服zal |
具體實現上有2種方案:hint 或APIapi
一些簡單的選擇策略包括:服務器
數據庫中間件主要對應用屏蔽瞭如下過程:架構
目前較爲流行的sql解析器包括:併發
3.2 SQL路由
框架
路由分則分爲:
· 庫規則:用於肯定到哪個分庫
· 表規則:用於肯定到哪個分表
在上例中,咱們使用id來做爲計算分表、分表,所以把id字段就稱之爲路由字段,或者分區字段。
須要注意的是,無論執行的是INSERT、UPDATE、DELETE、SELECT語句,SQL中都應該包含這個路由字段。不然,對於插入語句來講,就不知道插入到哪一個分庫或者分表;對於UPDATE、DELETE、SELECT語句而言,則更爲嚴重,由於不知道操做哪一個分庫分表,意味着必需要對全部分表都進行操做。SELECT聚合全部分表的內容,極容易內存溢出,UPDATE、DELETE更新、刪除全部的記錄,很是容易誤更新、刪除數據。所以,一些數據庫中間件,對於SQL可能有一些限制,例如UPDATE、DELETE必需要帶上分區字段,或者指定過濾條件。
路由引擎
https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/route/
3.3 SQL 改寫
前面已經介紹過,如一個批量插入語句,若是記錄要插入到不一樣的分庫分表中,那麼就須要對SQL進行改寫。 例如,將如下SQL
insert into user(id,name) values (1,」tianshouzhi」),(2,」huhuamin」), (3,」wanghanao」),(4,」luyang」)
改寫爲
insert into user_1(id,name) values (1,」tianshouzhi」)insert into user_2(id,name) values (2,」huhuamin」)insert into user_3(id,name) values (3,」wanghanao」)insert into user_0(id,name) values (4,」luyang」)這裏只是一個簡單的案例,一般對於INSERT、UPDATE、DELETE等,改寫相對簡單。比較複雜的是SELECT語句的改寫,對於一些複雜的SELECT語句,改寫過程當中會進行一些優化,例如將子查詢改爲JOIN,過濾條件下推等。由於SQL改寫很複雜,因此不少數據庫中間件並不支持複雜的SQL(一般有一個支持的SQL),只能支持一些簡單的OLTP場景。
下表都是按照order_id 分表 | 改以前 | 改以後 |
---|---|---|
標識符改寫 | SELECT order_id FROM t_order WHERE order_id=1; | SELECT order_id FROM t_order_1 WHERE order_id=1; |
排序補列 | SELECT order_id FROM t_order ORDER BY user_id; | SELECT order_id, user_id FROM t_order ORDER BY user_id; |
聚合補列 | SELECT AVG(price) FROM t_order WHERE user_id=1; | SELECT COUNT(price) AS AVG_DERIVED_COUNT_0, SUM(price) AS AVG_DERIVED_ SUM _0 FROM t_order WHERE user_id=1; |
自增主鍵補列 | INSERT INTO t_order (field1 , field2 ) VALUES (10, 1); |
INSERT INTO t_order (field1 , field2 , order_id) VALUES (10, 1, xxxxx); |
批量插入查分 | INSERT INTO t_order (order_id, xxx) VALUES (1, 'xxx'), (2, 'xxx'), (3, 'xxx'); | INSERT INTO t_order_0 (order_id, xxx) VALUES (2, 'xxx'); INSERT INTO t_order_1 (order_id, xxx) VALUES (1, 'xxx'), (3, 'xxx'); |
in查詢拆分 | SELECT * FROM t_order WHERE order_id IN (1, 2, 3); | vSELECT FROM t_order_0 WHERE order_id IN (2); SELECT FROM t_order_1 WHERE order_id IN (1, 3); |
3.4 SQL 執行
當通過SQL改寫階段後,會產生多個SQL,須要到不一樣的分片上去執行,一般咱們會使用一個線程池,將每一個SQL包裝成一個任務,提交到線程池裏面併發的去執行,以提高效率。
這些執行的SQL中,若是有一個失敗,則總體失敗,返回異常給業務代碼。
3.5 結果集合並
結果集合並,是數據庫中間件的一大難點,須要case by case的分析,主要是考慮實現的複雜度,以及執行的效率問題,對於一些複雜的SQL,可能並不支持。例如:
對於查詢條件:大部分中間件都支持=、IN做爲查詢條件,且能夠做爲分區字段。可是對於NOT IN、BETWEEN…AND、LIKE,NOT LIKE等,只能做爲普通的查詢條件,由於根據這些條件,沒法記錄究竟是在哪一個分庫或者分表,只能全表掃描。
聚合函數:大部分中間件都支持MAX、MIN、COUNT、SUM,可是對於AVG可能只是部分支持。另外,若是是函數嵌套、分組(GROUP BY)聚合,可能也有一些數據庫中間件不支持。
子查詢:分爲FROM部分的子查詢和WHERE部分的子查詢。大部分中對於子查詢的支持都是很是有限,例如語法上兼容,可是沒法識別子查詢中的分區字段,或者要求子查詢的表名必須與外部查詢表名相同,又或者只能支持一級嵌套子查詢。
JOIN:對於JOIN的支持一般很複雜,若是作不到過濾條件下推和流式讀取,在中間件層面,基本沒法對JOIN進行支持,由於不可能把兩個表的全部分表,所有拿到內存中來進行JOIN,內存早就崩了。固然也有一些取巧的辦法,一個是Binding Table,另一個是小表廣播(見後文)。
分頁排序:一般中間件都是支持ORDER BY和LIMIT的。可是在分庫分表的狀況下,分頁的效率較低。例如對於limit 100,10 ORDER BY id。表示按照id排序,從第100個位置開始取10條記錄。那麼,大部分數據庫中間件其實是要從每一個分表都查詢110(100+10)條記錄,拿到內存中進行從新排序,而後取出10條。假設有10個分表,那麼實際上要查詢1100條記錄,而最終只過濾出了10記錄。所以,在分頁的狀況下,一般建議使用"where id > ? limit 10」的方式來進行查詢,應用記住每次查詢的最大的記錄id。以後查詢時,每一個分表只須要從這個id以後,取10條記錄便可,而不是取offset + rows條記錄。
關於JOIN的特屬說明: