快捷搜索:  汽车  科技

数据库 分表如何查询(如何存取海量数据)

数据库 分表如何查询(如何存取海量数据)我们定义了两个数据库访问层的接口package com.example.itspringbootmybatisshared.domain; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.util.Date; /** * 用户信息 * * @author hongcunlin */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserInfo { /** * 自增ID */ private Long i

数据库 分表如何查询(如何存取海量数据)(1)

前言

笔者维护的许多系统,大多在线上稳定运行多年,拥有大量的用户,便拥有海量的数据,那么如何在数量多的情况确保数据库的存取性能呢?这便是本文要介绍的一个数据库分表方案了。

本文基于Mybaits实现,前置知识可以参考前文:

SpringBoot集成MyBatis的相关要点

思路

分表的最大难点,在于确定你存取的数据在哪张表上。

解决方案在于将一个不可或缺的字段(主键)作为分表依据,我们可以通过对其进行哈希计算,再取模表的数量,即可确定数据在第几张表上了,最后再对表名进行拼接,即可确定表名。

案例

1.domain层

我们定义了一个和数据库表字段对应的javaBean

package com.example.itspringbootmybatisshared.domain; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.util.Date; /** * 用户信息 * * @author hongcunlin */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserInfo { /** * 自增ID */ private Long id; /** * 用户名 */ private String name; /** * 用户余额 */ private BigDecimal money; /** * 创建时间 */ private Date createTime = new Date(); }

2.mapper层

我们定义了两个数据库访问层的接口

package com.example.itspringbootmybatisshared.mapper; import com.example.itspringbootmybatisshared.domain.UserInfo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Component; /** * @author hongcunlin */ @Mapper @Component public interface UserInfoMapper { /** * 新增用户 * * @param userInfo 用户信息 * @param tableName 表名 */ void addUserInfo(@Param("userInfo") UserInfo userInfo @Param("tableName") String tableName); /** * 查询用户信息 * * @param id 用户id * @param tableName 表名 * @return 用户信息 */ UserInfo getUserInfoById(@Param("id") Long id @Param("tableName") String tableName); }

编写对应的SQL

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--mapper--> <mapper namespace="com.example.itspringbootmybatisshared.mapper.UserInfoMapper"> <!--获取所有用户信息--> <select id="getAll" resultType="com.example.itspringbootmybatisshared.domain.UserInfo"> select * from user_info; </select> <!-- 新增用户,注意tableName不能预编译 --> <insert id="addUserInfo"> insert into ${tableName} (id name money create_time) values (#{userInfo.id jdbcType=BIGINT} #{userInfo.name jdbcType=VARCHAR} #{userInfo.money jdbcType=DECIMAL} #{userInfo.createTime jdbcType=DATE}) </insert> <!-- 查询用户,注意tableName不能预编译 --> <select id="getUserInfoById" resultType="com.example.itspringbootmybatisshared.domain.UserInfo"> select * from ${tableName} where id = #{id} </select> </mapper>

3.Service层

我们定义了用户信息操作的接口与实现,主要是对mapper做了一层表名的封装,统一处理表名的相关逻辑

package com.example.itspringbootmybatisshared.service; import com.example.itspringbootmybatisshared.domain.UserInfo; /** * 用户Service层接口 * * @author hongcunlin */ public interface UserInfoService { /** * 新增用户信息 * * @param userInfo 用户信息 */ void addUserInfo(UserInfo userInfo); /** * 查询用户信息 * * @param id 用户id * @return 用户信息 */ UserInfo getUserInfoById(Long id); }

package com.example.itspringbootmybatisshared.service.impl; import com.example.itspringbootmybatisshared.domain.UserInfo; import com.example.itspringbootmybatisshared.mapper.UserInfoMapper; import com.example.itspringbootmybatisshared.service.UserInfoService; import com.example.itspringbootmybatisshared.utils.TableUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * 用户Service层实现 * * @author hongcunlin */ @Service public class UserInfoServiceImpl implements UserInfoService { /** * 用户mapper层 */ @Resource private UserInfoMapper userInfoMapper; /** * 新增用户信息 * * @param userInfo 用户信息 */ @Override public void addUserInfo(UserInfo userInfo) { userInfoMapper.addUserInfo(userInfo TableUtils.getTabNameById(userInfo.getId())); } /** * 查询用户信息 * * @param id 用户id * @return 用户信息 */ @Override public UserInfo getUserInfoById(Long id) { return userInfoMapper.getUserInfoById(id TableUtils.getTabNameById(id)); } }

4.工具层

这是本文的核心,就是如何确定表在哪里。

我们定义了2个常量,分别是表名的基础名称、表数量。

我们定义了1个获取表名的方法,便是通过对用户的id进行hash计算,在%表数量,最后再拼接表名的基础组成部分。

package com.example.itspringbootmybatisshared.utils; /** * 分表工具 * 定位表 * * @author hongcunlin */ public class TableUtils { /** * 表名基本组成部分 */ private static final String TAB_NAME_BASE = "user_info_"; /** * 表数量 */ private static final Integer TAB_NUM = 8; /** * 根据id获取表名 * * @param id 用户id * @return 表名 */ public static String getTabNameById(Long id) { return TAB_NAME_BASE (id.hashCode() % TAB_NUM 1); } } 测试

我们先测试新增数据

package com.example.itspringbootmybatisshared; import com.example.itspringbootmybatisshared.domain.UserInfo; import com.example.itspringbootmybatisshared.service.UserInfoService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import java.math.BigDecimal; @SpringBootTest public class UserAddTest { @Resource UserInfoService userInfoService; @Test void addUserInfoTest() { UserInfo userInfo = new UserInfo(); userInfo.setId(10000L); userInfo.setMoney(new BigDecimal("10.00")); userInfo.setName("中国电信"); userInfoService.addUserInfo(userInfo); } }

运行完上面的单元测试,我查询数据库,可以看到该表user_info_1已有该数据了

数据库 分表如何查询(如何存取海量数据)(2)

我们再进行查询数据测试

package com.example.itspringbootmybatisshared; import com.example.itspringbootmybatisshared.domain.UserInfo; import com.example.itspringbootmybatisshared.service.UserInfoService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; @SpringBootTest public class UserQueryTest { @Resource UserInfoService userInfoService; @Test void getUserInfoTest() { UserInfo userInfo = userInfoService.getUserInfoById(10000L); System.out.println(userInfo); } }

运行完上面的单元测试,可以看到顺利打印出刚才新增的数据

数据库 分表如何查询(如何存取海量数据)(3)

说明分表读写是正常的。

最后

分表很好的解决了我们数据量大存取的性能问题。

此外,分库的解决方案和分表类似,只不过多了1层确定数据是在哪个库上的逻辑,而这逻辑和确定数据在哪张表上是一样的,依旧是依赖一个业务主键进行哈希计算、取模确定的。

然而分表也有一个缺点,就是你以后的写入、查询,都需要附带一个业务主键,由它来确定库表在的具体位置,不过数据基本都有主键的,而那个主键通常就可以用来业务主键,逻辑上并没有增加多少障碍。

数据库 分表如何查询(如何存取海量数据)(4)

猜您喜欢: