龙空技术网

如何存取海量数据——数据库分表实现方案

IT小村 5713

前言:

此刻看官们对“java bigdecimal取模”大致比较讲究,同学们都需要了解一些“java bigdecimal取模”的相关资讯。那么小编在网络上收集了一些有关“java bigdecimal取模””的相关知识,希望看官们能喜欢,兄弟们一起来学习一下吧!

前言

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

本文基于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@Builderpublic 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@Componentpublic 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" ";><!--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 */@Servicepublic 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;@SpringBootTestpublic 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已有该数据了

我们再进行查询数据测试

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;@SpringBootTestpublic class UserQueryTest {    @Resource    UserInfoService userInfoService;    @Test    void getUserInfoTest() {        UserInfo userInfo = userInfoService.getUserInfoById(10000L);        System.out.println(userInfo);    }}

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

说明分表读写是正常的。

最后

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

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

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

标签: #java bigdecimal取模