ER(Entity-Relationship)分片是 MyCat 針對「父子表強關聯」業務設計的分片策略。核心目標:保證子表數據與其關聯的父表數據落在同一分片,從而避免跨分片 JOIN,提升查詢性能與事務一致性。本文深度剖析兩大典型場景:
<table name="ORDER1" rule="mod-long">
<childTable name="ORDER_DETAIL" joinKey="ORDER_ID" parentKey="ID" primaryKey="ID"/>
</table>
joinKey:子表關聯字段(外鍵)parentKey:父表關聯字段(主鍵)primaryKey:子表主鍵<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
CREATE TABLE ORDER1(
ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
SN VARCHAR(64), CREATE_TIME DATETIME
);
CREATE TABLE ORDER_DETAIL(
ID INT AUTO_INCREMENT PRIMARY KEY,
ORDER_ID INT, ORD_STATUS CHAR(1),
ADDRESS VARCHAR(128), CREATE_TIME DATETIME,
CONSTRAINT FK_ORDID FOREIGN KEY (ORDER_ID) REFERENCES ORDER1(ID)
);
-- 北京訂單 ID=1
INSERT INTO ORDER1(ID,SN,CREATE_TIME) VALUES(1,'BJ0001',NOW());
INSERT INTO ORDER_DETAIL(ID,ORDER_ID,ORD_STATUS,ADDRESS,CREATE_TIME)
VALUES(1,1,'1','test data of ORDER1(ID=1,BJ001)',NOW());
-- 上海訂單 ID=3
INSERT INTO ORDER1(ID,SN,CREATE_TIME) VALUES(3,'SHH001',NOW());
INSERT INTO ORDER_DETAIL(ID,ORDER_ID,ORD_STATUS,ADDRESS,CREATE_TIME)
VALUES(3,3,'1','test data of ORDER1(ID=3,SHH001)',NOW());
-- 廣州訂單 ID=4
INSERT INTO ORDER1(ID,SN,CREATE_TIME) VALUES(4,'GZH004',NOW());
INSERT INTO ORDER_DETAIL(ID,ORDER_ID,ORD_STATUS,ADDRESS,CREATE_TIME)
VALUES(4,4,'1','test data of ORDER1(ID=4,GZH004)',NOW());
-- 武漢訂單 ID=6,但 ORDER_DETAIL 故意寫 ORDER_ID=4(測試是否跟隨父表)
INSERT INTO ORDER1(ID,SN,CREATE_TIME) VALUES(6,'WUHAN006',NOW());
INSERT INTO ORDER_DETAIL(ID,ORDER_ID,ORD_STATUS,ADDRESS,CREATE_TIME)
VALUES(6,4,'1','test data of ORDER1(ID=6,WUHAN005)',NOW());
-- dn21 (port 3327)
ORDER1: ID=1(BJ), ID=4(GZ)
ORDER_DETAIL: ID=1(ORDER_ID=1), ID=4(ORDER_ID=4), ID=6(ORDER_ID=4)
-- dn22 (port 3317)
ORDER1: ID=3(SH), ID=6(WUHAN)
ORDER_DETAIL: ID=3(ORDER_ID=3)
關鍵結論:ORDER_DETAIL 中 ORDER_ID=4 的三條記錄全部跟隨父表 ORDER1.ID=4 落在 dn21,即使最後一條對應的父表 ID=6 在 dn22。子表分片完全由父表主鍵決定,與子表自身主鍵無關。
關鍵日誌片段:
22:57:26.988 INSERT INTO ORDER1(ID,SN,CREATE_TIME) VALUES(3,'SHH001',NOW())
22:57:26.989 route={ 1 -> dn21{INSERT ...} } -- 父表路由到 dn21
22:57:26.999 release connection ... attachment=dn21{...}
23:09:02.690 INSERT INTO ORDER_DETAIL ... ORDER_ID=3
23:09:02.692 route={ VALUES ... } -- 直接複用父表路由 dn21
23:09:02.693 release connection ... attachment=dn21{...}
子表寫入無額外路由計算,直接復用父表已確定的分片,性能極高。
mysql> EXPLAIN SELECT t1.*,t2.* FROM ORDER1 t1,ORDER_DETAIL t2
WHERE t2.ORD_STATUS='1' AND t2.ID=1 AND t1.ID=t2.ORDER_ID;
+-----------+--------------------------------------------------+
| DATA_NODE | SQL |
+-----------+--------------------------------------------------+
| dn21 | SELECT ... |
| dn22 | SELECT ... |
+-----------+--------------------------------------------------+
注意:雖然數據同分片,但 MyCat 目前無法自動推導 JOIN 只需單分片,仍廣播至所有節點。業務層若明確知道分片,建議加上分片字段條件(如 t1.ID=1)以觸發單點路由。
<function name="province-str-split" class="org.opencloudb.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-str-pro.txt</property>
<property name="type">1</property> <!-- 字符串模式 -->
<property name="defaultNode">0</property>
</function>
<tablerule name="tr-province">
<rule>
<columns>PROVINCE</columns>
<algorithm>province-str-split</algorithm>
</rule>
</tablerule>
beijing=0
shanghai=1
tianjing=2
<table name="ORDER2" rule="tr-province">
<childTable name="ORDER_DETAIL2" joinKey="ORDER_ID" parentKey="ID" primaryKey="ID"/>
</table>
插入子表時,MyCat 必須先查詢父表確定分片:
17:23:50.530 find root parent's node sql: select order2.id from order2 where order2.id=1
17:23:50.531 execute in datanode dn21
17:23:50.532 received rowResponse response,1 from ... dn21
17:23:50.732 found partition node for child table to insert dn21
17:23:50.733 route={ 1 -> dn21{INSERT INTO ORDER_DETAIL2 ...} }
額外開銷:每次子表寫入需額外發起一條「查父表主鍵」的 SQL,並在父表所在分片執行,確認分片後再寫入子表。
mysql> INSERT INTO ORDER_DETAIL2 ... VALUES(6,9,...);
ERROR 1064 (HY000): can't find (root) parent sharding node for sql:...
日誌顯示遍歷 dn21、dn22 均未查到 ORDER2.ID=9,最終拋錯。業務必須保證先寫父表、再寫子表,且兩者在同一事務外提交(見下文避坑)。
mysql> EXPLAIN SELECT t1.*,t2.* FROM ORDER2 t1,ORDER_DETAIL2 t2
WHERE t2.ORD_STATUS='1' AND t2.ID=6;
+-----------+--------------------------------------------------+
| DATA_NODE | SQL |
+-----------+--------------------------------------------------+
| dn21 | SELECT ... |
| dn22 | SELECT ... |
+-----------+--------------------------------------------------+
| 報錯現象 | 根因 | 解決方案 |
|---|---|---|
For input string: "2BJ0001" |
分片字段為字符串但 type=0 (Integer) |
映射函數設置 type=1 |
can't find (root) parent sharding node(同事務內寫父子表) |
事務未提交,子表查不到父表數據 | 父子表分別提交事務,或使用 autocommit=1 |
Table 'db3.order2' doesn't exist(實際表名 ORDER2) |
MySQL lower_case_table_names=1 導致大小寫不敏感,MyCat 路由 SQL 使用小寫 |
後端 MySQL 設置 lower_case_table_names=1 並重啟,或統一表名小寫 |
| 同錯誤 3,但表已存在 | MyCat 緩存舊元數據 | 重啟 MyCat 服務刷新元數據緩存 |
lower_case_table_names=1,避免路由 SQL 大小寫不匹配導致「表不存在」誤報。WHERE t1.ID=?),觸發單點路由;跨分片聚合查詢建議下推至異構引擎(ClickHouse/Druid)。