初探分库分表

本文来自:DanielLin07的博客《初探分库分表》php

前言

分库分表是企业开发数据存储中很是常见的一项优化工做,但以前一直没有去认真了解过,直到最近接触了一个Spark表日同步千万数据到MySQL表的工做,才对分库分表有了一个初步的认识。 本文就是对此次分库分表初步学习的一个记录总结。html

概述

在数据量较小的时候,数据可能是以单表的形式存储。但随着业务量的扩大存储数据量的增长,单表的操做性能也会大大下降,影响正常的业务工做。 这时就须要考虑使用分库分表,通常而言,在单表数据量达到1000万左右(公司DBA建议)时,就能够考虑使用分库分表。java

分库分表策略

垂直切分

用简单的话来讲,垂直切分就是将一个表中涉及的多个字段切分到不一样的表甚至是库中存储。以下图所示:node

垂直切分

咱们经常使用的 数据库三大范式 设计,其实也是一种垂直切分。 另外一种经常使用的垂直切分,则是将热门访问字段与冷门访问字段进行切分,从而让数据库能够以更少的字段缓存更多的行,进而带来性能的提高。mysql

水平切分

用简单的话来讲,水平切分就是将一个表中存储的数据依照某种策略存储到不一样的表上。以下图所示:git

水平切分

Range

水平切分的第一种方式就是Range,即根据必定的范围进行分发。 如:根据时间范围,一个月的数据存储一张表,或者是根据用户ID这种自增序列,用户ID在000000至100000范围的存一张表,100001至200000范围的存一张表等。 根据Range分发的好处就是数据扩容时方便。缺点就是容易产生数据热点问题。github

Hash

水平切分的第二种方式就是Hash,即经过一次哈希运算而后取余分表数量-1的方式肯定数据要存的表的位置。 如:根据用户姓名进行Hash分发。用户姓名小明,计算hashcode,获得754703,预先肯定分表数量为8,再取余7,获得3,即分发到索引为3的数据表上。 根据Hash分发的好处就是数据分发均匀,不会产生数据热点问题,可是扩容的时候很是不方便,还须要从新计算数据的哈希值。web

MyBatis + ShardingJDBC 实践分库分表

ShardingJDBC是ShardingSphere的子项目,在Java的JDBC层提供的额外服务。具体可见ShardingPhere官方文档spring

数据库准备

现有用户信息须要存储,分别有五个字段:uid、name、mobile、credit_id、create_time。 如今的分库分表策略是:sql

  • 根据uid进行水平切分,uid最后一位为偶数的,分到sharding0db数据库,不然分到sharding1db数据库。
  • 在各数据库中,uid倒数第二位为偶数的,分到t_user_0表,不然分到t_user_1表。 因此每一个表存储的字段都是同样的,其中一个表的数据库 Schema 脚本以下:
DROP TABLE IF EXISTS `t_user_0`;
CREATE TABLE `t_user_0`  (
  `uid` int(6) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `credit_id` varchar(16) NOT NULL,
  `create_time` datetime(0) NULL,
  PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
复制代码

在sharding0db与sharding1db都创建了数据表后,结构以下图所示:

数据库结构

Maven依赖

本项目使用的是Spring-Boot 2.0.3.RELEASE,在项目中导入如下Maven依赖:

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
   </dependency>

   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
   </dependency>

   <dependency>
      <groupId>org.apache.shardingsphere</groupId>
      <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
      <version>4.0.0-RC1</version>
   </dependency>

   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
   </dependency>
</dependencies>
复制代码

配置文件

application.yml中进行配置:

spring:

 shardingsphere:
 datasource:
 names: sharding0db,sharding1db
 sharding0db:
 type: com.zaxxer.hikari.HikariDataSource
 jdbc-url: jdbc:mysql://localhost:3306/sharding0db?useUnicode=true&useSSL=false&useAffectedRows=true&characterEncoding=utf8
 driver-class-name: com.mysql.jdbc.Driver
 username: root
 password:
 sharding1db:
 type: com.zaxxer.hikari.HikariDataSource
 jdbc-url: jdbc:mysql://localhost:3306/sharding1db?useUnicode=true&useSSL=false&useAffectedRows=true&characterEncoding=utf8
 driver-class-name: com.mysql.jdbc.Driver
 username: root
 password:

 sharding:
      # 分库分表策略
 default-database-strategy:
 inline:
          # 分片的列
 sharding-column: uid
          # 分片的表达式,groovy语言,这里是对uid进行取余,若是为结果为0则分到sharding0db,结果为1则分到sharding1db
 algorithm-expression: sharding$->{uid % 2}db
 tables:
 t_user:
 actual-data-nodes: sharding$->{0..1}db.t_user_$->{0..1}
 table-strategy:
 inline:
 sharding-column: uid
              # 分片的表达式,对uid倒数第二位取余,若是为结果为0则分到t_user_0,结果为1则分到t_user_1
 algorithm-expression: t_user_$->{uid.intdiv(10) % 2}

# MyBatis配置
mybatis:
  # Mapper映射文件的位置
 mapper-locations: classpath:mapper/*.xml
  # 包下全部类的别名,配置别名为了在对象映射文件中接收参数类型和返回参数类型时省略包路径
 type-aliases-package: com.daniellin.demosharding.entity
复制代码

编码

准备UserDAO文件:

@Mapper
@Repository
public interface UserDAO {

    /** * 获取全部用户 * * @return 全部用户 */
    List<User> queryList();

    /** * 添加新用户 * * @param user 新用户 */
    void insert(User user);
}
复制代码

准备UserDAO的XML映射:

<?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 namespace="com.daniellin.demosharding.dao.UserDAO">

    <resultMap id="UserMapping" type="User">
        <id column="uid" property="uid" javaType="int"/>
        <result column="name" property="name" javaType="String"/>
        <result column="mobile" property="mobile" javaType="long"/>
        <result column="credit_id" property="creditId" javaType="long"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <!-- 获取全部用户 -->
    <select id="queryList" resultMap="UserMapping">
        SELECT * FROM t_user
    </select>

    <!-- 添加新用户 -->
    <insert id="insert" keyProperty="uid" parameterType="User" >
        INSERT INTO t_user(uid, name, mobile, credit_id, create_time)
        VALUES (#{uid},#{name},#{mobile},#{creditId},#{createTime})
    </insert>

</mapper>
复制代码

准备User实体:

@Data
public class User {

    private Integer uid;

    private String name;

    private String mobile;

    private String creditId;

    private Date createTime;
}
复制代码

编写单元测试插入数据,这里是经过随机生成100个用户的uid进行测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoShardingApplicationTests {

    @Autowired
    private UserDAO userDAO;

    @Test
    public void testInsert() {
        System.out.println(("----- sharding insert method test ------"));
        for (int i = 0; i < 100; i++) {
            User userData = new User();
            userData.setUid(new Random().nextInt(999999));
            userData.setName(UUID.randomUUID().toString().replaceAll("-", ""));
            userData.setCreditId("1234567890");
            userData.setMobile("1234567890");
            userData.setCreateTime(new Date());
            userDAO.insert(userData);
        }
    }
}
复制代码

运行结果

查看数据结果,能够看到数据已成功插入到指定的数据库表中。 最后一位为奇数,倒数第二位为偶数的,被插入到sharding1db.t_user_0:

sharding1db.t_user_0

最后一位为偶数,倒数第二位为奇数的,被插入到sharding0db.t_user_1:

sharding0db.t_user_1

参考资料

# 文章连接 做者
1 sharding:谁都能读懂的分库、分表、分区 骏马金龙
2 一次可贵的分库分表实践 crossoverjie
3 advanced-java doocs
相关文章
相关标签/搜索