MySQL訂單分庫分表多維度查詢

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

MySQL分庫分表,通常只能按照一個維度進行查詢.
以訂單表爲例, 按照用戶ID mod 64 分紅 64個數據庫.
按照用戶的維度查詢很快,由於最終的查詢落在一臺服務器上.
可是若是按照商戶的維度查詢,則代價很是高.
須要查詢所有64臺服務器.
在分頁的狀況下,更加惡化.
好比某個商戶查詢第10頁的數據(按照訂單的建立時間).須要在每臺數據庫服務器上查詢前100條數據,程序收到 64*100 條數據,而後按照訂單的建立時間排序,截取排名90-100號的10條記錄返回,而後拋棄其他的6390條記錄.若是查詢的是第100頁,第1000頁,則對數據庫IO,網絡,中間件CPU,都是不小的壓力.

分庫分表以後,爲了應對多維度查詢,不少狀況下會引入冗餘.
好比兩個集羣,一個按照用戶ID分庫分表,另一個按照商戶ID分庫分表.
這樣多維度查詢的時候,各查各的.
可是有幾個問題,同樣很差解決.
好比,
每擴展一個維度,就須要引入一個集羣.
集羣間的數據,如何保證一致性.
冗餘佔用大量磁盤空間.

從朋友那裏看到的訂單表結構.作冗餘會佔用大量的磁盤空間.
create table TS_ORDER  
(  
  ORDER_ID        NUMBER(8) not null,  
  SN              VARCHAR2(50),  
  MEMBER_ID       NUMBER(8),  
  STATUS          NUMBER(2),  
  PAY_STATUS      NUMBER(2),  
  SHIP_STATUS     NUMBER(2),  
  SHIPPING_ID     NUMBER(8),  
  SHIPPING_TYPE   VARCHAR2(255),  
  SHIPPING_AREA   VARCHAR2(255),  
  PAYMENT_ID      NUMBER(8),  
  PAYMENT_NAME    VARCHAR2(50),  
  PAYMENT_TYPE    VARCHAR2(50),  
  PAYMONEY        NUMBER(20,2),  
  CREATE_TIME     NUMBER(20) not null,  
  SHIP_NAME       VARCHAR2(255),  
  SHIP_ADDR       VARCHAR2(255),  
  SHIP_ZIP        VARCHAR2(20),  
  SHIP_EMAIL      VARCHAR2(50),  
  SHIP_MOBILE     VARCHAR2(50),  
  SHIP_TEL        VARCHAR2(50),  
  SHIP_DAY        VARCHAR2(255),  
  SHIP_TIME       VARCHAR2(255),  
  IS_PROTECT      VARCHAR2(1),  
  PROTECT_PRICE   NUMBER(20,2),  
  GOODS_AMOUNT    NUMBER(20,2),  
  SHIPPING_AMOUNT NUMBER(20,2),  
  ORDER_AMOUNT    NUMBER(20,2),  
  WEIGHT          NUMBER(20,2),  
  GOODS_NUM       NUMBER(8),  
  GAINEDPOINT     NUMBER(11) default 0,  
  CONSUMEPOINT    NUMBER(11) default 0,  
  DISABLED        VARCHAR2(1),  
  DISCOUNT        NUMBER(20,2),  
  IMPORTED        NUMBER(2) default 0,  
  PIMPORTED       NUMBER(2) default 0,  
  COMPLETE_TIME   NUMBER(11) default 0,  
  CANCEL_REASON   VARCHAR2(255),  
  SIGNING_TIME    NUMBER(11),  
  THE_SIGN        VARCHAR2(255),  
  ALLOCATION_TIME NUMBER(11),  
  SHIP_PROVINCEID NUMBER(11),  
  SHIP_CITYID     NUMBER(11),  
  SHIP_REGIONID   NUMBER(11),  
  SALE_CMPL       NUMBER(2),  
  SALE_CMPL_TIME  NUMBER(11),  
  DEPOTID         NUMBER(11),  
  ADMIN_REMARK    VARCHAR2(1000),  
  COMPANY_CODE    VARCHAR2(32),  
  PARENT_SN       VARCHAR2(50),  
  REMARK          VARCHAR2(100),  
  GOODS           CLOB,  
  ORIGINAL_AMOUNT NUMBER(20,2),  
  IS_ONLINE       CHAR(1),  
  IS_COMMENTED    CHAR(1) default 0,  
  ORDER_FLAG      CHAR(1) default 1  
)  

  


能夠試試用表代替索引的方法.
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個數據庫.畢竟都是冷數據,訪問量很小.
能分還要能合.好比:
相關文章
相關標籤/搜索