分表键即分库/分表字段,zebra里面叫作维度,是在水平拆分过程当中用于生成拆分规则的数据表字段。Zebra 根据分表键的值将数据表水平拆分到每一个物理分库中。数据库
数据表拆分的首要原则,就是要尽量找到数据表中的数据在业务逻辑上的主体,并肯定大部分(或核心的)数据库操做都是围绕这个主体的数据进行,而后可以使用该主体对应的字段做为分表键,进行分库分表。并发
业务逻辑上的主体,一般与业务的应用场景相关,下面的一些典型应用场景都有明确的业务逻辑主体,可用于分表键:函数
以此类推,其它类型的应用场景,大多也能找到合适的业务逻辑主体做为分表键的选择。性能
若是确实找不到合适的业务逻辑主体做为分表键,那么能够考虑下面的方法来选择分表键:大数据
注意:不管选择什么拆分键,采用何种拆分策略,都要注意拆分值是否存在热点的问题,尽可能规避热点数据来选择拆分键。线程
注意:不必定须要拿数据库主键当作分表键,也能够拿其余业务值当分表键。拿主键当分表键的好处是能够散列均衡,减小热点问题。日志
大部分场景下,一张表的查询条件比较单一,只须要一个分表键便可;可是有的时候,业务必需要有多个分表键,没有办法归一成一个。此时通常有四种处理方式:code
名词定义:orm
因为SQL中没有主维度,因此在对辅助维度进行查询时,只能在全部的主维度的表进行查询一遍,而后聚合。目前zebra的并发粒度是在数据库级别的,也就是说若是分了4个库,32张表,最终会以4个线程去并发查询32张表,最终把结果合并输出。router
适用场景:辅助维度的查询请求的量很小,而且是运营查询,对性能要求不高
主维度的数据,经过binlog的方式,同步到辅助维度一份。那么在查询辅助维度时,会落到辅助维度的数据上进行查询。
适用场景:辅助维度的查询请求的量也很可观,不能直接使用第一种全表扫描的方式
辅助维度其实有的时候也是主维度,好比在订单表Order中,OrderID和UserID实际上是一一对应的,Order表的主维度是UserID,OrderID是辅助维度,可是因为OrderID其中的6位和UserID彻底一致,也就是说,在OrderID中会把UserID打进去。
在路由的时候,若是SQL中带有UserID,那么直接拿UserID进行Hash取模路由;若是SQL中带有的OrderID维度,那么取出OrderID中的6位UserID进行Hash取模路由,结果是一致的。
适用场景:辅助维度和主维度其实能够经过将主维度和辅助维度的值进行信息共享
对于辅助维度能够建一张辅助维度和主维度的映射表。
举例来讲,表A有两个维度,主维度a,辅助维度b,目前只有主维度的一份数据。
此时,若是有SQL: select * from A where b = ?过来,那么势必会在主维度上进行全表扫描。
那么建一张新表B_A_Index,里面就只有两个字段,a和b的值,这张表能够分表,也能够不分表,建议分表这张表的主维度就是b。
因此能够先查:select a from B_A_Index where b = ?,得到到a的值,而后 查询 select * from A where a = 查询到的值 and b = ? 进行查询。
试用场景:主副维度是一一对应的。优点是,无需数据冗余,只须要冗余一份索引数据。缺点是,须要业务进行略微的改造。
zebra 中的水平拆分有两个层次:分库和分表。
通常状况下,建议单个物理分表的容量不超过1000万行数据。一般能够预估2到5年的数据增加量,用估算出的总数据量除以总的物理分库数,再除以建议的最大数据量1000万,便可得出每一个物理分库上须要建立的物理分表数:
表的数量不宜过多,涉及到聚合查询或者分表键在多个表上的SQL语句,就会并发到更多的表上进行查询。举个例子,分了4个表和分了2个表两种状况,一种须要并发到4表上执行,一种只须要并发到2张表上执行,显而后者效率更高。
表的数目不宜过少,少的坏处在于一旦容量不够就又要扩容了,而分库分表的库想要扩容是比较麻烦的。通常建议一次分够。
建议表的数目是2的幂次个数,方便将来可能的迁移。
DBA的操做,通常状况下,会把若干个分库放到一台实例上去。将来一旦容量不够,要发生迁移,一般是对数据库进行迁移。因此库的数目才是最终决定容量大小。
最差状况,全部的分库都共享数据库机器。最优状况,每一个分库都独占一台数据库机器。通常建议一个数据库机器上存放8个数据库分库。
分表方式 | 解释 | 优势 | 缺点 | 试用场景 | 版本要求 |
---|---|---|---|---|---|
Hash | 拿分表键的值Hash取模进行路由。最经常使用的分表方式。 | 数据量散列均衡,每一个表的数据量大体相同 请求压力散列均衡,不存在访问热点 | 一旦现有的表数据量须要再次扩容时,须要涉及到数据移动,比较麻烦。因此通常建议是一次性分够。 | 在线服务。通常均以UserID或者ShopID等进行hash。 | 任意版本 |
Range | 拿分表键按照ID范围进行路由,好比id在1-10000的在第一个表中,10001-20000的在第二个表中,依次类推。这种状况下,分表键只能是数值类型。 | 1.数据量可控,能够均衡,也能够不均衡. 2.扩容比较方便,由于若是ID范围不够了,只须要调整规则,而后建好新表便可。 | 没法解决热点问题,若是某一段数据访问QPS特别高,就会落到单表上进行操做。 | 离线服务。 | 2.9.4以上 |
时间 | 拿分表键按照时间范围进行路由,好比时间在1月的在第一个表中,在2月的在第二个表中,依次类推。这种状况下,分表键只能是时间类型。 | 扩容比较方便,由于若是时间范围不够了,只须要调整规则,而后建好新表便可 | 1.数据量不可控,有可能单表数据量特别大,有可能单表数据量特别小 2.没法解决热点问题,若是某一段数据访问QPS特别高,就会落到单表上进行操做。 | 离线服务。好比线下运营使用的表、日志表等等 |
XML格式
<?xml version="1.0" encoding="UTF-8"?> <router-rule> <table-shard-rule table="Order" generatedPK="id"> <shard-dimension dbRule="#UserID#.toInteger()%32" dbIndexes="order_test[0-31]" tbRule="#UserID#.toInteger().intdiv(32)%32" tbSuffix="alldb:[0,1023]" isMaster="true"> </shard-dimension> </table-shard-rule> </router-rule>
Order表的UserID维度一共分了32个库,分别是order_test0到order_test31。一共分了1024张表,表名分表是Order0到Order1023,平均分到了32个库中,每一个库32张表。
XML格式
<?xml version="1.0" encoding="UTF-8"?> <router-rule> <table-shard-rule table="Order" generatedPK="OrderID"> <shard-dimension dbRule="#UserID#.toInteger()%10000%32" dbIndexes="order_test[0-31]" tbRule="(#UserID#.toInteger()%10000).intdiv(32) %32" tbSuffix="alldb:[0,1023]" isMaster="true"> </shard-dimension> <shard-dimension dbRule="#ShopID# == null ? SKIP : (#ShopID#.toInteger()%16)" dbIndexes="order_shop_test[0-15]" tbRule="(#ShopID#.toInteger()).intdiv(16) %16" tbSuffix="alldb:[0,255]" needSync="true" isMaster="false"> </shard-dimension> </table-shard-rule> </router-rule>
Order表的ShopID维度的数据是根据UserID的数据从新经过binlog同步了一份。该份数据分了16个库,分别是order_shop_test0到order_shop_test15。一共分了256张表,每一个库16张表,表名分表是Order0到Order255。
其中zebra会负责根据该配置,把UserID维度的数据自动的同步到ShopID维度,背后是经过binlog方式,因此会有needSync的属性。**可是,该功能目前已经永久性停用,若是须要使用,请自行接入DTS/DataBus。
<?xml version="1.0" encoding="UTF-8"?> <router-rule> <table-shard-rule table="Order" generatedPK="OrderID"> <shard-dimension dbRule="#UserID#.toInteger()%10000%32" dbIndexes="order_test[0-31]" tbRule="(#UserID#.toInteger()%10000).intdiv(32) %32" tbSuffix="alldb:[0,1023]" isMaster="true"> </shard-dimension> <shard-dimension dbRule="#OrderID#[13..16].toInteger() % 32" dbIndexes="order_test[0-31]" tbRule="(#OrderID#[13..16].toInteger()).intdiv(32) %32" tbSuffix="alldb:[0,1023]" isMaster="false"> </shard-dimension> </table-shard-rule> </router-rule>
Order表的OrderID维度的数据和UserID的数据是一致的,并无冗余。可是因为OrderID中的13到16位就是UserID,因此可使用UserID的数据进行查询。本质上,OrderID和UserID确定能一一对应,实际上是一个维度。
<?xml version="1.0" encoding="UTF-8"?> <router-rule> <table-shard-rule table="Order" generatedPK="OrderID"> <shard-dimension dbRule="#UserID#.toInteger()%10000%32" dbIndexes="order_test[0-31]" tbRule="(#UserID#.toInteger()%10000).intdiv(32) %32" tbSuffix="alldb:[0,1023]" isMaster="true"> </shard-dimension> <shard-dimension dbRule="#AddTime# == null ? SKIP : 0" dbIndexes="order_one" tbRule="#AddTime# == null ? SKIP : 0" tbSuffix="alldb:[]" needSync="true" isMaster="false"> </shard-dimension> </table-shard-rule> </router-rule>
Order表的AddTime维度,实际上是经过binlog的方式把UserID的数据汇总到了order_one这个库,表名为Order这个表。这个汇总的过程是自动的。needSync=true。
线上服务使用ShardDataSource,对UserID的数据进行增删改查。运营服务在访问运营库order_one时,无需使用ShardDataSource,直接使用GroupDataSource进行访问,仅作查询使用。
<?xml version="1.0" encoding="UTF-8"?> <router-rule> <table-shard-rule table="Order" generatedPK="OrderID"> <shard-dimension dbRule="crc32(#UserID#.toInteger())%10000%32" dbIndexes="order_test[0-31]" tbRule="(crc32(#UserID#.toInteger())%10000).intdiv(32) %32" tbSuffix="alldb:[0,1023]" isMaster="true"> </shard-dimension> </table-shard-rule> </router-rule>
crc32是目前zebra的一个内置函数。
内置HASH函数
<?xml version="1.0" encoding="UTF-8"?> <router-rule> <table-shard-rule table="Order" generatedPK="OrderID"> <shard-dimension dbRule="#UserID#.toInteger()%10000%32" dbIndexes="order_test[0-31]" tbRule="(#UserID#.toInteger()%10000).intdiv(32) %32" tbSuffix="alldb:[0,1023]" isMaster="true"> </shard-dimension> <shard-dimension dbRule="shardByMonth(#AddTime#,"yyyy-MM-dd", "2017-01-01","2027-01-31",3,0) % 4" dbIndexes="order_time[0-3]" tbRule="shardByMonth(#AddTime#,"yyyy-MM-dd", "2017-01-01","2027-01-31",3,0).intdiv(4)% 10" tbSuffix="alldb:[0,39]" needSync="true" isMaster="false"> </shard-dimension> </table-shard-rule> </router-rule>
Order表的AddTime维度,实际上是经过binlog的方式把UserID的数据从新根据时间进行分表的,上述例子中,一共分了4个库,40张表,3个月一张表,涵盖10年的量。
线上系统直接使用ShardDataSource使用UserID维度,线下运营系统也是用ShardDataSource,若是带了AddTime,都会落到AddTime维度的数据上进行查询。支持>,<,>=,<=,between and等范围查询;不支持Join。
内置时间函数