1. 分库分表介绍

随着互联网以及移动互联网的发展 , 应用系统的数据量也是成指数式增长 , 若采用单数据库进行数据存储 , 存在以下性能瓶颈:

  • IO瓶颈: 热点数据太多 , 数据库缓存不足 , 产生大量磁盘IO , 效率太低。请求数据太多 , 带宽不够 , 网络IO瓶颈。

  • CPU瓶颈: 排序 , 分组 , 连接查询 , 聚合统计等SQL会耗费大量的CPU资源 , 请求数太多 , CPU出现瓶颈。

未来解决上述问题 , 我们需要对数据库进行分库分表处理

分库分表的中心思想都是将数据分散存储 , 使得单一数据库/表的数据量变小来缓解单一数据库的性能问题 , 从而达到提升数据库性能的目的。

2. 拆分策略

分库分表的形式 , 主要是两种: 垂直拆分和水平拆分。而拆分的粒度 , 一般又分为分库和分表 , 所以组成的拆分策略最终如下:

mariadb-mycat-1

2.1 垂直拆分

  • 垂直分库

    mariadb-mycat-2

    以表为依据 , 根据业务将不同表拆分到不同库中。

    特点:

    • 每个库的表结构都不一样

    • 每个库的数据也不一样

    • 所有库的并集是全量数据

  • 垂直分表

    mariadb-mycat-3

    以字段为依据 , 根据字段属性将不同字段拆分到不同表中

    特点

    • 每个表的结构都不一样

    • 每个表的数据也不一样 , 一般通过一列(主键/外键)关联

    • 所有表的并集是全量数据

2.2 水平拆分

  • 水平分库

    mariadb-mycat-5

    以字段为依据 , 按照一定策略 , 将一个库的数据拆分到多个库中

    特点

    • 每个库的表结构都一样

    • 每个库的数据都不一样

    • 所有库的并集是全量数据

  • 水平分表

    mariadb-mycat-4

    以字段为依据 , 按照一定策略 , 将一个表的数据拆分到多个表中

    特点

    • 每个表的表结构都一样

    • 每个表的数据都不一样

    • 所有表的并集是全量数据

在业务系统中 , 未来缓解磁盘IO以及CPU的性能瓶颈 , 到底是垂直拆分还是水平拆分;具体看分库还是分表 , 都需要根据具体业务需求具体分析。

3. MyCat 基础

3.1 MyCat 介绍

MyCat是开源的 , 活跃的 , 基于Java语言编写的MySQL数据库中间件。可以像使用MySQL一样来使用MyCat , 对于开发人员来说根本感觉不到MyCat的存在。

开发人员只需要连接MyCat即可 , 而具体底层用到几台数据库 , 每一台数据库服务器里面存储了什么数据 , 都无需关心。而具体的分库分表的策略 , 只需要在MyCat中配置即可。

除了MyCat之外还有shardingJDBC等技术区实现分库分表。

官网地址: http://www.mycat.org.cn/

mycat2 页面: MyCat2

github链接: GitHub - MyCATApache/Mycat2: MySQL Proxy using Java NIO based on Sharding SQL,Calcite ,simple and fast

image-20250407235916780

MyCat最早的版本完成与2013年年底。最早是叫做OpencloudDB , 后来改成MyCat。这其中有一个重要的原因 , 就是打算入驻Apache , 与Tomcat异曲同工。MyCat的前身阿里内部的Cobar框架。Cobar是阿里研发的关系型数据库分布式处理系统 , 最重要的特性就是分库分表。而MyCat则是对Cobar的一些问题进行改良 , 并开源出来的。

MyCat从诞生之初就带上了程序员桀骜不驯的标签 , 不依托于任何商业公司 , 以社区的形式进行维护。曾经的MyCat社区 , 聚集了国内大部分的IT经营 , 一度是中国最大的IT社区。MyCat产品也是在迅猛发展。从GitHub上使用者公布的案例中看到 , 在某些数量很大的系统中 , MyCat能支持处理单表单月30亿级别的数据量。

但是 , 社区化运营毕竟给产品开发带来了不稳定性。MyCat社区一度想要开发一个叫做MycloudOA的云平台产品。但是 , 目标过大的结果是团队凝聚力逐渐下降。MyCat的产品版本发展到1.6就沉寂了很长一段时间。

而现在MyCat2产品相当于是重新收拾之前MyCat留下的烂摊子 , 重整山河 , 再次出发。以下是官方公布的MyCat1.6MyCat2的功能比较。

功能

1.6

2

多语句

不支持

支持

blob

支持一部分

支持

全局二级索引

不支持

支持

任意跨库join(包含复杂查询)

catlet支持

支持

分片表与分片表JOIN查询

ER表支持

支持

关联子查询

不支持

支持一部分

分库同时分表

不支持

支持

存储过程

支持固定形式

支持更多

支持逻辑视图

不支持

支持

支持物理视图

支持

支持

批量插入

不支持

支持

执行计划管理

不支持

支持

路由注释

支持

支持

集群功能

支持

支持更多集群类型

自动hash分片算法

不支持

支持

支持第三方监控

支持mycat-web

支持普罗米斯,kafka日志等监控

流式合拼结果集

支持

支持

范围查询

支持

支持

单表映射物理表

不支持

支持

XA事务

XA

支持,事务自动恢复

支持MySQL8

需要更改mysql8的服务器配置支持

支持

虚拟表

不支持

支持

joinClustering

不支持

支持

union all语法

不支持

支持

BKAJoin

不支持

支持

优化器注释

不支持

支持

ER

支持

支持

全局序列号

支持

支持

保存点

不支持

支持

离线迁移

支持

支持(实验)

增量迁移

CRC32算法支持

BINLOG追平(实验)

安全停机

不支持

支持(实验)

HAProxy协议

不支持

支持

会话粘滞

updateselect会粘滞

updateselect会粘滞且支持设置时间

全局表插入支持全局序列号

不支持

支持

全局表插入支持主表插入自增结果作为序列号

不支持

支持

外部调用的分片算法

不支持但可定制

支持

3.2 MyCat2 原理

MyCat的工作原理其实并不复杂。就是对请求进行“拦截”。他会拦截客户端发送过来的SQL语句 , 对SQL语句做一些特定的分析 , 如分片分析、路由分析、读写分离分析、缓存分析等。然后将此SQL发往后端的真实数据库。并将返回的结果做适当的处理 , 比如结果聚合、排序等 , 最终返回给用户。

28593b6b1f7d5be70b534fe83cb7e58b

而在工作方式上 , MyCat会部署成一个MySQL服务 , 程序员只需要像操作普通的单机MySQL服务一样去操作MyCatMyCat再去完成分库分表功能。而后端的数据库产品 , 建议还是使用MySQL。当然 , MyCat2目前也在开始提交与更多数据库产品的兼容。目前提交了Oracle的兼容版本 , 但是还没有到发布版本。

3.3 MyCat 入门

MyCat2是基于Java开发的 , 所以他的运行环境是比较简单的 , 只需要安装JDK即可。接下来准备一台Linux机器 , 搭建JDK8版本。初始搭建时 , 建议在这台服务器上也搭建一个MySQL服务。这里由于MyCat官网给定的下载网址一直无法打开 , 这里直接通过github下载源码编译的方式安装的。

这里需要注意MyCat2的安装包分为基础包和环境运行的Jar包。这里通过github编译安装的是jar包。基础包从网上其他地方找到的。

基础包下载地址:https://download.csdn.net/download/qq_42335715/89469711

具体步骤如下:

  1. 通过github下载源码包

    image-20250409222433948

  2. 下载依赖包

    dnf install unzip maven git java-1.8.0-openjdk
  3. 解压软件包 , 并切换到对应目录中

    unzip Mycat2-v1.22-2022-6-25.zip -d /opt/
    cd /opt/Mycat2-v1.22-2022-6-25/
  4. 开始编译安装

    mvn clean install
  5. 编译完成之后 , 会生成需要的jar

    [root@23 Mycat2-v1.22-2022-6-25]# ll mycat2/target/mycat2-1.22-release-jar-with-dependencies.jar 
    -rw-r--r-- 1 root root 152877591 Apr  9 03:30 mycat2/target/mycat2-1.22-release-jar-with-dependencies.jar
  6. 直接通过CSDN下载mycat2的基础包

  7. 解压基础包

    ​
    [root@23 ~]# unzip mycat2.zip -d /opt/
    [root@23 ~]# ll /opt/
    total 8
    drwxr-xr-x  5 root root 4096 Apr  9 22:52 mycat2
    drwxr-xr-x 29 root root 4096 Apr  9 03:25 Mycat2-v1.22-2022-6-25
  8. 修改基础包中的文件权限

    [root@23 ~]# chmod +x /opt/mycat2/bin/*
    [root@23 ~]# ll /opt/mycat2/bin/*
    -rwxr-xr-x 1 root root  15666 Jun 22  2024 /opt/mycat2/bin/mycat
    -rwxr-xr-x 1 root root   3916 Jun 22  2024 /opt/mycat2/bin/mycat.bat
    -rwxr-xr-x 1 root root 281540 Jun 22  2024 /opt/mycat2/bin/wrapper-aix-ppc-32
    -rwxr-xr-x 1 root root 319397 Jun 22  2024 /opt/mycat2/bin/wrapper-aix-ppc-64
    -rwxr-xr-x 1 root root 253808 Jun 22  2024 /opt/mycat2/bin/wrapper-hpux-parisc-64
    -rwxr-xr-x 1 root root 140198 Jun 22  2024 /opt/mycat2/bin/wrapper-linux-ppc-64
    -rwxr-xr-x 1 root root  99401 Jun 22  2024 /opt/mycat2/bin/wrapper-linux-x86-32
    -rwxr-xr-x 1 root root 111027 Jun 22  2024 /opt/mycat2/bin/wrapper-linux-x86-64
    -rwxr-xr-x 1 root root 114052 Jun 22  2024 /opt/mycat2/bin/wrapper-macosx-ppc-32
    -rwxr-xr-x 1 root root 233604 Jun 22  2024 /opt/mycat2/bin/wrapper-macosx-universal-32
    -rwxr-xr-x 1 root root 253432 Jun 22  2024 /opt/mycat2/bin/wrapper-macosx-universal-64
    -rwxr-xr-x 1 root root 112536 Jun 22  2024 /opt/mycat2/bin/wrapper-solaris-sparc-32
    -rwxr-xr-x 1 root root 148512 Jun 22  2024 /opt/mycat2/bin/wrapper-solaris-sparc-64
    -rwxr-xr-x 1 root root 110992 Jun 22  2024 /opt/mycat2/bin/wrapper-solaris-x86-32
    -rwxr-xr-x 1 root root 204800 Jun 22  2024 /opt/mycat2/bin/wrapper-windows-x86-32.exe
    -rwxr-xr-x 1 root root 220672 Jun 22  2024 /opt/mycat2/bin/wrapper-windows-x86-64.exe
    ​
  9. 把依赖的jar复制到安排目录的lib

    [root@23 ~]# cp /opt/Mycat2-v1.22-2022-6-25/mycat2/target/mycat2-1.22-release-jar-with-dependencies.jar /opt/mycat2/lib/
  10. 启动一个MySQL服务,这里已经提前准备好了一个主从集群的Mariadb

    MyCat在启动时需要先默认指定一个数据库 , 这里就默认MySQL数据库的配置信息【端口: 3306 , 用户名: root , 密码: 123456】 , 然后创建一个名为mycat的数据库。

  11. 配置MyCat的数据源

    [root@23 ~]# cat  /opt/mycat2/conf/datasources/prototypeDs.datasource.json  
    {
        "dbType":"mysql",
        "idleTimeout":60000,
        "initSqls":[],
        "initSqlsGetConnection":true,
        "instanceType":"READ_WRITE",
        "maxCon":1000,
        "maxConnectTimeout":3000,
        "maxRetryCount":5,
        "minCon":1,
        "name":"prototypeDs",
        "password":"123456",
        "type":"JDBC",
        "url":"jdbc:mysql://192.168.71.7:3306/mysql?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8",
        "user":"root",
        "weight":0
    }
    ​
  12. 启动mycat

    [root@23 ~]# /opt/mycat2/bin/mycat start 
    Starting mycat2...
    [root@23 ~]# /opt/mycat2/bin/mycat status
    mycat2 is running (17026).
    ​

    其他额外命令

    ./mycat stop 停止
    ./mycat console 前台运行
    ./mycat install 添加到系统自动启动
    ./mycat remove 取消随系统自动启动
    ./mycat restart 重启
    ./mycat pause 暂停
    ./mycat status 查看启动状态
  13. 之后尝试连接8066端口

    [root@23 ~]# mysql -uroot -p123456 -h127.0.0.1 -P8066
    Welcome to the MariaDB monitor.  Commands end with ; or \g.
    Your MySQL connection id is 0
    Server version: 5.7.33-mycat-2.0 MariaDB Server
    ​
    Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
    ​
    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
    ​
    MySQL [(none)]> show databases;
    +--------------------+
    | `Database`         |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    +--------------------+
    3 rows in set (0.157 sec)
    ​
    MySQL [(none)]> create database t1;
    Query OK, 0 rows affected (0.357 sec)
    ​
    MySQL [(none)]> show databases;
    +--------------------+
    | `Database`         |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | t1                 |
    +--------------------+
    4 rows in set (0.041 sec)
    ​

4. MyCat 配置文件

4.1 用户信息(users)

文件位置: mycat/conf/users/{用户名}.user.json

作用: 此文件主要配置MyCat的登录用户 , 也就是连接到8066这个端口的用户信息。

打开root.user.json , 看到配置内容如下

[root@23 ~]# cat /opt/mycat2/conf/users/root.user.json 
{
    "dialect":"mysql",
    "ip":null,
    "password":"123456",
    "transactionType":"xa",
    "username":"root"
}
​

说明如下

  • dialect: 数据库(方言)类型 , 默认是mysql

  • ip: 配置白名单使用 , 默认null。如果要限制ip访问 , 则需要配置需要访问的ip

  • password: 配置MyCAT用户的密码(明文)

  • transactionType: 事务类型

    • 默认值: proxy(本地事务 , MyCat不做事务控制 , 具体的事务控制由后端的真实数据库控制 , 而且对于分布式场景下事务可能会失败 , 但是兼容性最好)

    • 可选值: xa(分布式事务 , 需要确认存储节点集群类型是否支持XA

  • username: 配置MyCAT用户的用户名

4.2 数据源(datasource)

文件位置: mycat/conf/datasources/{数据源名称}.datasource.json

作用 : 配置MyCat连接真实数据库的数据源。

具体的配置如下:

[root@23 ~]# cat /opt/mycat2/conf/datasources/prototypeDs.datasource.json 
{
    "dbType":"mysql",
    "idleTimeout":60000,
    "initSqls":[],
    "initSqlsGetConnection":true,
    "instanceType":"READ_WRITE",
    "maxCon":1000,
    "maxConnectTimeout":3000,
    "maxRetryCount":5,
    "minCon":1,
    "name":"prototypeDs",
    "password":"123456",
    "type":"JDBC",
    "url":"jdbc:mysql://192.168.71.7:3306/mysql?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8",
    "user":"root",
    "weight":0
}
​

主要配置字段如下:

  • dbType : 数据源类型

  • name : 数据源名字

  • password : 真实MySQL的密码

  • url : 真实MySQLJDBC连接地址

  • user : 真实MySQL的用户名

  • weight : 配置数据源负载均衡的使用权重

4.3 逻辑库与逻辑表(logicaltable)

文件位置: mycat/conf/schemas/{库名}.schema.json

作用: 配置MyCat里面和MySQL对应的逻辑表。

打开root.user.json , 其主要配置字段如下

[root@23 ~]# cat /opt/mycat2/conf/schemas/t1.schema.json 
{
    "customTables":{},
    "globalTables":{},
    "normalProcedures":{},
    "normalTables":{},
    "schemaName":"t1",
    "shardingTables":{},
    "views":{}
}[root@23 ~]# 
​
​

说明如下

  • customTables: 自定义表

  • globalTables: 全局表

  • normalTables: 默认表

  • schemaName: 库名

  • shardingTables: 分片表

  • targetName: 数据源名 , 也可以是集群名

4.4 序列号(sequence)

文件位置: mycat/conf/sequences/{数据库名字}_{表名字}.sequence.json。 作用 : 使用序列号的分片表 , 对应的自增主键要在建表SQL中体现。

4.5 服务器(server)

文件位置 : mycat/server.json

作用 : MyCat的服务器的配置信息 , 一般无需额外配置。端口就在这里配置的。

{
  "loadBalance": {
    "defaultLoadBalance": "BalanceRandom",
    "loadBalances": []
  },
  "mode": "local",
  "properties": {},
  "server": {
    "bufferPool": {},
    "idleTimer": {
      "initialDelay": 3,
      "period": 60000,
      "timeUnit": "SECONDS"
    },
    "ip": "0.0.0.0",
    "mycatId": 1,
    "port": 8066,
    "reactorNumber": 8,
    "tempDirectory": null,
    "timeWorkerPool": {
      "corePoolSize": 0,
      "keepAliveTime": 1,
      "maxPendingLimit": 65535,
      "maxPoolSize": 2,
      "taskTimeout": 5,
      "timeUnit": "MINUTES"
    },
    "workerPool": {
      "corePoolSize": 1,
      "keepAliveTime": 1,
      "maxPendingLimit": 65535,
      "maxPoolSize": 1024,
      "taskTimeout": 5,
      "timeUnit": "MINUTES"
    }
  }
​
}
​
​

4.6 集群clusters

目录: mycat/conf/clusters

命名规则:{集群名字}.cluster.json

主要配置内容:

[root@23 ~]# cat /opt/mycat2/conf/clusters/prototype.cluster.json 
{
    "clusterType":"MASTER_SLAVE",
    "heartbeat":{
        "heartbeatTimeout":1000,
        "maxRetry":3,
        "minSwitchTimeInterval":300,
        "slaveThreshold":0
    },
    "masters":[
        "prototypeDs"
    ],
    "maxCon":200,
    "name":"prototype",
    "readBalanceType":"BALANCE_ALL",
    "switchType":"SWITCH"
}[root@23 ~]# 
​

主要字段含义:

  • clusterType: 集群类型

    • SIGNLE_NODE: 单一节点

    • MASTER_SLAVE:普通主从

    • 另外针对高可用集群可支持MHAMGR

  • readBalanceType: 查询负载均衡策略,可选值

    • BALANCE_ALL(默认值): 获取集群中所有数据源

    • BALANCE_ALL_READ:获取集群中允许读的数据源

    • BALANCE_READ_WRITE :获取集群中允许读写的数据源, 但允许读的数据源优先

    • BALANCE_NODE : 获取集群中允许写数据源, 即主节点中选择

  • switchType : 切换类型

    • NOT_SWITCH : 不进行主从切换

    • SWITCH : 进行主从切换

  • masters: 主从集群中的主库名字。从库用replicas配置

5. 注释配置

MyCat需要通过注释的方式进行配置 , 其语法就是如此 , 可能是为了和数据库的执行语句做区分吧。

5.1 配置用户

  1. 创建用户

    /*+ mycat:createUser{
      "username":"admin",
      "password":"admin",
      "transactionType":"xa"
    } */
  2. 删除用户

    /*+ mycat:dropUser{
      "username":"admin"} */
  3. 显示用户

    /*+ mycat:showUsers */

4.2 配置数据源

  1. 创建数据源

    只需要设置namepasswordtypeurluser几个字段即可

    /*+ mycat:createDataSource{
      "dbType":"mysql",
      "idleTimeout":60000,
      "initSqls":[],
      "initSqlsGetConnection":true,
      "instanceType":"READ_WRITE",
      "maxCon":1000,
      "maxConnectTimeout":3000,
      "maxRetryCount":5,
      "minCon":1,
      "name":"数据源名称",
      "password":"数据库密码",
      "type":"JDBC",
      "url":"jdbc:mysql://127.0.0.1:3306/数据库名?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
      "user":"数据库用户名",
      "weight":0
    } */;
  2. 删除数据源

    /*+ mycat:dropDataSource{
     "name":"数据源名称"
    } */;
  3. 显示数据源

    /*+ mycat:showDataSources{} */

5.3 配置集群

  1. 创建集群

    /*! mycat:createCluster{
      "clusterType":"MASTER_SLAVE",
      "heartbeat":{
        "heartbeatTimeout":1000,
        "maxRetry":3,
        "minSwitchTimeInterval":300,
        "slaveThreshold":0
      },
      "masters":[
        "dc1" //主节点数据源名称
      ],
      "maxCon":2000,
      "name":"c0",
      "readBalanceType":"BALANCE_ALL",
      "replicas":[
        "dc2" //从节点数据源名称
      ],
      "switchType":"SWITCH"
    } */;
  2. 删除集群

    /*! mycat:dropCluster{
      "name":"c0"
    } */;
  3. 显示集群

    /*+ mycat:showClusters{} */

5.4 重置配置

把配置的信息重置为默认值

/*+ mycat:resetConfig{} */

6. MyCat2 配置读写分离

  1. 为主节点M1创建数据源

    由于其他参数都是默认的 , 无需修改 , 这里直接省略 , 只写主要的参数

    /*+ mycat:createDataSource{
      "dbType":"mysql",
      "name":"m1",
      "password":"123456",
      "type":"JDBC",
      "url":"jdbc:mysql://127.0.0.1:3307/test_demo?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
      "user":"root"
    } */;
  2. 从节点M1S1创建数据源

    /*+ mycat:createDataSource{
      "dbType":"mysql",
      "name":"m1s1",
      "password":"123456",
      "type":"JDBC",
      "url":"jdbc:mysql://127.0.0.1:3308/test_demo?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8",
      "user":"root"
    } */;

    同时在文件夹中也生成了对应的配置文件

    1746338-20231126215839286-51296243

  3. 为主从节点搭建MyCat集群 , 在MyCat将主从节点关联起来

    /*! mycat:createCluster{
      "masters":[
        "m1"
      ],
      "name":"zxh_test",
      "replicas":[
        "m1s1" 
      ]
    } */;

    指定了主节点和从节点 , 集群名称是zxh_test , 查询结果

    可以看出 , 写操作只在m1中允许 , 读操作可以在m1m1s1之间轮询。

  4. 创建逻辑库 , 用于指向集群

    虽然上面已经配置好了集群 , 但如何去操作这个集群呢?这时可以创建一个数据库 , 这个库是逻辑库 , 关联到集群 , 那么后续只需要对这个逻辑库操作 , 就会转发到集群 , 集群再转发到主节点和从节点的逻辑数据源 , 这些逻辑数据源再分发到真实的数据库。这就是MyCat2的读写分离。

    CREATE DATABASE test_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

    创建数据库(建议和真实数据库的库名一致)

  5. 指定逻辑库的集群名称MyCat2暂时还未提供命令方式关联集群名称 , 这就需要手动打开配置文件conf/schemas/test_demo.schema.json , 在里面添加

    "targetName":"zxh_test",

    1746338-20231126221649166-1424076058

    让逻辑库和集群进行对应 , 保存后重启MyCat2服务。

  6. 创建表进行测试

    MyCat中创建表并添加数据进行测试

    use test_demo;
    CREATE TABLE `sys_enterprise` (
      `id` int NOT NULL,
      `name` varchar(100) DEFAULT NULL,
        `ent_no` varchar(20) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    insert into sys_enterprise values(1,"武汉小米","A001");
    insert into sys_enterprise values(2,"深圳腾讯小米","C001");
    select * from sys_enterprise;

    在真实的数据库中查询

  7. 手动修改从节点真实库的数据(现实场景一般不允许直接修改从库数据) , 在MyCat2中查询 , 发现查询的数据会出现不一样的情况 , 这更好的体现了读操作是在主节点和从节点之间轮询。

    从这里可以看出 , 并未对真实数据库操作 , 仅仅只是在MyCat进行操作 , 执行的SQL已按预期进行了读写操作 , 实现了读写分离。

7. 分库分表

编号

角色

IP 地址

机器名

1

Master1

192.168.95.101

master1

2

Slave1

192.168.95.201

slave1

3

Master2

192.168.95.102

master2

4

Slave2

192.168.95.202

slave2

5

mycat

192.168.95.8 | guest

一个数据库由很多表构成 , 每个表对应着不同的业务 , 垂直切分是指按照业务不同将表进行分类 , 分散到不同的数据库上面 , 这样也就将数据或者说压力分担到不同的数据库上面 , 如下图 :

7.1 如何分库

分库原则 : 有紧密关联关系的表应该在一个库里 , 没有关联关系的表可以分到不同的库里。

# 客户表rows : 20万
CREATE TABLE customer(
    id INT AUTO_INCREMENT,
    NAME VARCHAR(200),
    PRIMARY KEY(id)
);
​
# 订单表rows : 600万
CREATE TABLE orders(
    id INT AUTO_INCREMENT,
    order_type INT,
    customer_id INT,
    amount DECIMAL(10,2),
    PRIMARY KEY(id)
);
​
# 订单详细表rows : 600万
CREATE TABLE orders_detail(
    id INT AUTO_INCREMENT,
    detail VARCHAR(2000),
    order_id INT,
    PRIMARY KEY(id)
);
​
# 订单状态字典表rows : 20
CREATE TABLE dict_order_type(
    id INT AUTO_INCREMENT,
    order_type VARCHAR(200),
    PRIMARY KEY(id)
);

以上四个表如何分库?客户表分在一个数据库 , 另外三张都需要关联查询 , 分在另外一个数据库。

7.2 如何分表

  1. 选择要拆分的表

    MySQL 单表存储数据条数是有瓶颈的 , 单表达到 1000 万条数据就达到了瓶颈 , 会影响查询效率 , 需要进行水平拆分(分表)进行优化。

    例如 : 例子中的 ordersorders\_detail 都已经达到 600 万行数据 , 需要进行分表优化。

  2. 分表字段

    orders 表为例 , 可以根据不同字段进行分表。

    编号

    分表字段

    效果

    1

    id(主键、或创建时间)

    查询订单注重时效 , 历史订单被查询的次数少 , 如此分片会造成一个节点访问多 , 一个访问少 , 不平均。

    2

    customer_id(客户 id

    根据客户 id 去分 , 两个节点访问平均 , 一个客户的所有订单都在同一个节点。

7.3 实现分库分表

Mycat2 的一大优势就是可以在终端直接创建数据源、集群、库表 , 并在创建时指定分库、分表。与 1.6 版本比大大简化了分库分表的操作。

7.3.1 配置数据源

很多网上的资料都没有加 url 参数 , 我实验时经常遇到各种参数问题 , 所以我在做实验的时候 , 加上了这些参数 , 避免报错 :

# 第一个写库
/*+ mycat:createDataSource{"name":"dw0","url":"jdbc:mysql://192.168.95.101:3306/mysql?useJDBCCompliantTimezoneShift=true&useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true","user":"root","password":"WelC0me168!"} */;
​
# 第一个读库
/*+ mycat:createDataSource{"name":"dr0","url":"jdbc:mysql://192.168.95.201:3306/mysql?useJDBCCompliantTimezoneShift=true&useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true","user":"root","password":"WelC0me168!"} */;
​
# 第二个写库
/*+ mycat:createDataSource{"name":"dw1","url":"jdbc:mysql://192.168.95.102:3306/mysql?useJDBCCompliantTimezoneShift=true&useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true","user":"root","password":"WelC0me168!"} */;
​
# 第二个读库
/*+ mycat:createDataSource{"name":"dr1","url":"jdbc:mysql://192.168.95.202:3306/mysql?useJDBCCompliantTimezoneShift=true&useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true","user":"root","password":"WelC0me168!"} */;

生成下面 4 个数据源文件 :

[root@guest mycat]# ll conf/datasources/d*
-rw-r--r-- 1 root root 519 Dec 31 04:09 conf/datasources/dr0.datasource.json
-rw-r--r-- 1 root root 519 Dec 31 04:09 conf/datasources/dr1.datasource.json
-rw-r--r-- 1 root root 519 Dec 31 04:08 conf/datasources/dw0.datasource.json
-rw-r--r-- 1 root root 519 Dec 31 04:09 conf/datasources/dw1.datasource.json

7.3.2、配置集群

# 在mycat2终端输入
# 配置第一组集群
/*! mycat:createCluster{"name":"c0","masters":["dw0"],"replicas":["dr0"]}*/;
​
# 配置第二组集群
/*! mycat:createCluster{"name":"c1","masters":["dw1"],"replicas":["dr1"]}*/;

生成下面 2 个集群文件 :

[root@guest mycat]# ll conf/clusters/c*
-rw-r--r-- 1 root root 312 Dec 31 04:23 conf/clusters/c0.cluster.json
-rw-r--r-- 1 root root 312 Dec 31 04:23 conf/clusters/c1.cluster.json

这里集群名 c0 , c1MyCat2 默认支持的分片集群名字。不建议修改集群名字。按照使用习惯 , 接下来要配置逻辑库 , 然后逻辑库文件 xxx.schema.json 中指定目标集群。但是 mycat2 为了简化配置 , 在实现分库分表时 , 直接创建逻辑库和表的时候 , 指定规则 , 最终逻辑库文件会自动找到 c0c1、...... 集群进行配置。

7.3.3 配置全局表

全局表表示在所有数据分片上都相同的表。比如字典表。

# 添加数据库db1
CREATE DATABASE db1;
​
# 在建表语句中加上关键字 BROADCAST(广播 , 即为全局表)
CREATE TABLE db1.`travelrecord` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `user_id` varchar(100) DEFAULT NULL,
    `traveldate` date DEFAULT NULL,
    `fee` decimal(10,0) DEFAULT NULL,
    `days` int DEFAULT NULL,
    `blob` longblob,
    PRIMARY KEY (`id`),
    KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 BROADCAST;

配置完成后的全局表会写入到 mycat/conf/schemas/db1.schema.json 文件中。这样下次启动服务时就能够初始化表结构。

[root@guest mycat]# cat conf/schemas/db1.schema.json 
{
        "customTables":{},
        "globalTables":{
                "travelrecord":{
                        "broadcast":[
                                {
                                        "targetName":"c0"
                                },
                                {
                                        "targetName":"c1"
                                }
                        ],
                        "createTableSQL":"CREATE TABLE db1.`travelrecord` (\n\t`id` bigint NOT NULL AUTO_INCREMENT,\n\t`user_id` varchar(100) DEFAULT NULL,\n\t`traveldate` date DEFAULT NULL,\n\t`fee` decimal(10, 0) DEFAULT NULL,\n\t`days` int DEFAULT NULL,\n\t`blob` longblob,\n\tPRIMARY KEY (`id`),\n\tKEY `id` (`id`)\n) BROADCAST ENGINE = InnoDB CHARSET = utf8"
                }
        },
        "normalProcedures":{},
        "normalTables":{},
        "schemaName":"db1",
        "shardingTables":{},
        "views":{}
}

然后可以插入几条数据进行测试 :

INSERT INTO db1.`travelrecord` VALUES (1, "202212310001", CURDATE(),10,10,'ABC');
INSERT INTO db1.`travelrecord` VALUES (2, "202212310002", CURDATE(),10,10,'ABC');
INSERT INTO db1.`travelrecord` VALUES (3, "202212310003", CURDATE(),10,10,'ABC');
INSERT INTO db1.`travelrecord` VALUES (4, "202212310004", CURDATE(),10,10,'ABC');
INSERT INTO db1.`travelrecord` VALUES (5, "202212310005", CURDATE(),10,10,'ABC');
INSERT INTO db1.`travelrecord` VALUES (6, "202212310006", CURDATE(),10,10,'ABC');

经测试 , 2 套一主一从集群中 , 每个 mysql 服务都有 db1.travelrecord 表 , 并且有完整的 6 条测试数据。

7.3.4、创建分片表(分库分表)

分片表表示逻辑表中的数据会分散保存到多个数据分片中。

# 在 Mycat 终端直接运行建表语句进行数据分片。
# 数据库分片规则 , 表分片规则 , 以及各分多少片。
# 核心就是后面的分片规则。 dbpartition表示分库规则 , tbpartition表示分表规则。而mod_hash表示按照customer_id字段取模进行分片。
# 意思是切2份到两个数据库集群 , 每个数据库集群切1份表(当然也可以切成多张表)
CREATE TABLE db1.orders(
    id BIGINT NOT NULL AUTO_INCREMENT,
    order_type INT,
    customer_id INT,
    amount DECIMAL(10,2),
    PRIMARY KEY(id),
    KEY `id` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8 dbpartition BY mod_hash(customer_id) tbpartition BY mod_hash(customer_id) tbpartitions 1 dbpartitions 2;

接下来可以往测试表中插入部分测试数据进行验证。

INSERT INTO orders(id,order_type,customer_id,amount) VALUES (1,101,100,100100);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES (2,101,100,100300);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES (3,101,101,120000);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES (4,101,101,103000);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES (5,102,101,100400);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES (6,102,100,100020);
​
mysql> SELECT * FROM orders;
+----+------------+-------------+-----------+
| id | order_type | customer_id | amount    |
+----+------------+-------------+-----------+
|  1 |        101 |         100 | 100100.00 |
|  2 |        101 |         100 | 100300.00 |
|  6 |        102 |         100 | 100020.00 |
|  3 |        101 |         101 | 120000.00 |
|  4 |        101 |         101 | 103000.00 |
|  5 |        102 |         101 | 100400.00 |
+----+------------+-------------+-----------+
6 rows in set (0.09 sec)

此时 , 可以看一下自动生成的 schema 信息 :

[root@guest mycat]# cat conf/schemas/db1.schema.json
{
    "customTables":{},
    "globalTables":{
        "travelrecord":{
            "broadcast":[
                {
                    "targetName":"c0"
                },
                {
                    "targetName":"c1"
                }
            ],
            "createTableSQL":"CREATE TABLE db1.`travelrecord` (\n\t`id` bigint NOT NULL AUTO_INCREMENT,\n\t`user_id` varchar(100) DEFAULT NULL,\n\t`traveldate` date DEFAULT NULL,\n\t`fee` decimal(10, 0) DEFAULT NULL,\n\t`days` int DEFAULT NULL,\n\t`blob` longblob,\n\tPRIMARY KEY (`id`),\n\tKEY `id` (`id`)\n) BROADCAST ENGINE = InnoDB CHARSET = utf8"
        }
    },
    "normalProcedures":{},
    "normalTables":{},
    "schemaName":"db1",
    "shardingTables":{
        "orders":{
            "createTableSQL":"CREATE TABLE db1.orders (\n\tid BIGINT NOT NULL AUTO_INCREMENT,\n\torder_type INT,\n\tcustomer_id INT,\n\tamount DECIMAL(10, 2),\n\tPRIMARY KEY (id),\n\tKEY `id` (`id`)\n) ENGINE = INNODB CHARSET = utf8\nDBPARTITION BY mod_hash(customer_id) DBPARTITIONS 2\nTBPARTITION BY mod_hash(customer_id) TBPARTITIONS 1",
            "function":{
                "properties":{
                    "dbNum":"2",
                    "mappingFormat":"c${targetIndex}/db1_${dbIndex}/orders_${index}",
                    "tableNum":"1",
                    "tableMethod":"mod_hash(customer_id)",
                    "storeNum":2,
                    "dbMethod":"mod_hash(customer_id)"
                }
            },
            "shardingIndexTables":{}
        }
    },
    "views":{}
}

最后查看一下切分的结果 :

7.3.5 创建 ER 表

关联表也称为绑定表或者 ER 表。表示数据逻辑上有关联性的两个或多个表 , 例如订单和订单详情表。对于关联表 , 通常希望他们能够有相同的分片规则 , 这样在进行关联查询时 , 能够快速定位到同一个数据分片中。

# 在 Mycat 终端直接运行建表语句进行数据分片
CREATE TABLE orders_detail(
    id BIGINT NOT NULL AUTO_INCREMENT,
    detail VARCHAR(2000),
    order_id INT,
    PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8 dbpartition BY mod_hash(order_id) tbpartition BY mod_hash(order_id) tbpartitions 1 dbpartitions 2;
​
INSERT INTO orders_detail(id,detail,order_id) VALUES(1,'detail1',1);
INSERT INTO orders_detail(id,detail,order_id) VALUES(2,'detail1',2);
INSERT INTO orders_detail(id,detail,order_id) VALUES(3,'detail1',3);
INSERT INTO orders_detail(id,detail,order_id) VALUES(4,'detail1',4);
INSERT INTO orders_detail(id,detail,order_id) VALUES(5,'detail1',5);
INSERT INTO orders_detail(id,detail,order_id) VALUES(6,'detail1',6);

对比一下数据 , 会发现由于我们分片字段不同 , 导致 ER 分片表条目不对应 :

上述两表具有相同的分片算法 , 但是分片字段不相同 , mycat2 在涉及这两个表的 join 分片字段等价关系的时候可以完成 join 的下推。mycat2 无需指定 ER 表 , 是自动识别的 , 具体看分片算法的接口。

查看配置的表是否具有 ER 关系 , 使用 :

mysql> /*+ mycat:showErGroup{}*/;
+---------+------------+---------------+
| groupId | schemaName | tableName     |
+---------+------------+---------------+
| 0       | db1        | orders        |
| 0       | db1        | orders_detail |
+---------+------------+---------------+
2 rows in set (0.00 sec)

group\_id 表示相同的组 , 该组中的表具有相同的存储分布 , ER 关系暂时不检查分区的目标是否相同 , 仅使用分片算法判断是否满足 ER 关系。

mysql> SELECT * FROM orders o INNER JOIN orders_detail od ON od.order_id=o.id;
+----+------------+-------------+-----------+-----+---------+----------+
| id | order_type | customer_id | amount    | id0 | detail  | order_id |
+----+------------+-------------+-----------+-----+---------+----------+
|  1 |        101 |         100 | 100100.00 |   1 | detail1 |        1 |
|  2 |        101 |         100 | 100300.00 |   2 | detail1 |        2 |
|  3 |        101 |         101 | 120000.00 |   3 | detail1 |        3 |
|  4 |        101 |         101 | 103000.00 |   4 | detail1 |        4 |
|  5 |        102 |         101 | 100400.00 |   5 | detail1 |        5 |
|  6 |        102 |         100 | 100020.00 |   6 | detail1 |        6 |
+----+------------+-------------+-----------+-----+---------+----------+
6 rows in set (0.08 sec)


参考链接

MySQL之分库分表利器MyCat(一)

MyCat2测试实战 -- 王者归来的故事_mycat2.0和mycat1.6区别-CSDN博客

MyCat2 , 你会了吗? - 钟小嘿 - 博客园

第 17 章 数据库中间件 Mycat2


熊熊