几种水平分表方案与具体实践

最近在写水平分表,调研总结实践了一些比较普遍的方法,下面总结一下。

水平分表

所谓水平分表,顾名思义,就是由于某个单表数据过多,影响查询效率,于是选择分表,将原来的业务表按某个维度拆分成几个表,查询的时候根据维度限定选择对应的维度分表去查。比如常见情况下的a表,我按某个维度把它分成了a_1,a_2,a_3。那么当我要查1的数据就去查a_1,查2的数据就去查a_2.

前提条件

  • 最最前提,主键要唯一,有很多实现方法,比如可以针对不同分表设置不同步长来实现,也可以通过雪花算法,uuid来生成分布式唯一id,一般情况下,用后者比较多。

  • 不要过度分表或分库,要从实际需求出发,可以是已经碰到的情况下,也可以是预估出不分表不行的情况。

  • 所要分的表与其他表关联度不是很大,如果它经常被做连接查询,而且它的主键在别的表经常以外键形式存在,那么就不建议直接水平分表,可以先考虑垂直分表把数据量大的部分独立出去再做水平切分。

维度的选择

维度的选择方法有很多,一般情况下,我们可以分成以下几种情况。

  • 按时间分表:这种情况是比较普遍的选择,绝大多数业务表的场景是很少有具体的隔离情况。我们比较简单的选择就是按照时间分表,选择雪花算法,在查询的时候拦截主键,从主键中获取分表key,选择查询的范围。举一个最常见的例子,比方说电商平台的订单表order,我们根据时间分成order_2010_2020,order_2020_2030,表示时间在2010年到2020年与2020年到2030年的表。在进行插入更新时候,选择当前时间属于的时间区间替换表名并插入,查询的时候则选择主键中的时间部分进行表名的替换再进行改写sql查询。
  • 按id取模分表:适用于区分表的业务属性不大相关的情况,没有明显的特征,只要单纯将数据按一定规格拆分,比方说一百万条数据分成3个表,只需要id对3取余即可。
  • 按某一字段分表:针对表中的某一字段进行分表,举个具体例子,仍然是订单表,订单里存了一个属性叫地区,按地区分表。那么只需要在每次查询的时候带上这个地区即可,同样的根据userid范围分表也是一样的道理。
  • 按不存在于数据库的情况分表:这种情况分几种类型,即分表的字段存在数据库外,有时来自上下文,有时来自实例里的yml配置。这时候需要我们在业务层做强制路由即可实现。

针对非分表key的查询处理

针对有些情况没有分表key的情况也是比较普遍的,比方说一个c端与b端之间的平台类项目,往往在选择分表key的时候会出现顾守不顾尾的情况。这个时候总要牺牲一端的用户体验。比方说针对userid对order表分表,那么c端用户查询确实是便利了,但是b端用户就很难找到这个userid,这个时候我们一般有几种处理方法:

  • 改写sql走union查询:这种方式问题比较大,一方面是由于union效率比较低,做排序分页等查询问题也比较大,另一方面如果使用mybatis-plus之类的工具,使用拦截器插件进行开发分表功能的话,如果我们进行查询的时候参数拼接重写会有参数无法注入的问题。具体来说比方说select * from a where id = 1.那么分表之后的查询变成了(select * from a_1 where id = 1) union (select * from a_2 where id = 1);但在mp框架里会出现参数集为1,待重写阶段的sql为select * from a_1 where id = ?,这样的在一句sql里面出现了两个问号,但是没法填充。当然如果对mp源码理解比较深的话,可以把参数重写。即扩充一倍即可。
  • 双写:另外搞一个主库或者主表,比较简单,每次插入更新的时候都做两次,主表的话加个事务问题不大,如果加个主库的话,则要注意保证一致性,可以考虑消息队列或者binlog之类的处理方法。
  • 更改业务逻辑,这种方法一般不太可能,也有特殊情况。

分表具体实现形式

  1. 第一种做法的话就是使用成熟的框架,比方说mycat和sharding-jdbc这种,业务侵入比较小,也比较方便,具体代码官网示例也写的较为清楚,当然对于一些团队没有接触过的东西,学习周期就会相对长一点。
  2. 第二种做法的话使用插件自行开发,其实就是拦截sql并改写。下面以mybatis-plus插件为例,参照官网的分页插件可以写出分表插件,自行实现。
@Intercepts(
        {
                @Signature(
                        type = StatementHandler.class,
                        method = "prepare",
                        args = {Connection.class, Integer.class}
                )
        }
)
public class TableShardInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement)
                metaObject.getValue("delegate.mappedStatement");
        String id = mappedStatement.getId();
        id = id.substring(0, id.lastIndexOf('.'));
        Class clazz = Class.forName(id);
        
            String sql = (String) metaObject.getValue("delegate.boundSql.sql");
            metaObject.setValue("delegate.boundSql.sql", strategy.rewriteSql(sql, tableName));
       //这里的rewirite todo写具体的逻辑。
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身, 减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
水平分表是将一个表的数据按照某个规拆分成多个子表存储的一种方式。在MySQL中,可以通过以下步骤来实现水平分表: 1. 设计分表规则:根据业务需求,确定分表的规则,例如按照某个字段的取值范围、哈希值或者时间等来进行分表。 2. 创建主表和子表:首先创建主表,然后根据分表规则创建多个子表,每个子表都具有相同的结构。 3. 插入数据:将数据根据分表规则插入到对应的子表中。 4. 查询数据:在查询时,根据分表规则确定需要查询的子表,然后执行查询操作。 下面是一个简单的示例来演示水平分表的实现: 1. 创建主表和子表: ```sql CREATE TABLE main_table ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), age INT ); CREATE TABLE sub_table_1 ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), age INT ); CREATE TABLE sub_table_2 ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), age INT ); ``` 2. 插入数据: ```sql -- 将数据插入到子表1 INSERT INTO sub_table_1 (name, age) VALUES ('Alice', 25); -- 将数据插入到子表2 INSERT INTO sub_table_2 (name, age) VALUES ('Bob', 30); ``` 3. 查询数据: ```sql -- 查询子表1的数据 SELECT * FROM sub_table_1; -- 查询子表2的数据 SELECT * FROM sub_table_2; ``` 注意:在实际应用中,可能需要使用存储过程或者触发器来自动将数据插入到对应的子表中,以及在查询时动态确定需要查询的子表。这里的示例仅为了演示水平分表的基本概念。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值