为什么要分表

Mysql是当前互联网系统中使用非常广泛的关系数据库,具有ACID的特性。

但是mysql的单表性能会受到表中数据量的限制,主要原因是B+树索引过大导致查询时索引无法全部加载到内存。读取磁盘的次数变多,而磁盘的每次读取对性能都有很大的影响。

这时一个简单可行的方案就是分表(当然土豪也可以堆硬件),将一张数据量庞大的表的数据,拆分到多个表中,这同时也减少了B+树索引的大小,减少磁盘读取次数,提高性能。

两种基础分表逻辑

说完了为什么要分表,下面聊聊业务开发中常见的两种基础的分表逻辑。

按日期分表

这种方式通常会在表名的最后加上年月日,主要适用于按日期划分的统计数据或操作记录。在线实时展示的只有最近表中的数据,其他数据用于离线统计等。

按id取模分表

这种方式需要一个id生成器,例如snowflake id或分布式id服务。它保证了相同id的数据都在一张表中,主要适用于保存用户基础信息,系统中的资源信息,购买记录等。当然这种分表方式扩展性较差,后期数据持续增多后需要按id大小分库再分表处理。

下面看下这两种分表逻辑在mybatis-plus中的实现。

Mybatis-plus中的分表实现

说到java的分表中间件,可能有人会想到sharding-jdbc,作为使用很广泛的一个分表中间件,功能也比较完善,但是使用它需要引入额外的jar包和增加学习成本。

实际上mybatis-plus本身就提供了一个分表的解决方案,配置使用都很简单,适合快速开发系统。

动态表名处理器

没错,mybatis-plus提供了动态表名处理器接口TableNameHandler,只需要在系统中实现该接口,并作为插件加载到mybatis-plus中就可以使用,下面来看下详细的步骤。

3.4版本之前的动态表名接口是ITableNameHandler,需要和分页插件配合使用。

3.4版本新增了TableNameHandler,在方法参数上取消了MetaObject。这里用最新的版本为例,使用方式差别不大。

假设我们的系统中有两种分表方式,按日期分表和按id取模分表。通过四个步骤来看下具体的使用示例。

1.创建日期表名处理器

先来看下日期处理的表名处理器,实现TableNameHandler接口后,在dynamicTableName方法中实现动态生成表名的逻辑,方法的返回值就是查询时要使用的表名。

/**
* 按天分表解析
*/
public class DaysTableNameParser implements TableNameHandler { @Override
public String dynamicTableName(String sql, String tableName) {
String dateDay = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
return tableName + "_" + dateDay;
}
}
2.创建id取模表名处理器

再来看下按id取模表名处理器的实现,这个处理器相对日期处理就要复杂一些,主要原因为需要动态传入用于分表的id值

在之前的版本中可以在方法中通过解析MetaObject中带有的sql查询信息,获取分表使用的值。但是这种方式比较复杂,对于不同的QueryMapper分析的方式不同,比较容易出错。新版本中的方法取消了MetaObject参数,需要使用其他方式传入。

需要注意的是,表名处理器是作为mybatis-plus的插件,在项目启动时实例化的。这意味着,在运行过程中只有一个对象,多线程处理过程中,一个线程对参数的修改,会影响到其他线程。为了解决这个问题,可以使用ThreadLocal来定义参数。

由于现在的框架中大部分会使用线程池,例如springboot web项目中的tomcat。所以在每次使用后,需要手动清除本次数据,防止线程复用时的影响。

具体实现如下:

/**
* 按id取模分表处理器
*/
public class IdModTableNameParser implements TableNameHandler {
private Integer mod; //使用ThreadLocal防止多线程相互影响
private static ThreadLocal<Integer> id = new ThreadLocal<Integer>(); public static void setId(Integer idValue) {
id.set(idValue);
} IdModTableNameParser(Integer modValue) {
mod = modValue;
} @Override
public String dynamicTableName(String sql, String tableName) {
Integer idValue = id.get();
if (idValue == null) {
throw new RuntimeException("请设置id值");
} else {
String suffix = String.valueOf(idValue % mod);
//这里清除ThreadLocal的值,防止线程复用出现问题
id.set(null);
return tableName + "_" + suffix;
}
}
}
3.加载表名处理器

表名处理器实际是mybatis-plus的插件,需要在初始化时创建实例并加载。因为系统中存在两种分表类型,在初始化时可以指定每张表使用的表名处理器。具体实现如下:

@Configuration
@MapperScan(basePackages = "com.yourcom.proname.repository.mapper.mainDb*", sqlSessionFactoryRef = "mainSqlSessionFactory")
public class MainDb {
@Bean(name = "mainDataSource")
@ConfigurationProperties(prefix = "dbconfig.maindb")
public DataSource druidDataSource() {
return DruidDataSourceBuilder.create().build();
} @Bean(name = "mainTransactionManager")
public DataSourceTransactionManager masterTransactionManager(@Qualifier(value = "mainDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
} @Bean(name = "mainSqlSessionFactory")
@ConfigurationPropertiesBinding()
public SqlSessionFactory sqlSessionFactory(@Qualifier(value = "mainDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
//加载插件
factoryBean.setPlugins(mybatisPlusInterceptor());
return factoryBean.getObject();
} @Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
HashMap<String, TableNameHandler> map = new HashMap<String, TableNameHandler>(); //这里为不同的表设置对应表名处理器
map.put("user_daily_record", new DaysTableNameParser());
map.put("user_consume_flow", new IdModTableNameParser(10)); dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map);
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
return interceptor;
}
}
4.在controller中使用

下面通过controller中的三个接口,展示下使用方式:

@RestController
public class TableTestController {
@Resource
IUserDailyRecordService userDailyRecordService; @Resource
IUserConsumeFlowService userConsumeFlowService; @GetMapping("user/record/today")
public CommonResVo<UserDailyRecord> getRecordToday(Integer userId) throws Exception {
//这里在查询时,会根据系统当前时间,自动生成当天的表名
UserDailyRecord userDailyRecord = userDailyRecordService.getOne(new LambdaQueryWrapper<UserDailyRecord>().eq(UserDailyRecord::getUserId, userId));
return CommonResVo.success(userDailyRecord);
} @GetMapping("user/consume/flow")
public CommonResVo<List<UserConsumeFlow>> getConsumeFlow(Integer userId) throws Exception {
//设置用于分表的id值
IdModTableNameParser.setId(userId);
List<UserConsumeFlow> userConsumeFlowList = userConsumeFlowService.list(new LambdaQueryWrapper<UserConsumeFlow>().eq(UserConsumeFlow::getUserId, userId));
return CommonResVo.success(userConsumeFlowList);
} /**
* 新增数据
*/
@PostMapping("user/consume/flow")
public CommonResVo<Boolean> addConsumeFlow(@RequestBody UserConsumeFlow userConsumeFlow) throws Exception {
Integer userId = userConsumeFlow.getUserId();
//设置用于分表的id值
IdModTableNameParser.setId(userId);
userConsumeFlowService.save(userConsumeFlow);
return CommonResVo.success(true);
}
}

这篇对mybatis-plus动态表名处理器的介绍,通过实现TableNameHandler接口,可以按实际情况灵活定义表名的生成规则,希望对大家有帮助。

项目完整示例地址:https://gitee.com/dothetrick/web-demo/tree/tabel-shading

以上内容属个人学习总结,如有不当之处,欢迎在评论中指正

mybatis-plus还可以这样分表的更多相关文章

  1. sharding-jdbc结合mybatis实现分库分表功能

    最近忙于项目已经好久几天没写博客了,前2篇文章我给大家介绍了搭建基础springMvc+mybatis的maven工程,这个简单框架已经可以对付一般的小型项目.但是我们实际项目中会碰到很多复杂的场景, ...

  2. 基于mybatis拦截器分表实现

    1.拦截器简介 MyBatis提供了一种插件(plugin)的功能,但其实这是拦截器功能.基于这个拦截器我们可以选择在这些被拦截的方法执行前后加上某些逻辑或者在执行这些被拦截的方法时执行自己的逻辑. ...

  3. 分表需要解决的问题 & 基于MyBatis 的轻量分表落地方案

    分表:垂直拆分.水平拆分 垂直拆分:根据业务将一个表拆分为多个表. 如:将经常和不常访问的字段拆分至不同的表中.由于与业务关系密切,目前的分库分表产品均使用水平拆分方式. 水平拆分:根据分片算法将一个 ...

  4. Mybatis中分表插件shardbatis使用说明

    Mybatis中实现分表,有个很简单的插件,叫shardbatis,使用maven构建的工程,可以在pom.xml中添加依赖性即可: <!-- 分库分表插件 --> <depende ...

  5. MySQL性能优化(五):分表

    原文:MySQL性能优化(五):分表 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/vbi ...

  6. php面试专题---MySQL分表

    php面试专题---MySQL分表 一.总结 一句话总结: 分库分表要数据达到一定的量级才用,这样才有效率,不然利不一定大于弊,可能会增加一次I/O消耗 1.分库分表的使用量级是多少? 单表行数超过 ...

  7. MyBatis实现Mysql数据库分库分表操作和总结

    前言 作为一个数据库,作为数据库中的一张表,随着用户的增多随着时间的推移,总有一天,数据量会大到一个难以处理的地步.这时仅仅一张表的数据就已经超过了千万,无论是查询还是修改,对于它的操作都会很耗时,这 ...

  8. 数据库分表之Mybatis+Mysql实践(含部分关键代码)

    2018年01月31日      随着我们系统用户数量的日增,业务数据处于一个爆发前,增长的数据量已经给我们的系统造成了很大的不确定.在上个周末用户量较多,并发较大的情况下,读写频繁的验证码表,数据量 ...

  9. Mybatis的分表实战

    前言: 以前写代码, 关于mysql的分库分表已被中间件服务所支持, 业务代码涉及的sql已规避了这块. 它对扩展友好, 你也不知道到底他分为多少库, 多少表, 一切都是透明的. 不过对于小的团队/工 ...

随机推荐

  1. SpringBoot开发秘籍 - 集成Graphql Query

    概述 REST作为一种现代网络应用非常流行的软件架构风格受到广大WEB开发者的喜爱,在目前软件架构设计模式中随处可见REST的身影,但是随着REST的流行与发展,它的一个最大的缺点开始暴露出来: 在很 ...

  2. python 迭代器,生成器,表达式

    1.迭代器 (1)什么是迭代器: #迭代器即迭代的工具,那什么是迭代呢?#迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值 while True: #只是单纯地重复, ...

  3. java面试一日一题:mysql中的自增主键

    问题:请讲下mysql中的自增主键 分析:该问题主要考察对mysql中自增主键的掌握,使用场景及如何设置 回答要点: 主要从以下几点去考虑 1.什么自增主键 2.使用场景是什么: 3.innodb_a ...

  4. 解决mysql You can't specify target table for update in FROM clause错误

    mysql中You can't specify target table for update in FROM clause错误的意思是说,不能先select出同一表中的某些值,再update这个表( ...

  5. Netty 框架学习 —— 第一个 Netty 应用

    概述 在本文,我们将编写一个基于 Netty 实现的客户端和服务端应用程序,相信通过学习该示例,一定能更全面的理解 Netty API 该图展示的是多个客户端同时连接到一台服务器.客户端建立一个连接后 ...

  6. 【JavaWeb】 Cookie和Session

    Session和Cookie出现的原因: 由于Http是无状态的协议,会话之间没有任何关联,也就是上一次会话和下一次会话没有任何关联,因此出现了会话技术Cookie和Session 下面分别从Cook ...

  7. 路由器逆向分析------MIPS交叉编译环境的搭建(Buildroot)

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/68950682 为了能在我们熟悉的windows或者ubuntu下开发mips架构的 ...

  8. Dalvik模式下System.loadLibrary函数的执行流程分析

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78212010 Android逆向分析的过程中免不了碰到Android so被加固的 ...

  9. hdu4975 行列和构造矩阵(dp判断唯一性)

    题意:       和hdu4888一样,只不过是数据加强了,就是给你行列的和,让你构造一个矩阵,然后判断矩阵是否唯一. 思路:       构造矩阵很简单,跑一次最大流就行了,关键是判断矩阵的唯一性 ...

  10. 《THE LEAN STARTUP》 《精益创业》

    书名:<THE LEAN STARTUP> <精益创业> 作者: [美] 埃里克·莱斯 IMVU:(3D人物场景聊天)https://secure.imvu.com 作者是这个 ...