訂單分庫分表方案


MySQL分庫分表,一般只能按照一個維度進行查詢.
以訂單表為例, 按照用戶ID mod 64 分成 64個數據庫.
按照用戶的維度查詢很快,因為最終的查詢落在一台服務器上.
但是如果按照商戶的維度查詢,則代價非常高.
需要查詢全部64台服務器.
在分頁的情況下,更加惡化.
比如某個商戶查詢第10頁的數據(按照訂單的創建時間).需要在每台數據庫服務器上查詢前100條數據,程序收到 64*100 條數據,然后按照訂單的創建時間排序,截取排名90-100號的10條記錄返回,然后拋棄其余的6390條記錄.如果查詢的是第100頁,第1000頁,則對數據庫IO,網絡,中間件CPU,都是不小的壓力.

分庫分表之后,為了應對多維度查詢,很多情況下會引入冗余.
比如兩個集群,一個按照用戶ID分庫分表,另外一個按照商戶ID分庫分表.
這樣多維度查詢的時候,各查各的.
但是有幾個問題,一樣不好解決.
比如,
每擴展一個維度,就需要引入一個集群.
集群間的數據,如何保證一致性.
冗余占用大量磁盤空間.

從朋友那里看到的訂單表結構.做冗余會占用大量的磁盤空間.

  1. create table TS_ORDER  
  2. (  
  3.   ORDER_ID        NUMBER(8) not null,  
  4.   SN              VARCHAR2(50),  
  5.   MEMBER_ID       NUMBER(8),  
  6.   STATUS          NUMBER(2),  
  7.   PAY_STATUS      NUMBER(2),  
  8.   SHIP_STATUS     NUMBER(2),  
  9.   SHIPPING_ID     NUMBER(8),  
  10.   SHIPPING_TYPE   VARCHAR2(255),  
  11.   SHIPPING_AREA   VARCHAR2(255),  
  12.   PAYMENT_ID      NUMBER(8),  
  13.   PAYMENT_NAME    VARCHAR2(50),  
  14.   PAYMENT_TYPE    VARCHAR2(50),  
  15.   PAYMONEY        NUMBER(20,2),  
  16.   CREATE_TIME     NUMBER(20) not null,  
  17.   SHIP_NAME       VARCHAR2(255),  
  18.   SHIP_ADDR       VARCHAR2(255),  
  19.   SHIP_ZIP        VARCHAR2(20),  
  20.   SHIP_EMAIL      VARCHAR2(50),  
  21.   SHIP_MOBILE     VARCHAR2(50),  
  22.   SHIP_TEL        VARCHAR2(50),  
  23.   SHIP_DAY        VARCHAR2(255),  
  24.   SHIP_TIME       VARCHAR2(255),  
  25.   IS_PROTECT      VARCHAR2(1),  
  26.   PROTECT_PRICE   NUMBER(20,2),  
  27.   GOODS_AMOUNT    NUMBER(20,2),  
  28.   SHIPPING_AMOUNT NUMBER(20,2),  
  29.   ORDER_AMOUNT    NUMBER(20,2),  
  30.   WEIGHT          NUMBER(20,2),  
  31.   GOODS_NUM       NUMBER(8),  
  32.   GAINEDPOINT     NUMBER(11) default 0,  
  33.   CONSUMEPOINT    NUMBER(11) default 0,  
  34.   DISABLED        VARCHAR2(1),  
  35.   DISCOUNT        NUMBER(20,2),  
  36.   IMPORTED        NUMBER(2) default 0,  
  37.   PIMPORTED       NUMBER(2) default 0,  
  38.   COMPLETE_TIME   NUMBER(11) default 0,  
  39.   CANCEL_REASON   VARCHAR2(255),  
  40.   SIGNING_TIME    NUMBER(11),  
  41.   THE_SIGN        VARCHAR2(255),  
  42.   ALLOCATION_TIME NUMBER(11),  
  43.   SHIP_PROVINCEID NUMBER(11),  
  44.   SHIP_CITYID     NUMBER(11),  
  45.   SHIP_REGIONID   NUMBER(11),  
  46.   SALE_CMPL       NUMBER(2),  
  47.   SALE_CMPL_TIME  NUMBER(11),  
  48.   DEPOTID         NUMBER(11),  
  49.   ADMIN_REMARK    VARCHAR2(1000),  
  50.   COMPANY_CODE    VARCHAR2(32),  
  51.   PARENT_SN       VARCHAR2(50),  
  52.   REMARK          VARCHAR2(100),  
  53.   GOODS           CLOB,  
  54.   ORIGINAL_AMOUNT NUMBER(20,2),  
  55.   IS_ONLINE       CHAR(1),  
  56.   IS_COMMENTED    CHAR(1) default 0,  
  57.   ORDER_FLAG      CHAR(1) default 1  
  58. )  



可以試試用表代替索引的方法.
1.分庫分表
2.最終一致性
3.用表代替索引的功能



首先,還是基於分庫分表.訂單表按照用戶ID mod 64 分到不同的服務器上(按照查詢最多的維度分)。

數據庫服務器1 的數據庫名稱為 db_1
數據庫服務器2 的數據庫名稱為 db_2
...

以db_1為例,創建如下表
1.訂單表 
TS_ORDER_1  分區表,每個月一個分區.

2.事務表
create table tran_log_1(
    tran_id bigint primary key,
    param varchar(2000)
);
分區表,每個月一個分區.

3.消息表
create table msg_log_1(
    tran_id bigint,
    shardKey varchar(20) not null,
    primary key(tran_id,shardKey)
);
分區表,每個月一個分區.

4.維度索引表
create table shard_shop_1(
    id bigint primary key auto_increment,
    shopid int,
    ts timestamp,
    state int,
    dbid int,
    orderid bigint,
    index(shopid,ts,state)
);
分區表,每個月一個分區.

關於使用事務表,消息表實現分庫分表最終一致性請參考
http://blog.itpub.net/29254281/viewspace-1819422/

關於集群主鍵生成服務請參考
http://blog.itpub.net/29254281/viewspace-1811711/

訂單創建的流程
Web服務器接收到用戶訂單,首先通過RPC獲取一個事務ID(tran_id).
用事務ID mod 64 找到數據庫服務器,
將事務ID,參數寫入tran_log 表,
然后將事務ID,參數寫入消息隊列.
如果寫入消息隊列成功,則提交事務.否則回滾事務.
此時就可以返回用戶界面.

后端處理服務收到消息隊列的信息,首先查詢tran_log 表,是否存在這個事務ID,如果不存在則不予處理.
然后將隊列的消息,分為兩個維度分別處理,一個是用戶維度,一個是商戶維度.
作為用戶維度,
先根據用戶ID mod 64 找到最終落地的數據庫,查詢那個數據庫的消息表msg_log,在用戶維度,是否存在這個事務ID,如果存在,則不予處理.
(select count(*) from msg_log_XX where shardKey='訂單創建:用戶維度' and tran_id=?)
如果不存在,則開啟一個事務
插入訂單表,我覺得可以用tran_id直接作為訂單的ID,
並且插入消息表 insert msg_log_XX(tran_id,shardKey) values(?,'訂單創建:用戶維度');
提交事務,commit.

作為商戶維度,
則根據商戶ID mod 64 找到最終的數據庫,和用戶維度的數據庫,可能不是同一台服務器.
同樣,也是先查詢落地數據庫的消息表,
(select count(*) from msg_log_XXX where shardKey='訂單創建:商戶維度' and tran_id=?)
如果不存在記錄,則開啟事務,
插入維度索引表,
insert into shard_shop_XXX(shopid,ts,state,dbid,orderid) values(......)
shopid,ts,state 商戶ID,訂單時間,訂單狀態都是根據訂單的原始信息.
dbid 指的是 根據用戶維度(主維度),訂單數據所在的數據庫ID,
orderid 指的是 在用戶維度(主維度),訂單表的主鍵.

插入消息表,insert msg_log_XX(tran_id,shardKey) values(?,'訂單創建:商戶維度');
最后提交.


這樣,作為商戶維度查詢的時候,先根據商戶的ID mod 64 找到 維度索引表,獲取該商戶的訂單信息
select * from shard_shop_1 where shopid=? and state=2 order by ts limit 300,10;
獲取的信息可能如下

可以看到,符合條件的訂單信息,分別來自 服務器1,2,16,32,64
獲取了這部分信息,就可以直接去這些服務器上取數據,並且是主鍵查詢,速度很快.


每隔一段時間,由后台程序,查看 tran_log和msg_log,如果發現有缺失的數據,則進行事務補償.

擴展的時候,則新增維度索引表即可.

因為所有的表,都是按月的分區表,可以將過去的冷數據,在一個服務器集中存放,這個實例就同時存放64個數據庫.畢竟都是冷數據,訪問量很小.

 

轉載:  http://blog.itpub.net/29254281/viewspace-2086198/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM