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個數據庫.畢竟都是冷數據,訪問量很小.
能分還要能合.好比: