Mycat 分库分表配置详解:垂直/水平 分库/分表 一网打尽
Mycat 作为经典的数据库中间件,其核心功能就是对数据进行分片,而分片又常被划分为四种典型策略:垂直分库、水平分库、垂直分表、水平分表 。很多人在配置时对 rule 的写法、分片键的位置、多表规则能否复用等问题感到困惑。本文将用最清晰的配置示例和总结,一次性讲透这四种方式,并给出可直接套用的模板。
本文基于 Mycat 1.6+ 版本,配置均以
schema.xml和rule.xml为例。
0. 公共基础结构
在开始之前,先给出 Mycat 的 schema.xml 基本骨架,后面的所有配置都将基于此展开:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 逻辑库 -->
<schema name="MYCAT_DB" checkSQLschema="false" sqlMaxLimit="100">
<!-- 各种 <table> 配置放在这里 -->
</schema>
<!-- 数据节点:逻辑库 → 物理库 -->
<dataNode name="dn1" dataHost="host1" database="db1" />
<dataNode name="dn2" dataHost="host2" database="db2" />
<dataNode name="dn3" dataHost="host3" database="db3" />
<!-- 物理主机配置(示例仅写一个,实际可扩展) -->
<dataHost name="host1" maxCon="1000" minCon="10"
balance="0" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="123456"/>
</dataHost>
<!-- host2、host3 配置类似,略 -->
</mycat:schema>
rule.xml 的基本结构:
<?xml version="1.0"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
<!-- 这里放 tableRule 和 function -->
</mycat:rule>
1. 垂直分库(按业务分库,整表迁移)
特点 :不同业务表放在不同的数据库节点上,不涉及数据行的拆分,不需要配置分片规则(rule)。
场景:用户表、订单表、商品表分别存入不同的物理库。
schema.xml 配置
<schema name="MYCAT_DB" checkSQLschema="false" sqlMaxLimit="100">
<!-- 用户模块:放在 dn1 -->
<table name="t_user" dataNode="dn1" primaryKey="id"/>
<table name="t_user_addr" dataNode="dn1" primaryKey="id"/>
<!-- 订单模块:放在 dn2 -->
<table name="t_order" dataNode="dn2" primaryKey="order_id"/>
<table name="t_order_item" dataNode="dn2" primaryKey="id"/>
<!-- 商品模块:放在 dn3 -->
<table name="t_goods" dataNode="dn3" primaryKey="id"/>
</schema>
- 每一张表都完整存在于某个库中,Mycat 仅负责将 SQL 路由到对应的数据节点。
- 无 rule,因为数据没有被打散。
2. 水平分库(一张表的数据散列到多个库)
特点:同一张表的结构在多个库中都有,数据按分片键分散存储,必须配置分片规则(rule) 。
与水平分表的核心区别:水平分库的 <table> 里是多个 dataNode,且没有 subTables。
为避免和单库分表混淆,这里用两张关联表(订单表、订单项表)同时进行水平分库的示例。
schema.xml
<schema name="MYCAT_DB" checkSQLschema="false" sqlMaxLimit="100">
<!-- 两张表都水平分库到 dn1,dn2,dn3,共用同一个 rule -->
<table name="t_order" dataNode="dn1,dn2,dn3" rule="mod_rule" primaryKey="id"/>
<table name="t_order_item" dataNode="dn1,dn2,dn3" rule="mod_rule" primaryKey="id"/>
</schema>
rule.xml(取模分片)
<tableRule name="mod_rule">
<rule>
<columns>id</columns> <!-- 分片键 -->
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">3</property> <!-- 分为3个库 -->
</function>
说明
- 分片键
id写在 rule 的<columns>里,物理表中必须包含该字段。 - 多张表共用
mod_rule,Mycat 会根据每张表的分片键独立路由到对应库,完全不需要重复定义 rule。 - 因为两张表的分片规则一致,关联查询(如
JOIN ON id)时,数据大概率落在同一分片节点,避免跨库 JOIN。
3. 垂直分表(单库内大表拆小表:主表 + 扩展表)
特点 :在同一个数据库中,将一张表的字段拆分成多张表(如常用字段与大字段分离)。
核心真相:垂直分表是纯人工手动拆表,Mycat 不参与任何字段拆分或合并逻辑,它只负责声明表在哪个节点!
场景 :商品表拆分为 t_goods_main(高频小字段)和 t_goods_ext(大文本字段)。
物理建表(自己在 MySQL 中执行)
-- 主表:常用字段
CREATE TABLE t_goods_main(
id INT PRIMARY KEY,
goods_name VARCHAR(50),
price DECIMAL(10,2),
stock INT
);
-- 扩展表:大字段、冷门字段
CREATE TABLE t_goods_ext(
goods_id INT PRIMARY KEY, -- 与主表 id 一一对应
goods_detail LONGTEXT,
goods_img TEXT
);
schema.xml 配置
<schema name="MYCAT_DB" checkSQLschema="false" sqlMaxLimit="100">
<!-- 两张表都在同一个数据节点 dn1,没有 rule -->
<table name="t_goods_main" dataNode="dn1" primaryKey="id"/>
<table name="t_goods_ext" dataNode="dn1" primaryKey="goods_id"/>
</schema>
Mycat 这段配置只做了三件事:
- 告诉 Mycat 这两张表都在
dn1库中; - 声明各自的主键字段;
- 不控制任何字段,字段如何拆分完全由建表时决定。
正因为是"纯手动",所以垂直分表不需要任何 rule,增删改查也需要自己写关联 SQL(见下文第 7 节)。
4. 水平分表(单库内,逻辑表拆多张物理子表)
特点 :在同一个数据库中,一张逻辑表对应多张物理子表(如 t_log_0, t_log_1 ......),数据按分片键自动路由到对应的子表。必须配置 rule。
schema.xml
<schema name="MYCAT_DB" checkSQLschema="false" sqlMaxLimit="100">
<!-- 单库 dn1,分成 4 张子表:t_log_0 ~ t_log_3 -->
<table name="t_log" subTables="t_log_$0-3" dataNode="dn1" rule="log_rule" primaryKey="id"/>
</schema>
rule.xml(单库内取模)
<tableRule name="log_rule">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">4</property> <!-- 单库分4张表 -->
</function>
注意 :subTables="t_log_$0-3" 表示物理表名称为 t_log_0、t_log_1、t_log_2、t_log_3,Mycat 会根据分片算法自动计算目标子表。
5. 组合:水平分库 + 水平分表(常用)
将数据先分到多个库,每个库内再分多张子表。例如订单表分 2 个库,每个库内分 2 张子表,总共 4 个分片。
schema.xml
<table name="t_order" dataNode="dn1,dn2" subTables="t_order_$0-1" rule="order_rule" primaryKey="id"/>
rule.xml
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">4</property> <!-- 2库 × 2表 = 4 -->
</function>
6. 多表分片规则:复用与独立性
6.1 同分片规则 → 复用同一个 rule(推荐)
如果多张关联表都按照相同的分片键和算法分片,只需定义一个 rule,多次引用即可。
<!-- schema.xml -->
<table name="t_order" dataNode="dn1,dn2,dn3" rule="mod_rule" primaryKey="id"/>
<table name="t_order_item" dataNode="dn1,dn2,dn3" rule="mod_rule" primaryKey="id"/>
rule.xml 中只写一份 mod_rule,全局复用。
6.2 不同表分片规则可以不一致(允许)
对于毫无关联的业务表,可以使用完全不同的分片键和分片算法。
<!-- schema.xml -->
<table name="t_order" dataNode="dn1,dn2,dn3" rule="mod_rule" primaryKey="order_id"/>
<table name="t_pay" dataNode="dn1,dn2,dn3" rule="range_rule" primaryKey="user_id"/>
rule.xml 定义两套独立的规则:
<!-- 规则1:取模 -->
<tableRule name="mod_rule">
<rule>
<columns>order_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<!-- 规则2:范围分片 -->
<tableRule name="range_rule">
<rule>
<columns>user_id</columns>
<algorithm>range-hash</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">3</property>
</function>
<function name="range-hash" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">partition-hash-long.txt</property>
</function>
6.3 重要原则:同业务关联表必须统一分片字段
- 订单、订单项、物流、支付等需要关联查询的表,必须使用相同的分片键和分片规则,否则 JOIN 将跨库,性能极差甚至报错。
- 毫无关联的独立表(如用户表按 uid 分,日志表按 id 分)允许不同。
正确示例 :订单表和订单详情表都按 user_id 分片,且共用 user_rule,联表时数据在同一个库,性能高。
<!-- 两张表都使用同一个 user_rule,分片键都是 user_id -->
<table name="t_order" dataNode="dn1,dn2" rule="user_rule" primaryKey="id"/>
<table name="t_order_item" dataNode="dn1,dn2" rule="user_rule" primaryKey="id"/>
同时,物理表中必须都包含 user_id 字段,否则 Mycat 无法正确路由。
CREATE TABLE t_order (id INT, user_id INT, ...);
CREATE TABLE t_order_item (id INT, user_id INT, order_id INT, ...);
7. 垂直分表的插入与查询注意事项
垂直分表后,插入数据必须使用两条 INSERT 语句,不能一条 SQL 同时插入两张表。
START TRANSACTION;
INSERT INTO t_goods_main(id, goods_name, price) VALUES(1001, '手机', 2999);
INSERT INTO t_goods_ext(goods_id, goods_detail) VALUES(1001, '超长详情...');
COMMIT;
查询时只需要一条联表 SQL(因为两表在同一库,性能很好):
SELECT m.*, e.goods_detail
FROM t_goods_main m
LEFT JOIN t_goods_ext e ON m.id = e.goods_id
WHERE m.id = 1001;
Mycat 在此处只负责将 SQL 路由到正确节点,不做任何自动拆分或合并字段的操作。
8. 四种策略本质对比总结
| 策略 | 核心操作 | 是否拆分数据 | 是否需要 rule | 分片键位置 | Mycat 参与度 |
|---|---|---|---|---|---|
| 垂直分库 | 不同表存不同库 | 否 | 不需要 | 无 | 按配置路由表到指定节点 |
| 水平分库 | 同一张表数据散列到多个库 | 是 | 必须 | rule 的 <columns> | 自动分片路由 |
| 垂直分表 | 同库内一张表拆成多张表(字段拆分) | 否 | 不需要 | 无 | 只登记表位置,拆分人为完成 |
| 水平分表 | 同库内逻辑表拆成多张物理子表 | 是 | 必须 | rule 的 <columns> | 自动路由到正确的子表 |