MySQL資料庫之分庫分表方案

lhrbest發表於2019-07-24


資料庫之網際網路常用分庫分表方案


一、資料庫瓶頸
1、IO瓶頸
2、CPU瓶頸
二、分庫分表
1、水平分庫
2、水平分表
3、垂直分庫
4、垂直分表
三、分庫分表工具
四、分庫分表步驟
五、分庫分表問題
1、非partition key的查詢問題(水平分庫分表,拆分策略為常用的hash法)
2、非partition key跨庫跨表分頁查詢問題(水平分庫分表,拆分策略為常用的hash法)
3、擴容問題(水平分庫分表,拆分策略為常用的hash法)
六、分庫分表總結
七、分庫分表示例

一、資料庫瓶頸

不管是IO瓶頸,還是CPU瓶頸,最終都會導致資料庫的活躍連線數增加,進而逼近甚至達到資料庫可承載活躍連線數的閾值。在業務Service來看就是,可用資料庫連線少甚至無連線可用。接下來就可以想象了吧(併發量、吞吐量、崩潰)。

1、IO瓶頸

第一種:磁碟讀IO瓶頸,熱點資料太多,資料庫快取放不下,每次查詢時會產生大量的IO,降低查詢速度 ->  分庫和垂直分表

第二種:網路IO瓶頸,請求的資料太多,網路頻寬不夠 ->  分庫

2、CPU瓶頸

第一種:SQL問題,如SQL中包含join,group by,order by,非索引欄位條件查詢等,增加CPU運算的操作 -> SQL最佳化,建立合適的索引,在業務Service層進行業務計算。

第二種:單表資料量太大,查詢時掃描的行太多,SQL效率低,CPU率先出現瓶頸 ->  水平分表

二、分庫分表

1、水平分庫

  1. 概念:以 欄位為依據 ,按照一定策略(hash、range等),將一個 中的資料拆分到多個 中。
  2. 結果:
    • 每個 結構都一樣;
    • 每個 資料都不一樣,沒有交集;
    • 所有 並集是全量資料;
  3. 場景:系統絕對併發量上來了,分表難以根本上解決問題,並且還沒有明顯的業務歸屬來垂直分庫。
  4. 分析:庫多了,io和cpu的壓力自然可以成倍緩解。

2、水平分表

  1. 概念:以 欄位為依據 ,按照一定策略(hash、range等),將一個 中的資料拆分到多個 中。
  2. 結果:
    • 每個 結構都一樣;
    • 每個 資料都不一樣,沒有交集;
    • 所有 並集是全量資料;
  3. 場景:系統絕對併發量並沒有上來,只是單表的資料量太多,影響了SQL效率,加重了CPU負擔,以至於成為瓶頸。
  4. 分析:表的資料量少了,單次SQL執行效率高,自然減輕了CPU的負擔。

3、垂直分庫

  1. 概念:以 為依據,按照業務歸屬不同,將不同的 拆分到不同的
  2. 結果:
    • 每個 結構都不一樣;
    • 每個 資料也不一樣,沒有交集;
    • 所有 並集是全量資料;
  3. 場景:系統絕對併發量上來了,並且可以抽象出單獨的業務模組。
  4. 分析:到這一步,基本上就可以服務化了。例如,隨著業務的發展一些公用的配置表、字典表等越來越多,這時可以將這些表拆到單獨的庫中,甚至可以服務化。再有,隨著業務的發展孵化出了一套業務模式,這時可以將相關的表拆到單獨的庫中,甚至可以服務化。

4、垂直分表

  1. 概念:以 欄位為依據,按照欄位的活躍性,將 中欄位拆到不同的 (主表和擴充套件表)中。
  2. 結果:
    • 每個 結構都不一樣;
    • 每個 資料也不一樣,一般來說,每個表的 欄位至少有一列交集,一般是主鍵,用於關聯資料;
    • 所有 並集是全量資料;
  3. 場景:系統絕對併發量並沒有上來,表的記錄並不多,但是欄位多,並且熱點資料和非熱點資料在一起,單行資料所需的儲存空間較大。以至於資料庫快取的資料行減少,查詢時會去讀磁碟資料產生大量的隨機讀IO,產生IO瓶頸。
  4. 分析:可以用列表頁和詳情頁來幫助理解。垂直分表的拆分原則是將熱點資料(可能會冗餘經常一起查詢的資料)放在一起作為主表,非熱點資料放在一起作為擴充套件表。這樣更多的熱點資料就能被快取下來,進而減少了隨機讀IO。拆了之後,要想獲得全部資料就需要關聯兩個表來取資料。但記住,千萬別用join,因為join不僅會增加CPU負擔並且會講兩個表耦合在一起(必須在一個資料庫例項上)。關聯資料,應該在業務Service層做文章,分別獲取主表和擴充套件表資料然後用關聯欄位關聯得到全部資料。

三、分庫分表工具

  1. sharding-sphere:jar,前身是sharding-jdbc;
  2. TDDL:jar,Taobao Distribute Data Layer;

  3. Mycat:中介軟體。

注:工具的利弊,請自行調研,官網和社群優先。

四、分庫分表步驟

根據容量(當前容量和增長量)評估分庫或分表個數 -> 選key(均勻)-> 分表規則(hash或range等)-> 執行(一般雙寫)-> 擴容問題(儘量減少資料的移動)。

五、分庫分表問題

1、非partition key的查詢問題(水平分庫分表,拆分策略為常用的hash法)

  1. 端上除了partition key只有一個非partition key作為條件查詢
    • 對映法
    • 基因法

      注:寫入時,基因法生成user_id,如圖。關於xbit基因,例如要分8張表,2 3=8,故x取3,即3bit基因。根據user_id查詢時可直接取模路由到對應的分庫或分表。根據user_name查詢時,先透過user_name_code生成函式生成user_name_code再對其取模路由到對應的分庫或分表。id生成常用 snowflake演算法

  2. 端上除了partition key不止一個非partition key作為條件查詢
    • 對映法
    • 冗餘法

      注:按照order_id或buyer_id查詢時路由到db_o_buyer庫中,按照seller_id查詢時路由到db_o_seller庫中。感覺有點本末倒置!有其他好的辦法嗎?改變技術棧呢?

  3. 後臺除了partition key還有各種非partition key組合條件查詢
    • NoSQL法
    • 冗餘法

2、非partition key跨庫跨表分頁查詢問題(水平分庫分表,拆分策略為常用的hash法)

注:用 NoSQL法解決(ES等)。

3、擴容問題(水平分庫分表,拆分策略為常用的hash法)

  1. 水平擴容庫(升級從庫法)

    注:擴容是成倍的。

  2. 水平擴容表(雙寫遷移法)

    第一步:(同步雙寫)應用配置雙寫,部署;
    第二步:(同步雙寫)將老庫中的老資料複製到新庫中;
    第三步:(同步雙寫)以老庫為準校對新庫中的老資料;
    第四步:(同步雙寫)應用去掉雙寫,部署;

注: 雙寫是通用方案。

六、分庫分表總結

  1. 分庫分表,首先得知道瓶頸在哪裡,然後才能合理地拆分(分庫還是分表?水平還是垂直?分幾個?)。且不可為了分庫分表而拆分。
  2. 選key很重要,既要考慮到拆分均勻,也要考慮到非partition key的查詢。
  3. 只要能滿足需求,拆分規則越簡單越好。

七、分庫分表示例

示例GitHub地址:

作者: 尜尜人物



三.分庫分表

當一張表隨著時間和業務的發展,庫裡表的資料量會越來越大。資料操作也隨之會越來越大。一臺物理機的資源有限,最終能承載的資料量、資料的處理能力都會受到限制。這時候就會使用分庫分表來承接超大規模的表,單機放不下的那種。

區別於分割槽的是,分割槽一般都是放在單機裡的,用的比較多的是時間範圍分割槽,方便歸檔。只不過分庫分表需要程式碼實現,分割槽則是 mysql內部實現。分庫分表和分割槽並不衝突,可以結合使用。

MySqlæ°æ®åºä¸ºä»ä¹è¦è¿è¡ååºå表æä½


3.1 實現

3.1.1 分庫分表標準

  • 儲存佔用100G+
  • 資料增量每天200w+
  • 單表條數1億條+

3.1.2 分庫分表欄位

分庫分表欄位取值非常重要

1.在大多數場景該欄位是查詢欄位

2.數值型

一般使用userId,可以滿足上述條件



3.2 分散式資料庫中介軟體

分散式資料庫中介軟體分為兩種,proxy和客戶端式架構。proxy模式有 MyCat、DBProxy等,客戶端式架構有TDDL、 Sharding-JDBC等。那麼proxy和客戶端式架構有何區別呢?各自有什麼優缺點呢?其實看一張圖便可知曉。

proxy模式的話我們的select和update語句都是傳送給代理,由這個代理來操作具體的底層資料庫。所以必須要求代理本身需要保證高可用,否則資料庫沒有當機,proxy掛了,那就走遠了。

客戶端模式通常在連線池上做了一層封裝,內部與不同的庫連線,sql交給smart-client進行處理。通常僅支援一種語言,如果其他語言要使用,需要開發多語言客戶端。

各自的優缺點如下:




為什麼要分庫分表?

隨著近些年資訊化大躍進,各行各業無紙化辦公產生了大量的資料,而越來越多的資料存入了資料庫中。當使用 MySQL資料庫的時候,單表超出了2000萬資料量就會出現效能上的分水嶺。並且物理伺服器的CPU、記憶體、儲存、連線數等資源有限,某個時段大量連線同時執行操作,會導致資料庫在處理上遇到效能瓶頸。為了解決這個問題,行業先驅門充分發揚了 分而治之的思想,對大表進行分割,然後實施更好的控制和管理,同時使用多臺機器的CPU、記憶體、儲存,提供更好的效能。而 分而治之則有兩種方式: 垂直拆分水平拆分

垂直拆分

垂直拆分分為 垂直分庫垂直分表。先說說 垂直分庫。垂直分庫其實是一種簡單邏輯分割。比如我們的資料庫中有商品表Products、還有對訂單表Orders,還有積分表Scores。接下來我們就可以建立三個資料庫,一個資料庫存放商品,一個資料庫存放訂單,一個資料庫存放積分。如下圖所示:
垂直分庫

垂直分庫有一個優點,他能夠根據業務場景進行孵化,比如某一單一場景只用到某2-3張表,基本上應用和資料庫可以拆分出來做成相應的服務。

再來說說 垂直分表,比較適用於那種欄位比較多的表,假設我們一張表有100個欄位,我們分析了一下當前業務執行的SQL語句,有20個欄位是經常使用的,而另外80個欄位使用比較少。這樣我們就可以把20個欄位放在主表裡面,我們在建立一個輔助表,存放另外80個欄位。當然主表和輔助表都是有主鍵的。他們透過主鍵進行關聯合並,就可以湊成100個欄位的表。
垂直分表

垂直分表可以解決 跨頁的問題。在Oracle中叫行連結。怎麼理解呢?就是你欄位少的情況下,原本一行資料只需要存在一個頁裡面就行了,但是欄位多的情況就存不下了,就需要跨頁。這樣就會造成額外定址,造成效能上的開銷。另外將這麼長的一行資料載到記憶體中,往往是幾個頁面,結果我們們經常只訪問其中的幾個欄位,對記憶體也是一個極大的開銷。所以為了讓記憶體快取更多資料,減少磁碟I/O, 垂直分表就是很好的手段。

總體來說: 垂直拆分有以下優點:

  • 跟隨業務進行分割,和最近流行的微服務概念相似,方便解耦之後的管理及擴充套件。
  • 高併發的場景下,垂直拆分使用多臺伺服器的CPU、I/O、記憶體能提升效能,同時對單機資料庫連線數、一些資源限制也得到了提升。
  • 能實現冷熱資料的分離。

垂直拆分的缺點:

  • 部分業務表無法join,應用層需要很大的改造,只能透過聚合的方式來實現。增加了開發的難度。
  • 當單庫中的表資料量增大的時候依然沒有得到有效的解決。
  • 分散式事務也是一個難題。

水平拆分

當某張表資料量達到一定的程度的時候,前面曾說過MySQL單表出現2000萬以上資料就會出現效能上的分水嶺。此時發現沒有辦法根據業務規則再進行拆分了,就會導致單庫上的讀寫效能出現瓶頸。此時就只能進行水平拆分了。

水平拆分又分為 庫內分表分庫分表。先說說 庫內分表。假設當我們的Orders表達到了5000萬行記錄的時候,非常影響資料庫的讀寫效率,怎麼辦呢?我們可以考慮按照訂單編號的order_id進行rang分割槽,就是把訂單編號在1-1000萬的放在order1表中,將編號在1000萬-2000萬的放在order2中,以此類推,每個表中存放1000萬資料。如下圖所示:

庫內分表

雖然我們可以透過 庫內分表把單表的容量固定在1000萬,但是這些表的資料仍然存放在一個庫內,使用的是該主機的CPU、IO、記憶體。單庫的連線數也有限制。並不能完全的降低系統的壓力。此時,我們就要考慮另外一種技術叫 分庫分表。分庫分表在庫內分表的基礎上,將分的表挪動到不同的主機和資料庫上。可以充分的使用其他主機的CPU、記憶體和IO資源。並且分庫之後,單庫的連線數限制也不在成為瓶頸。但是“成也蕭何敗也蕭何”,如果你執行一個掃描不帶分片鍵,則需要在每個庫上查一遍。剛剛我們按照order_id分成了5個庫,但是我們查詢是name='AAA'的條件並且不帶order_id欄位時,它並不知道在哪個分片上查,則會建立5個連線,然後每個庫都檢索一遍。這種廣播查詢則會造成連線數增多。因為它需要在每個庫上都創立連線。如果是高併發的系統,執行這種廣播查詢,系統的thread很快就會告警。

分庫分表

總體來說: 水平拆分的優點有以下:

  • 水平擴充套件能無線擴充套件。不存在某個庫某個表過大的情況。
  • 能夠較好的應對高併發,同時可以將熱點資料打散。
  • 應用側的改動較小,不需要根據業務來拆分。

水平拆分的缺點:

  • 路由是個問題,需要增加一層路由的計算,而且像前面說的一樣,不帶分片鍵查詢會產生廣播SQL。
  • 跨庫join的效能比較差。
  • 需要處理分散式事務的一致性問題。

一起使用

當前我們的系統, 垂直拆分水平拆分都在使用, 垂直拆分主要是做業務上的分割,把業務的各個子系統都規劃好,能解耦就解耦。而垂直拆分之後。我們再做水平分庫分表。透過取模演算法將大表資料拆到若干個庫中。

邏輯庫和物理庫

介紹了上述的分庫分表,我們有必要說一下幾個概念,一個是 邏輯庫物理庫的概念。我們還是拿水平拆分中的 分庫分表來說。我們在物理層面,將一個庫的資料分割到了5個資料庫中。這5個資料庫就是 物理庫,而它們對上層應用的展現則是一個庫。這個對上層展現的庫就叫 邏輯庫。邏輯庫對應用層是透明的。應用不需要了解底層的情況,直接使用就行了。

邏輯表和物理表

還是拿水平拆分中的 分庫分表來說,orders表總共被分成了5份,分別在底層是orders_1~5。這底層的5個表就是物理表。但是對應用層面來說,只有orders表。這就是 邏輯表

總結:這一篇主要是講述一些分庫分表之後的概念。需要加深一些理解,因為我們的專案也才是剛剛開始拆分,所以有寫的不對的地方還希望小夥伴們提出意見指正。







資料庫分庫分表思路

一. 資料切分

關係型資料庫本身比較容易成為系統瓶頸,單機儲存容量、連線數、處理能力都有限。當單表的資料量達到1000W或100G以後,由於查詢維度較多,即使新增從庫、最佳化索引,做很多操作時效能仍下降嚴重。此時就要考慮對其進行切分了,切分的目的就在於減少資料庫的負擔,縮短查詢時間。

資料庫分散式核心內容無非就是資料切分(Sharding),以及切分後對資料的定位、整合。資料切分就是將資料分散儲存到多個資料庫中,使得單一資料庫中的資料量變小,透過擴充主機的數量緩解單一資料庫的效能問題,從而達到提升資料庫操作效能的目的。

資料切分根據其切分型別,可以分為兩種方式: 垂直(縱向)切分和水平(橫向)切分

 

1、垂直(縱向)切分

垂直切分常見有垂直分庫和垂直分表兩種。

垂直分庫就是根據業務耦合性,將關聯度低的不同表儲存在不同的資料庫。做法與大系統拆分為多個小系統類似,按業務分類進行獨立劃分。與"微服務治理"的做法相似,每個微服務使用單獨的一個資料庫。如圖:

垂直分表是基於資料庫中的"列"進行,某個表欄位較多,可以新建一張擴充套件表,將不經常用或欄位長度較大的欄位拆分出去到擴充套件表中。在欄位很多的情況下(例如一個大表有100多個欄位),透過"大表拆小表",更便於開發與維護,也能避免跨頁問題,MySQL底層是透過資料頁儲存的,一條記錄佔用空間過大會導致跨頁,造成額外的效能開銷。另外資料庫以行為單位將資料載入到記憶體中,這樣表中欄位長度較短且訪問頻率較高,記憶體能載入更多的資料,命中率更高,減少了磁碟IO,從而提升了資料庫效能。

垂直切分的優點:

  • 解決業務系統層面的耦合,業務清晰
  • 與微服務的治理類似,也能對不同業務的資料進行分級管理、維護、監控、擴充套件等
  • 高併發場景下,垂直切分一定程度的提升IO、資料庫連線數、單機硬體資源的瓶頸

缺點:

  • 部分表無法join,只能透過介面聚合方式解決,提升了開發的複雜度
  • 分散式事務處理複雜
  • 依然存在單表資料量過大的問題(需要水平切分)

 

2、水平(橫向)切分

當一個應用難以再細粒度的垂直切分,或切分後資料量行數巨大,存在單庫讀寫、儲存效能瓶頸,這時候就需要進行水平切分了。

水平切分分為 庫內分表和分庫分表,是根據表內資料內在的邏輯關係,將同一個表按不同的條件分散到多個資料庫或多個表中,每個表中只包含一部分資料,從而使得單個表的資料量變小,達到分散式的效果。如圖所示:  

庫內分表只解決了單一表資料量過大的問題,但沒有將表分佈到不同機器的庫上,因此對於減輕MySQL資料庫的壓力來說,幫助不是很大,大家還是競爭同一個物理機的CPU、記憶體、網路IO,最好透過分庫分表來解決。

水平切分的優點:

  • 不存在單庫資料量過大、高併發的效能瓶頸,提升系統穩定性和負載能力
  • 應用端改造較小,不需要拆分業務模組

缺點:

  • 跨分片的事務一致性難以保證
  • 跨庫的join關聯查詢效能較差
  • 資料多次擴充套件難度和維護量極大

水平切分後同一張表會出現在多個資料庫/表中,每個庫/表的內容不同。幾種典型的資料分片規則為:

1、根據數值範圍

按照時間區間或ID區間來切分。例如:按日期將不同月甚至是日的資料分散到不同的庫中;將userId為1~9999的記錄分到第一個庫,10000~20000的分到第二個庫,以此類推。某種意義上,某些系統中使用的" 冷熱資料分離",將一些使用較少的歷史資料遷移到其他庫中,業務功能上只提供熱點資料的查詢,也是類似的實踐。

這樣的優點在於:

  • 單表大小可控
  • 天然便於水平擴充套件,後期如果想對整個分片叢集擴容時,只需要新增節點即可,無需對其他分片的資料進行遷移
  • 使用分片欄位進行範圍查詢時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。

缺點:

  • 熱點資料成為效能瓶頸。連續分片可能存在資料熱點,例如按時間欄位分片,有些分片儲存最近時間段內的資料,可能會被頻繁的讀寫,而有些分片儲存的歷史資料,則很少被查詢

2、根據數值取模

一般採用hash取模mod的切分方式,例如:將 Customer 表根據 cusno 欄位切分到4個庫中,餘數為0的放到第一個庫,餘數為1的放到第二個庫,以此類推。這樣同一個使用者的資料會分散到同一個庫中,如果查詢條件帶有cusno欄位,則可明確定位到相應庫去查詢。

優點:

  • 資料分片相對比較均勻,不容易出現熱點和併發訪問的瓶頸

缺點:

  • 後期分片叢集擴容時,需要遷移舊的資料(使用一致性hash演算法能較好的避免這個問題)
  • 容易面臨跨分片查詢的複雜問題。比如上例中,如果頻繁用到的查詢條件中不帶cusno時,將會導致無法定位資料庫,從而需要同時向4個庫發起查詢,再在記憶體中合併資料,取最小集返回給應用,分庫反而成為拖累。

二. 分庫分錶帶來的問題

分庫分表能有效的環節單機和單庫帶來的效能瓶頸和壓力,突破網路IO、硬體資源、連線數的瓶頸,同時也帶來了一些問題。下面將描述這些技術挑戰以及對應的解決思路。 

1、事務一致性問題

分散式事務

當更新內容同時分佈在不同庫中,不可避免會帶來跨庫事務問題。跨分片事務也是 分散式事務,沒有簡單的方案,一般可使用"XA協議"和"兩階段提交"處理

分散式事務能最大限度保證了資料庫操作的原子性。但在提交事務時需要協調多個節點,推後了提交事務的時間點,延長了事務的執行時間。導致事務在訪問共享資源時發生衝突或死鎖的機率增高。隨著資料庫節點的增多,這種趨勢會越來越嚴重,從而成為系統在資料庫層面上水平擴充套件的枷鎖。

最終一致性

對於那些效能要求很高,但對一致性要求不高的系統, 往往不苛求系統的實時一致性,只要在允許的時間段內達到最終一致性即可,可採用事務補償的方式。與事務在執行中發生錯誤後立即回滾的方式不同,事務補償是一種事後檢查補救的措施,一些常見的實現方法有:對資料進行對賬檢查,基於日誌進行對比,定期同標準資料來源進行同步等等。事務補償還要結合業務系統來考慮。

 

2、跨節點關聯查詢 join 問題

切分之前,系統中很多列表和詳情頁所需的資料可以透過sql join來完成。而切分之後,資料可能分佈在不同的節點上,此時join帶來的問題就比較麻煩了,考慮到效能,儘量避免使用join查詢。

解決這個問題的一些方法:

1)全域性表

全域性表,也可看做是"資料字典表",就是系統中所有模組都可能依賴的一些表,為了避免跨庫join查詢, 可以將這類表在每個資料庫中都儲存一份。這些資料通常很少會進行修改,所以也不擔心一致性的問題。

2)欄位冗餘

一種典型的反正規化設計, 利用空間換時間,為了效能而避免join查詢。例如:訂單表儲存userId時候,也將userName冗餘儲存一份,這樣查詢訂單詳情時就不需要再去查詢"買家user表"了。

但這種方法適用場景也有限,比較適用於依賴欄位比較少的情況。而冗餘欄位的資料一致性也較難保證,就像上面訂單表的例子,買家修改了userName後,是否需要在歷史訂單中同步更新呢?這也要結合實際業務場景進行考慮。

3)資料組裝

在系統層面, 分兩次查詢,第一次查詢的結果集中找出關聯資料id,然後根據id發起第二次請求得到關聯資料。最後將獲得到的資料進行欄位拼裝。

4)ER分片

關係型資料庫中,如果可以先確定表之間的關聯關係,並 將那些存在關聯關係的表記錄存放在同一個分片上,那麼就能較好的避免跨分片join問題。在1:1或1:n的情況下,通常按照主表的ID主鍵切分。如下圖所示:

這樣一來,Data Node1上面的order訂單表與orderdetail訂單詳情表就可以透過orderId進行區域性的關聯查詢了,Data Node2上也一樣。

 

3、跨節點分頁、排序、函式問題

跨節點多庫進行查詢時,會出現limit分頁、order by排序等問題。分頁需要按照指定欄位進行排序,當排序欄位就是分片欄位時,透過分片規則就比較容易定位到指定的分片;當排序欄位非分片欄位時,就變得比較複雜了。需要 先在不同的分片節點中將資料進行排序並返回,然後將不同分片返回的結果集進行彙總和再次排序,最終返回給使用者。如圖所示:

上圖中只是取第一頁的資料,對效能影響還不是很大。但是如果取得頁數很大,情況則變得複雜很多,因為各分片節點中的資料可能是隨機的,為了排序的準確性,需要將所有節點的前N頁資料都排序好做合併,最後再進行整體的排序,這樣的操作時很耗費CPU和記憶體資源的,所以頁數越大,系統的效能也會越差。

在使用Max、Min、Sum、Count之類的函式進行計算的時候,也需要 先在每個分片上執行相應的函式,然後將各個分片的結果集進行彙總和再次計算,最終將結果返回。如圖所示:

 

4、全域性主鍵避重問題

在分庫分表環境中,由於表中資料同時存在不同資料庫中,主鍵值平時使用的自增長將無用武之地,某個分割槽資料庫自生成的ID無法保證全域性唯一。因此需要單獨設計全域性主鍵,以避免跨庫主鍵重複問題。有一些常見的主鍵生成策略:

1)UUID

UUID標準形式包含32個16進位制數字,分為5段,形式為8-4-4-4-12的36個字元,例如:550e8400-e29b-41d4-a716-446655440000

UUID是主鍵是最簡單的方案,本地生成,效能高,沒有網路耗時。但缺點也很明顯,由於UUID非常長,會佔用大量的儲存空間;另外,作為主鍵建立索引和基於索引進行查詢時都會存在效能問題,在InnoDB下,UUID的無序性會引起資料位置頻繁變動,導致分頁。

2)結合資料庫維護主鍵ID表

在資料庫中建立 sequence 表:

CREATE TABLE `sequence` (  
  `id` bigint(20) unsigned NOT NULL auto_increment,  
  `stub` char(1) NOT NULL default '',  
  PRIMARY KEY  (`id`),  
  UNIQUE KEY `stub` (`stub`)  
) ENGINE=MyISAM;

stub欄位設定為唯一索引,同一stub值在sequence表中只有一條記錄,可以同時為多張表生成全域性ID。sequence表的內容,如下所示:

+-------------------+------+  | id                | stub |  +-------------------+------+  | 72157623227190423 |    a |  +-------------------+------+

使用 MyISAM 儲存引擎而不是 InnoDB,以獲取更高的效能。MyISAM使用的是表級別的鎖,對錶的讀寫是序列的,所以不用擔心在併發時兩次讀取同一個ID值。

當需要全域性唯一的64位ID時,執行:

REPLACE INTO sequence (stub) VALUES ('a');  
SELECT LAST_INSERT_ID();

這兩條語句是Connection級別的,select last_insert_id() 必須與 replace into 在同一資料庫連線下才能得到剛剛插入的新ID。

使用replace into代替insert into好處是避免了錶行數過大,不需要另外定期清理。

此方案較為簡單,但缺點也明顯:存在單點問題,強依賴DB,當DB異常時,整個系統都不可用。配置主從可以增加可用性,但當主庫掛了,主從切換時,資料一致性在特殊情況下難以保證。另外效能瓶頸限制在單臺MySQL的讀寫效能。

flickr團隊使用的一種主鍵生成策略,與上面的sequence表方案類似,但更好的解決了單點和效能瓶頸的問題。

這一方案的整體思想是:建立2個以上的全域性ID生成的伺服器,每個伺服器上只部署一個資料庫,每個庫有一張sequence表用於記錄當前全域性ID。表中ID增長的步長是庫的數量,起始值依次錯開,這樣能將ID的生成雜湊到各個資料庫上。如下圖所示:

由兩個資料庫伺服器生成ID,設定不同的auto_increment值。第一臺sequence的起始值為1,每次步長增長2,另一臺的sequence起始值為2,每次步長增長也是2。結果第一臺生成的ID都是奇數(1, 3, 5, 7 ...),第二臺生成的ID都是偶數(2, 4, 6, 8 ...)。

這種方案將生成ID的壓力均勻分佈在兩臺機器上。同時提供了系統容錯,第一臺出現了錯誤,可以自動切換到第二臺機器上獲取ID。但有以下幾個缺點:系統新增機器,水平擴充套件時較複雜;每次獲取ID都要讀寫一次DB,DB的壓力還是很大,只能靠堆機器來提升效能。

可以基於flickr的方案繼續最佳化,使用批次的方式降低資料庫的寫壓力,每次獲取一段區間的ID號段,用完之後再去資料庫獲取,可以大大減輕資料庫的壓力。如下圖所示:

還是使用兩臺DB保證可用性,資料庫中只儲存當前的最大ID。ID生成服務每次批次拉取6個ID,先將max_id修改為5,當應用訪問ID生成服務時,就不需要訪問資料庫,從號段快取中依次派發0~5的ID。當這些ID發完後,再將max_id修改為11,下次就能派發6~11的ID。於是,資料庫的壓力降低為原來的1/6。

3)Snowflake分散式自增ID演算法

Twitter的snowflake演算法解決了分散式系統生成全域性ID的需求,生成64位的Long型數字,組成部分:

  • 第一位未使用
  • 接下來41位是毫秒級時間,41位的長度可以表示69年的時間
  • 5位datacenterId,5位workerId。10位的長度最多支援部署1024個節點
  • 最後12位是毫秒內的計數,12位的計數順序號支援每個節點每毫秒產生4096個ID序列

這樣的好處是:毫秒數在高位,生成的ID整體上按時間趨勢遞增;不依賴第三方系統,穩定性和效率較高,理論上QPS約為409.6w/s(1000*2^12),並且整個分散式系統內不會產生ID碰撞;可根據自身業務靈活分配bit位。

不足就在於:強依賴機器時鐘,如果時鐘回撥,則可能導致生成ID重複。

綜上

結合資料庫和snowflake的唯一ID方案,可以參考業界較為成熟的解法: ,並考慮到了高可用、容災、分散式下時鐘等問題。

 

5、資料遷移、擴容問題

當業務高速發展,面臨效能和儲存的瓶頸時,才會考慮分片設計,此時就不可避免的需要考慮歷史資料遷移的問題。一般做法是先讀出歷史資料,然後按指定的分片規則再將資料寫入到各個分片節點中。此外還需要根據當前的資料量和QPS,以及業務發展的速度,進行容量規劃,推算出大概需要多少分片(一般建議單個分片上的單表資料量不超過1000W)

如果採用數值範圍分片,只需要新增節點就可以進行擴容了,不需要對分片資料遷移。如果採用的是數值取模分片,則考慮後期的擴容問題就相對比較麻煩。

三. 什麼時候考慮切分

下面講述一下什麼時候需要考慮做資料切分。

1、能不切分儘量不要切分

並不是所有表都需要進行切分,主要還是看資料的增長速度。切分後會在某種程度上提升業務的複雜度,資料庫除了承載資料的儲存和查詢外,協助業務更好的實現需求也是其重要工作之一。

不到萬不得已不用輕易使用分庫分表這個大招,避免"過度設計"和"過早最佳化"。分庫分表之前,不要為分而分,先盡力去做力所能及的事情,例如:升級硬體、升級網路、讀寫分離、索引最佳化等等。當資料量達到單表的瓶頸時候,再考慮分庫分表。

2、資料量過大,正常運維影響業務訪問

這裡說的運維,指:

1)對資料庫備份,如果單表太大,備份時需要大量的磁碟IO和網路IO。例如1T的資料,網路傳輸佔50MB時候,需要20000秒才能傳輸完畢,整個過程的風險都是比較高的

2)對一個很大的表進行DDL修改時,MySQL會鎖住全表,這個時間會很長,這段時間業務不能訪問此表,影響很大。如果使用pt-online-schema-change,使用過程中會建立觸發器和影子表,也需要很長的時間。在此操作過程中,都算為風險時間。將資料表拆分,總量減少,有助於降低這個風險。

3)大表會經常訪問與更新,就更有可能出現鎖等待。將資料切分,用空間換時間,變相降低訪問壓力

3、隨著業務發展,需要對某些欄位垂直拆分

舉個例子,假如專案一開始設計的使用者表如下:

id                   bigint             #使用者的ID
name                 varchar            #使用者的名字
last_login_time      datetime           #最近登入時間
personal_info        text               #私人資訊
.....                                   #其他資訊欄位

在專案初始階段,這種設計是滿足簡單的業務需求的,也方便快速迭代開發。而當業務快速發展時,使用者量從10w激增到10億,使用者非常的活躍,每次登入會更新 last_login_name 欄位,使得 user 表被不斷update,壓力很大。而其他欄位:id, name, personal_info 是不變的或很少更新的,此時在業務角度,就要將 last_login_time 拆分出去,新建一個 user_time 表。

personal_info 屬性是更新和查詢頻率較低的,並且text欄位佔據了太多的空間。這時候,就要對此垂直拆分出 user_ext 表了。

4、資料量快速增長

隨著業務的快速發展,單表中的資料量會持續增長,當效能接近瓶頸時,就需要考慮水平切分,做分庫分表了。此時一定要選擇合適的切分規則,提前預估好資料容量

5、安全性和可用性

雞蛋不要放在一個籃子裡。在業務層面上垂直切分,將不相關的業務的資料庫分隔,因為每個業務的資料量、訪問量都不同,不能因為一個業務把資料庫搞掛而牽連到其他業務。利用水平切分,當一個資料庫出現問題時,不會影響到100%的使用者,每個庫只承擔業務的一部分資料,這樣整體的可用性就能提高。

四. 案例分析

1、使用者中心業務場景

使用者中心是一個非常常見的業務,主要提供使用者註冊、登入、查詢/修改等功能,其核心表為:

User(uid, login_name, passwd, sex, age, nickname)
uid為使用者ID,  主鍵
login_name, passwd, sex, age, nickname,  使用者屬性

任何脫離業務的架構設計都是耍流氓,在進行分庫分表前,需要對業務場景需求進行梳理:

  • 使用者側:前臺訪問,訪問量較大,需要保證高可用和高一致性。主要有兩類需求:

    • 使用者登入:透過login_name/phone/email查詢使用者資訊,1%請求屬於這種型別
    • 使用者資訊查詢:登入之後,透過uid來查詢使用者資訊,99%請求屬這種型別
  • 運營側:後臺訪問,支援運營需求,按照年齡、性別、登陸時間、註冊時間等進行分頁的查詢。是內部系統,訪問量較低,對可用性、一致性的要求不高。

 

2、水平切分方法

當資料量越來越大時,需要對資料庫進行水平切分,上文描述的切分方法有"根據數值範圍"和"根據數值取模"。

"根據數值範圍":以主鍵uid為劃分依據,按uid的範圍將資料水平切分到多個資料庫上。例如:user-db1儲存uid範圍為0~1000w的資料,user-db2儲存uid範圍為1000w~2000wuid資料。

  • 優點是:擴容簡單,如果容量不夠,只要增加新db即可。

  • 不足是:請求量不均勻,一般新註冊的使用者活躍度會比較高,所以新的user-db2會比user-db1負載高,導致伺服器利用率不平衡

"根據數值取模":也是以主鍵uid為劃分依據,按uid取模的值將資料水平切分到多個資料庫上。例如:user-db1儲存uid取模得1的資料,user-db2儲存uid取模得0的uid資料。

  • 優點是:資料量和請求量分佈均均勻

  • 不足是:擴容麻煩,當容量不夠時,新增加db,需要rehash。需要考慮對資料進行平滑的遷移。

 

3、非uid的查詢方法

水平切分後,對於按uid查詢的需求能很好的滿足,可以直接路由到具體資料庫。而按非uid的查詢,例如login_name,就不知道具體該訪問哪個庫了,此時需要遍歷所有庫,效能會降低很多。

對於使用者側,可以採用"建立非uid屬性到uid的對映關係"的方案;對於運營側,可以採用"前臺與後臺分離"的方案

3.1、建立非uid屬性到uid的對映關係

1)對映關係

例如:login_name不能直接定位到資料庫,可以 建立 login_name→uid的對映關係,用索引表或快取來儲存。當訪問login_name時,先透過對映表查詢出login_name對應的uid,再透過uid定位到具體的庫。

對映表只有兩列,可以承載很多資料,當資料量過大時,也可以對對映表再做水平切分。這類kv格式的索引結構,可以很好的使用cache來最佳化查詢效能,而且對映關係不會頻繁變更,快取命中率會很高。

2)基因法

分庫基因:假如透過uid分庫,分為8個庫,採用uid%8的方式進行路由,此時是由uid的最後3bit來決定這行User資料具體落到哪個庫上,那麼這3bit可以看為分庫基因。

上面的對映關係的方法需要額外儲存對映表,按非uid欄位查詢時,還需要多一次資料庫或cache的訪問。如果想要消除多餘的儲存和查詢,可以透過f函式取login_name的基因作為uid的分庫基因。生成uid時,參考上文所述的分散式唯一ID生成方案,再加上最後3位bit值=f(login_name)。當查詢login_name時,只需計算f(login_name)%8的值,就可以定位到具體的庫。不過這樣需要提前做好容量規劃,預估未來幾年的資料量需要分多少庫,要預留一定bit的分庫基因。

 

3.2、前臺與後臺分離

對於使用者側,主要需求是以單行查詢為主,需要建立login_name/phone/email到uid的對映關係,可以解決這些欄位的查詢問題。

而對於運營側,很多批次分頁且條件多樣的查詢,這類查詢計算量大,返回資料量大,對資料庫的效能消耗較高。此時,如果和使用者側公用同一批服務或資料庫,可能因為後臺的少量請求,佔用大量資料庫資源,而導致使用者側訪問效能降低或超時。

這類業務最好採用"前臺與後臺分離"的方案, 運營側後臺業務抽取獨立的service和db,解決和前臺業務系統的耦合。由於運營側對可用性、一致性的要求不高,可以不訪問實時庫,而是 透過binlog非同步同步資料到運營庫進行訪問。在資料量很大的情況下,還可以使用ES搜尋引擎或Hive來滿足後臺複雜的查詢方式。

五. 支援分庫分表中介軟體

站在巨人的肩膀上能省力很多,目前分庫分表已經有一些較為成熟的開源解決方案:

六. 參考

 

分庫分表的思想  

 

 

 

資料庫水平切分架構實踐-【架構師之路】公眾號



搞清分庫分表(垂直分庫,垂直分表,水平分庫,水平分表)

分庫分表是什麼

下邊以電商系統中的例子來說明,下圖是電商系統賣家模組的表結構:
在這裡插入圖片描述
透過以下SQL能夠獲取到商品相關的店鋪資訊、地理區域資訊:

SELECT p.*,r.[地理區域名稱],s.[店鋪名稱],s.[信譽]
FROM [商品資訊] p 
LEFT JOIN [地理區域] r ON p.[產地] = r.[地理區域編碼]
LEFT JOIN [店鋪資訊] s ON p.id = s.[所屬店鋪]
WHERE p.id = ?12345

隨著公司業務快速發展,資料庫中的資料量猛增,訪問效能也變慢了,最佳化迫在眉睫。分析一下問題出現在哪兒呢? 關係型資料庫本身比較容易成為系統瓶頸,單機儲存容量、連線數、處理能力都有限。當單表的資料量達到1000W或100G以後,由於查詢維度較多,即使新增從庫、最佳化索引,做很多操作時效能仍下降嚴重。

方案1:

透過提升伺服器硬體能力來提高資料處理能力,比如增加儲存容量 、CPU等,這種方案成本很高,並且如果瓶頸在MySQL本身那麼提高硬體也是有很的。

方案2:

把資料分散在不同的資料庫中,使得單一資料庫的資料量變小來緩解單一資料庫的效能問題,從而達到提升資料庫效能的目的,如下圖:將電商資料庫拆分為若干獨立的資料庫,並且對於大表也拆分為若干小表,透過這種資料庫拆分的方法來解決資料庫的效能問題。
在這裡插入圖片描述
分庫分表就是為了解決由於資料量過大而導致資料庫效能降低的問題,將原來獨立的資料庫拆分成若干資料庫組成 ,將資料大表拆分成若干資料表組成,使得單一資料庫、單一資料表的資料量變小,從而達到提升資料庫效能的目的。

垂直分表

分庫分表包括分庫和分表兩個部分,在生產中通常包括:垂直分庫、水平分庫、垂直分表、水平分表四種方式。
先說 垂直分表:
通常在商品列表中是不顯示商品詳情資訊的,如下圖:
在這裡插入圖片描述
使用者在瀏覽商品列表時,只有對某商品感興趣時才會檢視該商品的詳細描述。因此,商品資訊中商品描述欄位訪問頻次較低,且該欄位儲存佔用空間較大,訪問單個資料IO時間較長;商品資訊中商品名稱、商品圖片、商品價格等其他欄位資料訪問頻次較高。

由於這兩種資料的特性不一樣,因此他考慮將商品資訊表拆分如下:

將訪問頻次低的商品描述資訊單獨存放在一張表中,訪問頻次較高的商品基本資訊單獨放在一張表中。
在這裡插入圖片描述
商品列表可採用以下sql:

SELECT p.*,r.[地理區域名稱],s.[店鋪名稱],s.[信譽]
FROM [商品資訊] p 
LEFT JOIN [地理區域] r ON p.[產地] = r.[地理區域編碼]
LEFT JOIN [店鋪資訊] s ON p.id = s.[所屬店鋪]
WHERE...ORDER BY...LIMIT...12345

需要獲取商品描述時,再透過以下sql獲取:

SELECT *
FROM [商品描述] 
WHERE [商品ID] = ?123

垂直分表定義:將一個表按照欄位分成多表,每個表儲存其中一部分欄位。
它帶來的提升是:

1.為了避免IO爭搶並減少鎖表的機率,檢視詳情的使用者與商品資訊瀏覽互不影響

2.充分發揮熱門資料的操作效率,商品資訊的操作的高效率不會被商品描述的低效率所拖累。

為什麼大欄位IO效率低:第一是由於資料量本身大,需要更長的讀取時間;第二是跨頁,頁是資料庫儲存單位,很多查詢及定位操作都是以頁為單位,單頁內的資料行越多資料庫整體效能越好,而大欄位佔用空間大,單頁記憶體儲行數少,因此IO效率較低。第三,資料庫以行為單位將資料載入到記憶體中,這樣表中欄位長度較短且訪問頻率較高,記憶體能載入更多的資料,命中率更高,減少了磁碟IO,從而提升了資料庫效能。

一般來說,某業務實體中的各個資料項的訪問頻次是不一樣的,部分資料項可能是佔用儲存空間比較大的BLOB或是TEXT。例如上例中的商品描述。所以,當表資料量很大時,可以將表按欄位切開,將熱門欄位、冷門欄位分開放置在不同庫中,這些庫可以放在不同的儲存裝置上,避免IO爭搶。垂直切分帶來的效能提升主要集中在熱門資料的操作效率上,而且磁碟爭用情況減少。

通常我們按以下原則進行垂直拆分:

  1. 把不常用的欄位單獨放在一張表;
  2. 把text,blob等大欄位拆分出來放在附表中;
  3. 經常組合查詢的列放在一張表中;

垂直分庫

透過垂直分表效能得到了一定程度的提升,但是還沒有達到要求,並且磁碟空間也快不夠了,因為資料還是始終限制在一臺伺服器,庫內垂直分表只解決了單一表資料量過大的問題,但沒有將表分佈到不同的伺服器上,因此每個表還是競爭同一個物理機的CPU、記憶體、網路IO、磁碟。

經過思考,他把原有的SELLER_DB(賣家庫),分為了PRODUCT_DB(商品庫)和STORE_DB(店鋪庫),並把這兩個庫分散到不同伺服器,如下圖:
在這裡插入圖片描述
由於商品資訊與商品描述業務耦合度較高,因此一起被存放在PRODUCT_DB(商品庫);而店鋪資訊相對獨立,因此單獨被存放在STORE_DB(店鋪庫)。

垂直分庫是指按照業務將表進行分類,分佈到不同的資料庫上面,每個庫可以放在不同的伺服器上,它的核心理念是專庫專用。

它帶來的提升是:

  • 解決業務層面的耦合,業務清晰

  • 能對不同業務的資料進行分級管理、維護、監控、擴充套件等

  • 高併發場景下,垂直分庫一定程度的提升IO、資料庫連線數、降低單機硬體資源的瓶頸

    垂直分庫透過將表按業務分類,然後分佈在不同資料庫,並且可以將這些資料庫部署在不同伺服器上,從而達到多個伺服器共同分攤壓力的效果,但是依然沒有解決單表資料量過大的問題。

水平分庫

經過垂直分庫後,資料庫效能問題得到一定程度的解決,但是隨著業務量的增長,PRODUCT_DB(商品庫)單庫儲存資料已經超出預估。粗略估計,目前有8w店鋪,每個店鋪平均150個不同規格的商品,再算上增長,那商品數量得往1500w+上預估,並且PRODUCT_DB(商品庫)屬於訪問非常頻繁的資源,單臺伺服器已經無法支撐。此時該如何最佳化?

再次分庫?但是從業務角度分析,目前情況已經無法再次垂直分庫。

嘗試水平分庫,將店鋪ID為單數的和店鋪ID為雙數的商品資訊分別放在兩個庫中。

在這裡插入圖片描述
也就是說,要操作某條資料,先分析這條資料所屬的店鋪ID。如果店鋪ID為雙數,將此操作對映至RRODUCT_DB1(商品庫1);如果店鋪ID為單數,將操作對映至RRODUCT_DB2(商品庫2)。此操作要訪問資料庫名稱的表示式為RRODUCT_DB[店鋪ID%2 + 1] 。

水平分庫是把同一個表的資料按一定規則拆到不同的資料庫中,每個庫可以放在不同的伺服器上。

垂直分庫是把不同表拆到不同資料庫中,它是對資料行的拆分,不影響表結構

它帶來的提升是:

  • 解決了單庫大資料,高併發的效能瓶頸。
  • 提高了系統的穩定性及可用性。

穩定性體現在IO衝突減少,鎖定減少,可用性指某個庫出問題,部分可用`

當一個應用難以再細粒度的垂直切分,或切分後資料量行數巨大,存在單庫讀寫、儲存效能瓶頸,這時候就需要進行水平分庫了,經過水平切分的最佳化,往往能解決單庫儲存量及效能瓶頸。但由於同一個表被分配在不同的資料庫,需要額外進行資料操作的路由工作,因此大大提升了系統複雜度。

水平分表

按照水平分庫的思路對他把PRODUCT_DB_X(商品庫)內的表也可以進行水平拆分,其目的也是為解決單表資料量大的問題,如下圖:
在這裡插入圖片描述
與水平分庫的思路類似,不過這次操作的目標是表,商品資訊及商品描述被分成了兩套表。如果商品ID為雙數,將此操作對映至商品資訊1表;如果商品ID為單數,將操作對映至商品資訊2表。此操作要訪問表名稱的表示式為商品資訊[商品ID%2 + 1] 。

水平分表是在同一個資料庫內,把同一個表的資料按一定規則拆到多個表中。

它帶來的提升是:

  • 最佳化單一表資料量過大而產生的效能問題

  • 避免IO爭搶並減少鎖表的機率

    庫內的水平分表,解決了單一表資料量過大的問題,分出來的小表中只包含一部分資料,從而使得單個表的資料量變小,提高檢索效能。

總結

垂直分表:可以把一個寬表的欄位按訪問頻次、是否是大欄位的原則拆分為多個表,這樣既能使業務清晰,還能提升部分效能。拆分後,儘量從業務角度避免聯查,否則效能方面將得不償失。

垂直分庫:可以把多個表按業務耦合鬆緊歸類,分別存放在不同的庫,這些庫可以分佈在不同伺服器,從而使訪問壓力被多伺服器負載,大大提升效能,同時能提高整體架構的業務清晰度,不同的業務庫可根據自身情況定製最佳化方案。但是它需要解決跨庫帶來的所有複雜問題。

水平分庫:可以把一個表的資料(按資料行)分到多個不同的庫,每個庫只有這個表的部分資料,這些庫可以分佈在不同伺服器,從而使訪問壓力被多伺服器負載,大大提升效能。它不僅需要解決跨庫帶來的所有複雜問題,還要解決資料路由的問題(資料路由問題後邊介紹)。

水平分表:可以把一個表的資料(按資料行)分到多個同一個資料庫的多張表中,每個表只有這個表的部分資料,這樣做能小幅提升效能,它僅僅作為水平分庫的一個補充最佳化。

一般來說,在系統設計階段就應該根據業務耦合鬆緊來確定垂直分庫,垂直分表方案,在資料量及訪問壓力不是特別大的情況,首先考慮快取、讀寫分離、索引技術等方案。若資料量極大,且持續增長,再考慮水平分庫水平分表方案。

Sharding-jdbc影片分享

分庫分表生產方案Sharding-jdbc影片分享






About Me

........................................................................................................................

● 本文作者:小麥苗,部分內容整理自網路,若有侵權請聯絡小麥苗刪除

● 本文在itpub、部落格園、CSDN和個人微 信公眾號( xiaomaimiaolhr)上有同步更新

● 本文itpub地址: http://blog.itpub.net/26736162

● 本文部落格園地址: http://www.cnblogs.com/lhrbest

● 本文CSDN地址: https://blog.csdn.net/lihuarongaini

● 本文pdf版、個人簡介及小麥苗雲盤地址: http://blog.itpub.net/26736162/viewspace-1624453/

● 資料庫筆試面試題庫及解答: http://blog.itpub.net/26736162/viewspace-2134706/

● DBA寶典今日頭條號地址:

........................................................................................................................

● QQ群號: 230161599(滿) 、618766405

● 微 信群:可加我微 信,我拉大家進群,非誠勿擾

● 聯絡我請加QQ好友 646634621 ,註明新增緣由

● 於 2019-07-01 06:00 ~ 2019-07-31 24:00 在西安完成

● 最新修改時間:2019-07-01 06:00 ~ 2019-07-31 24:00

● 文章內容來源於小麥苗的學習筆記,部分整理自網路,若有侵權或不當之處還請諒解

● 版權所有,歡迎分享本文,轉載請保留出處

........................................................................................................................

小麥苗的微店

小麥苗出版的資料庫類叢書http://blog.itpub.net/26736162/viewspace-2142121/

小麥苗OCP、OCM、高可用網路班http://blog.itpub.net/26736162/viewspace-2148098/

小麥苗騰訊課堂主頁https://lhr.ke.qq.com/

........................................................................................................................

使用 微 信客戶端掃描下面的二維碼來關注小麥苗的微 信公眾號( xiaomaimiaolhr)及QQ群(DBA寶典)、新增小麥苗微 信, 學習最實用的資料庫技術。

........................................................................................................................

歡迎與我聯絡

 

 



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26736162/viewspace-2651606/,如需轉載,請註明出處,否則將追究法律責任。

相關文章