简单易懂的MyBatis分库分表方案分享

前言

数据库分库分表除了使用中间件来代理请求分发之外,另外一种常见的方法就是在客户端层面来分库分表 —— 通过适当地包装客户端代码使得分库分表的数据库访问操作代码编写起来也很方便。本文的分库分表方案基于 MyBatis 框架,但是又不同于市面上常用的方案,它们一般都是通过编写复杂的 MyBatis 插件来重写 SQL 语句,这样的插件代码会巨复杂无比,可能最终只有插件的原作者自己可以完全吃透相关代码,给项目的维护性带来一定问题。本文的方案非常简单易懂,而且也不失使用上的便捷性。它的设计哲学来源于 Python —— Explicit is better than Implicit,也就是显式优于隐式,它不会将分库分表的过程隐藏起来。

很多分库分表的设计在实现上会尽量将分库分表的逻辑隐藏起来,其实这是毫无必要的。使用者必须知道背后确实进行了分库分表,否则他怎么会无法进行全局的索引查找?他怎么会无法随意进行多表的 join 操作。如果你真的将它当成单表来用,到上线时必然会出大问题。

项目名称叫:shardino,项目地址:https://github.com/pyloque/shardino

接下来我们来看看在本文的方案之下,数据库操作代码的形式是怎样的帖子表一共分出来 64 个表,不同的记录会各自分发到其中一个表,可以是按 hash 分发,也可以按照日期分发,分发逻辑由用户代码自己来决定。在不同的环境中可以将分表数量设置为不同的值,比如在单元测试下分表设为 4 个,而线上可能需要设置为 64 个。

帖子表又会被分配到多个库,这里就直接取模分配。假设有 4 个帖子库,帖子表总共分出来 64 个表,分别是 post_0、post_1、post_2 一直到 post_63。那么 post_0、post_4、post_8 等分配到 0 号库,post_1、post_5、post_9 等分配到 1 号库,post_2、post_6、post_10 等分配到 2 号库,post_3、post_5、post_11 等分配到 4 号库。

从配置文件中构建 MySQLGroupStore 数据库组对象,这个对象是我们执行 MySQL 操作的入口,通过它可以找到具体的物理的 MySQL 主从数据源。

配置文件 application.properties 如下

这里的数据库组是由多个对等的 Master-Slaves 对构成,每个 Master-Slaves 是由一个主库和多个不同权重的从库构成,Master-Slaves 对的数量就是分库的数量。

mysqlgroup 还有一个特殊的配置选项 slaveEnabled 来控制是否需要从库,从而关闭读写分离,默认是关闭的,这样就不会去构建从库实例相关对象。

post_k 这张表后缀 k 我们称之为 partition number,也就是后续代码中到处在用的 partition 变量,表明当前的记录被分配到对应物理数据表的序号。我们需要根据记录的内容计算出 partition number,再根据 partition number 决定出这条记录所在的物理表属于那个物理数据库,然后对这个物理数据库进行相应的读写操作。

在本例中,帖子表按照 userId 字段 hash 出 64 张表,平均分配到 2 对物理库中,每个物理库包含一个主库和2个从库。

有了 MySQLGroupStore 实例,我们就可以尽情操纵所有数据库了。

从上面的代码中可以看出所有的读写、创建、删除表操作的第一步都是计算出 partition number,然后根据它来选出目标主从库再进一步对目标的数据表进行操作。这里我默认开启了autocommit,所以不需要显式来 session.commit() 了。

在对数据表的操作过程中,又需要将具体的 partition number 传递过去,如此 MyBatis 才能知道具体操作的是哪个分表。

在每一条数据库操作中都必须带上 partition 参数,你可能会觉得这有点繁琐。但是这也很直观,它明确地告诉我们目前正在操作的是哪一个具体的分表。在 MyBatis 的注解 Mapper 类中,如果方法含有多个参数,需要使用 @Param 注解进行名称标注,这样才可以在 SQL 语句中直接使用相应的注解名称。否则你得使用默认的变量占位符名称 param0、param1 来表示,这就很不直观。我们将分表的 hash 算法写在实体类 Post 中,这里使用 CRC32 算法进行 hash。

代码中的 partitionFor 方法的参数 num 就是一共要分多少表。如果是按日期来分表,这个参数可能就不需要,直接返回日期的整数就行比如 20190304。

还有最后一个问题是多个带权重的从库是如何做到概率分配的。这里就要使用到 spring-jdbc 自带的 AbstractRoutingDataSource —— 带路由功能的数据源。它可以包含多个子数据源,然后根据一定的策略算法动态挑选出一个数据源来,这里就是使用权重随机。

但是有个问题,我这里只需要这一个类,但是需要引入整个 spring-boot-jdbc-starter 包,有点拖泥带水的感觉。我研究了一下 AbstractRoutingDataSource 类的代码,发现它的实现非常简单,如果就仿照它自己实现了一个简单版的,这样就不需要引入整个包代码了。

还需进一步深入理解其实现代码的可以将 shardino 代码仓库拉到本地跑一跑

里面有单元测试可以运行起来,运行之前需要确保本机安装了 docker 环境

这条指令会启动2对主从库,各1主两从。在本例中虽然用到了 springboot ,其实也只是用了它方便的依赖注入和单元测试功能,shardino 完全可以脱离 springboot 而独立存在。shardino 并不是一个完美的开源库,它只是一份实现代码的样板,如果读者使用的是其它数据库或者 MySQL 的其它版本,那就需要自己微调一下代码来适配了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Mybatis MapperScannerConfigurer自动扫描Mapper接口生成代理注入到Spring的方法

    前言 Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到Spring Mybatis在与Spring集成的时候可以配置 MapperFactoryBean来生成Mapper接口的代理. 例如: <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mappe

  • Mybatis模糊查询和动态sql语句的用法

    Mybatis 模糊查询和动态sql语句 模糊查询 对数据库最常用的操作就是查询了,但是如何使用Mybatis进行模糊查询呢?下面先看一个简单的模糊查询 <select id="select01" resultMap="BasicResultMap"> SELECT * FROM oa_employee WHERE emp_name LIKE #{asd} </select> 这是一条伪模糊查询, 因为没有实现真正的模糊 "%&qu

  • eclipse下整合springboot和mybatis的方法步骤

    1.新建maven项目 先新建一个maven项目,勾选上creat a simple project,填写groupid,artifactid 2.建立项目结构 3.添加依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE<

  • MyBatis异常-Property 'configLocation' not specified, using default MyBatis Configuration

    配置文件如下: base-context.xml文件如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http

  • SpringBoot项目整合mybatis的方法步骤与实例

    1. 导入依赖的jar包 springboot项目整合mybatis之前首先要导入依赖的jar包,配置pom.xml文件如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  • MyBatis insert操作插入数据之后返回插入记录的id

    MyBatis插入数据的时候,返回该记录的id <insert id="insert" keyProperty="id" useGeneratedKeys="true"
 parameterType="com.demo.domain.CountRateConfig">
 insert into query_rate_config (code,partner_type,search_count, booking_co

  • Mybatis下动态sql中##和$$的区别讲解

    一.介绍 mybatis 中使用 Mapper.xml里面的配置进行 sql 查询,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,sql 如下: select * from user where name = "Jack"; 上述 sql 中,我们希望 name 后的参数 "Jack" 是动态可变的,即不同的时刻根据不同的姓名来查询用户.在 Mapper.xml文件中使用如下的 sql 可以实现动态传递参数 name: select * from u

  • mysql+spring+mybatis实现数据库读写分离的代码配置

    场景:一个读数据源一个读写数据源. 原理:借助spring的[org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource]这个抽象类实现,看名字可以了解到是一个路由数据源的东西,这个类中有一个方法 /** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction

  • mybatis利用association或collection传递多参数子查询

    有时候我们在查询数据库时,需要以查询结果为查询条件进行关联查询. 在mybatis 中通过 association 标签(一对一查询,collection 一对多 查询) 实现延迟加载子查询 <resultMap id="xxxMap" type="xxxx.bean.xxx" extends="zzzzMap"> <association property="destName" javaType="

  • mybatis动态sql之Map参数的讲解

    mybatis 动态sql之Map参数 Mapper文件: <mapper namespace="com.cn.shoje.oa.modules.logistics.dao.PurcDao"> <select id="findAll" parameterType="Map" resultType="Purchase"> select * from prod_purchase where 1=1 <

随机推荐