第 10 章 Mycat 分片规则
10.1 分片规则概述
在数据切分处理中,特别是水平切分中,中间件最终要的两个处理过程就是数据的切分、数据的聚合。选择合适的切分规则,至关重要,因为它决定了后续数据聚合的难易程度,甚至可以避免跨库的数据聚合处理。
前面讲了数据切分中重要的几条原则,其中有几条是数据冗余,表分组(Table Group),这都是业务上规避跨库join的很好的方式,但不是所有的业务场景都适合这样的规则,因此本章将讲述如何选择合适的切分规则。
10.2 Mycat 全局表
如果你的业务中有些数据类似于数据字典,比如配置文件的配置,常用业务的配置或者数据量不大很少变动的表,这些表往往不是特别大,而且大部分的业务场景都会用到,那么这种表适合于 Mycat 全局表,无须对数据进行切分,只要在所有的分片上保存一份数据即可,Mycat 在 Join 操作中,业务表与全局表进行 Join 聚合会优先选择相同分片内的全局表 join,避免跨库 Join,在进行数据插入操作时,mycat 将把数据分发到全局表对应的所有分片执行,在进行数据读取时候将会随机获取一个节点读取数据。
目前 Mycat 没有做全局表的数据一致性检查,后续版本 1.4 之后可能会提供全局表一致性检查,检查每个分片的数据一致性。
全局表的配置如下
<table name="t_area" primaryKey="id" type="global" dataNode="dn1,dn2" />
10.3 ER 分片表
有一类业务,例如订单(order)跟订单明细(order_detail),明细表会依赖于订单,也就是说会存在表的主从关系,这类似业务的切分可以抽象出合适的切分规则,比如根据用户 ID 切分,其他相关的表都依赖于用户 ID,再或者根据订单 ID 切分,总之部分业务总会可以抽象出父子关系的表。这类表适用于 ER 分片表,子表的记录与所关联的父表记录存放在同一个数据分片上,避免数据 Join 跨库操作。
以 order 与 order_detail 例子为例,schema.xml 中定义如下的分片配置,order,order_detail 根据 order_id进行数据切分,保证相同 order_id 的数据分到同一个分片上,在进行数据插入操作时,Mycat 会获取 order 所在的分片,然后将 order_detail 也插入到 order 所在的分片。
<table name="order" dataNode="dn$1-32" rule="mod-long">
<childTable name="order_detail" primaryKey="id" joinKey="order_id" parentKey="order_id" />
</table>
10.4 多对多关联
有一类业务场景是 “主表 A+关系表+主表 B”,举例来说就是商户会员+订单+商户,对应这类业务,如何切分?
从会员的角度,如果需要查询会员购买的订单,那按照会员进行切分即可,但是如果要查询商户当天售出的订单,那又需要按照商户做切分,可是如果既要按照会员又要按照商户切分,几乎是无法实现,这类业务如何选择切分规则非常难。目前还暂时无法很好支持这种模式下的 3 个表之间的关联。目前总的原则是需要从业务角度来看,关系表更偏向哪个表,即“A 的关系”还是“B 的关系”,来决定关系表跟从那个方向存储,未来 Mycat版本中将考虑将中间表进行双向复制,以实现从 A-关系表 以及 B-关系表的双向关联查询如下图所示:
10.4.1 主键分片 vs 非主键分片
当你没人任何字段可以作为分片字段的时候,主键分片就是唯一选择,其优点是按照主键的查询最快,当采用自动增长的序列号作为主键时,还能比较均匀的将数据分片在不同的节点上。
若有某个合适的业务字段比较合适作为分片字段,则建议采用此业务字段分片,选择分片字段的条件如下:
- 尽可能的比较均匀分布数据到各个节点上;
- 该业务字段是最频繁的或者最重要的查询条件。
常见的除了主键之外的其他可能分片字段有“订单创建时间”, “店铺类别”或“所在省”等。当你找到某个合适的业务字段作为分片字段以后,不必纠结于“牺牲了按主键查询记录的性能”,因为在这种情况下,MyCAT 提供了“主键到分片”的内存缓存机制,热点数据按照主键查询,丝毫不损失性能。
<table name="t_user" primaryKey="user_id" dataNode="dn$1-32" rule="mod-long">
<childTable name="t_user_detail" primaryKey="id" joinKey="user_id" parentKey="user_id" />
</table>
对于非主键分片的 table,填写属性 primaryKey,此时 MyCAT 会将你根据主键查询的 SQL 语句的第一次执行结果进行分析,确定该 Table 的某个主键在什么分片上,并进行主键到分片 ID 的缓存。第二次或后续查询mycat 会优先从缓存中查询是否有 id–>node 即主键到分片的映射,如果有直接查询,通过此种方法提高了非主键分片的查询性能。
本节主要讲了如何去分片,如何选择合适分片的规则,总之尽量规避跨库 Join 是一条最重要的原则,下一节将介绍 Mycat 目前已有的分片规则,每种规则都有特定的场景,分析每种规则去选择合适的应用到项目中。
10.5 Mycat 常用的分片规则
10.5.1 分片枚举
通过在配置文件中配置可能的枚举 id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则,配置如下:
<tableRule name="sharding-by-intfile">
<rule>
<columns>user_id</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
<property name="type">0</property>
<property name="defaultNode">0</property>
</function>
partition-hash-int.txt 配置:
10000=0
10010=1
DEFAULT_NODE=1
上面 columns 标识将要分片的表字段,algorithm 分片函数,
其中分片函数配置中,mapFile 标识配置文件名称,type 默认值为 0,0 表示 Integer,非零表示 String,所有的节点配置都是从 0 开始,及 0 代表节点 1
/**
* defaultNode 默认节点:小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点
* 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点
* 如果不配置默认节点(defaultNode 值小于 0 表示不配置默认节点),碰到
* 不识别的枚举值就会报错,
* like this:can’t find datanode for sharding column:column_nameval:ffffffff
*/
10.5.2 固定分片 hash 算法
本条规则类似于十进制的求模运算,区别在于是二进制的操作,是取 id 的二进制低 10 位,即 id 二进制&1111111111。
此算法的优点在于如果按照 10 进制取模运算,在连续插入 1-10 时候 1-10 会被分到 1-10 个分片,增大了插入的事务控制难度,而此算法根据二进制则可能会分到连续的分片,减少插入事务事务控制难度。
<tableRule name="rule1"