《SQL与数据库基础》21. 分库分表(一)
阅读原文时间:2023年08月29日阅读:1

目录


本文以 MySQL 为例。以MyCat数据库中间件为例,通过MyCat来完成分库分表操作。

分库分表(一)

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

  • IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。请求数据太多,带宽不够,网络出现IO瓶颈。
  • CPU瓶颈:排序、分组、连接查询、聚合统计等SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。

为了解决上述问题,需要对数据库进行分库分表处理。

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

分库分表的形式主要有两种:垂直拆分水平拆分

根据拆分的粒度,一般又分为分库分表

组成的拆分策略最终如下:

垂直分库

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

特点:

  • 每个库的表结构都不一样。
  • 每个库的数据也不一样。
  • 所有库的并集是全量数据。

垂直分表

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

特点:

  • 每个表的结构都不一样。
  • 每个表的数据也不一样,一般通过一列(主键/外键)关联。
  • 所有表的并集是全量数据。

水平分库

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

特点:

  • 每个库的表结构都一样。
  • 每个库的数据都不一样。
  • 所有库的并集是全量数据。

水平分表

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

特点:

  • 每个表的表结构都一样。
  • 每个表的数据都不一样。
  • 所有表的并集是全量数据。

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

shardingJDBC:基于AOP原理,在应用程序中对本地执行的SQL进行拦截,解析、改写、路由处理。需要自行编码配置实现,只支持java语言,性能较高。

MyCat:数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不如shardingJDBC。

所谓中间件,是一类连接软件组件和应用的计算机软件,以便软件各部件之间的通信。

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

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

优势:

  • 性能可靠稳定
  • 强大的技术团队
  • 体系完善
  • 社区活跃

概念介绍

在MyCat的整体结构中,分为两个部分:逻辑结构物理结构

MyCat的逻辑结构主要负责逻辑库逻辑表分片规则分片节点等逻辑结构的处理,而具体的数据还是存储在物理结构,也就是数据库服务器中存储的。

环境准备

MyCat是采用java语言开发的开源数据库中间件,支持Windows和Linux运行环境。要使用MyCat,需要在服务器中准备如下配置:

  • MySQL
  • JDK
  • Mycat

本文以Linux环境为例。

服务器

安装软件

说明

192.168.200.210

JDK、MyCat

MyCat中间件服务器

192.168.200.210

MySQL

分片服务器

192.168.200.213

MySQL

分片服务器

192.168.200.214

MySQL

分片服务器

目录介绍

bin:存放可执行文件,用于启动停止mycat。

conf:存放mycat的配置文件。

lib:存放mycat的项目依赖包(jar)。

logs:存放mycat的日志文件。

假设有如下需求

由于 tb_order 表中数据量很大,磁盘IO及容量都到达了瓶颈,现在需要对 tb_order 表进行数据分片,分为三个数据节点,每一个节点主机位于不同的服务器上。具体结构参考下图:

环境准备:

且在上述3台数据库中创建数据库 db01 。

配置

分片配置(schema.xml)

在schema.xml中配置逻辑库、逻辑表、数据节点、节点主机等相关信息。

具体的配置如下:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">

<mycat:schema xmlns:mycat="http://io.mycat/">

    <schema name="DB01" checkSQLschema="true" sqlMaxLimit="100">
        <table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long"/>
    </schema>

    <dataNode name="dn1" dataHost="dhost1" database="db01" />
    <dataNode name="dn2" dataHost="dhost2" database="db01" />
    <dataNode name="dn3" dataHost="dhost3" database="db01" />

    <dataHost name="dhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="jdbc:mysql://192.168.200.210:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
    </dataHost>

    <dataHost name="dhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="jdbc:mysql://192.168.200.213:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
    </dataHost>

    <dataHost name="dhost3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="jdbc:mysql://192.168.200.214:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
    </dataHost>

</mycat:schema>

分片配置(server.xml)

配置MyCat的用户与用户权限信息:

需要在server.xml中配置用户名、密码,以及用户的访问权限信息,具体的配置如下(只找到相应部分):

<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">DB01</property>

    <!-- 表级 DML 权限设置 -->
    <!--
    <privileges check="true">
        <schema name="DB01" dml="0110" >
            <table name="TB_ORDER" dml="1110"></table>
        </schema>
    </privileges>
    -->
</user>

<user name="user">
    <property name="password">123456</property>
    <property name="schemas">DB01</property>
    <property name="readOnly">true</property>
</user>

上述的配置表示,定义了两个用户 root 和 user ,这两个用户都可以访问 DB01 这个逻辑库,访问密码都是123456,但root用户访问DB01逻辑库,既可以读也可以写,user用户访问DB01逻辑库只可以读。

测试

配置完毕后,先启动涉及到的3台分片服务器,然后启动MyCat服务器。

切换到Mycat的安装目录,执行如下指令

启动MyCat

bin/mycat start

停止MyCat

bin/mycat stop

MyCat启动之后,占用端口号 8066。

启动完毕之后,可以查看logs目录下的 wrapper.log(启动日志),查看Mycat是否启动成功。

连接测试:

# 连接并登陆MyCat
mysql -h 192.168.200.210 -P 8066 -uroot -p123456

可以看到是通过MySQL的指令来连接的MyCat,因为MyCat在底层是模拟MySQL的协议。

数据测试:

CREATE TABLE tb_order (
    id BIGINT(20) NOT NULL,
    title VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
) ENGINE = INNODB DEFAULT CHARSET = utf8;

INSERT INTO tb_order(id,title)
VALUES(1, 'goods1');
      (2, 'goods2');
      (3, 'goods3');
      (5000000, 'goods5000000');
      (10000000, 'goods10000000');
      (10000001, 'goods10000001');
      (15000000, 'goods15000000');
      (15000001, 'goods15000001');

数据落在哪一个分片服务器是由逻辑表配置时的一个参数 rule 决定的,这个参数配置的就是分片规则,关于分片规则的配置,在后面会讲解。

三种配置文件:

  • schema.xml
  • rule.xml
  • server.xml

schema.xml

schema.xml 作为MyCat中最重要的配置文件之一,涵盖了MyCat的逻辑库逻辑表分片规则分片节点数据源的配置。

主要包含以下三组标签:

  • schema标签
  • datanode标签
  • datahost标签

schema标签

schema定义逻辑库

schema 标签用于定义MyCat实例中的逻辑库,一个MyCat实例中,可以有多个逻辑库,可以通过 schema 标签来划分不同的逻辑库。

MyCat中的逻辑库的概念,等同于MySQL中的database概念,需要操作某个逻辑库下的表时,也需要切换逻辑库(use xxx)。

核心属性:

  • name:指定自定义的逻辑库库名
  • checkSQLschema:在SQL语句操作时指定了数据库名称,执行时是否自动去除。true:自动去除;false:不自动去除
  • sqlMaxLimit:如果未指定limit进行查询,列表查询模式查询多少条记录

schema中的table定义逻辑表

table 标签定义了MyCat中逻辑库schema下的逻辑表,所有需要拆分的表都需要在table标签中定义 。

核心属性:

  • name:定义逻辑表表名,在该逻辑库下唯一
  • dataNode:定义逻辑表所属的dataNode,该属性需要与dataNode标签中name对应;多个dataNode逗号分隔
  • rule:分片规则的名字,分片规则名字是在rule.xml中定义的
  • primaryKey:逻辑表对应真实表的主键
  • type:逻辑表的类型,目前逻辑表只有全局表和普通表,如果未配置,就是普通表。全局表,配置为 global

dataNode标签

dataNode标签定义了MyCat中的数据节点,也就是数据分片。一个dataNode标签就是一个独立的数据分片。

核心属性:

  • name:定义数据节点名称
  • dataHost:数据库实例主机名称,引用自 dataHost 标签中 name 属性
  • database:定义分片所属数据库

dataHost标签

该标签在MyCat逻辑库中作为底层标签存在,直接定义了具体的数据库实例、读写分离、心跳语句

核心属性:

  • name:唯一标识,供上层标签使用
  • maxCon:最大连接数
  • minCon:最小连接数
  • balance:负载均衡策略,取值 0,1,2,3
  • writeType:写操作分发方式(0:写操作转发到第一个writeHost,第一个挂了,则切换到第二个;1:写操作随机分发到配置的writeHost)
  • dbDriver:数据库驱动,支持 native、jdbc

rule.xml

rule.xml中定义所有拆分表的规则,在使用过程中可以灵活的使用分片算法,或者对同一个分片算法使用不同的参数。它让分片过程可配置化。主要包含两类签:tableRule、Function。

server.xml

server.xml配置文件包含了MyCat的系统配置信息,主要有两个重要的标签:system、user。

system标签

主要配置MyCat中的系统配置信息。对应的系统配置项及其含义如下:

属性

取值

含义

charset

utf8

设置Mycat的字符集,字符集需要与MySQL的字符集保持一致

nonePasswordLogin

0,1

0为需要密码登陆、1为不需要密码登陆。默认为0,设置为1则需要指定默认账户

useHandshakeV10

0,1

使用该选项主要的目的是为了能够兼容高版本的jdbc驱动,是否采用HandshakeV10Packet来与client进行通信。1:是;0:否

useSqlStat

0,1

开启SQL实时统计,1为开启,0为关闭。开启之后,MyCat会自动统计SQL语句的执行情况。mysql -h 127.0.0.1 -P 9066 -u root -p,查看MyCat执行的SQL、执行效率比较低的SQL、SQL的整体执行情况、读写比例等。(SHOW @@sql;、 SHOW @@sql.slow;、 SHOW @@sql.sum;)

useGlobleTableCheck

0,1

是否开启全局表的一致性检测。1为开启,0为关闭

sqlExecuteTimeout

1000

SQL语句执行的超时时间,单位为 s

sequnceHandlerType

0,1,2

用来指定Mycat全局序列类型。0 为本地文件,1 为数据库方式,2 为时间戳列方式。默认使用本地文件方式,文件方式主要用于测试

sequnceHandlerPattern

正则表达式

必须带有MYCATSEQ或者mycatseq进入序列匹配流程。注意MYCATSEQ_有空格的情况

subqueryRelationshipCheck

true,false

子查询中存在关联查询的情况下,检查关联字段中是否有分片字段。默认 false

useCompression

0,1

开启mysql压缩协议。0:关闭;1:开启

fakeMySQLVersion

5.5,5.6

设置模拟的MySQL版本号

defaultSqlParser

由于MyCat的最初版本使用了FoundationDB的SQL解析器,在MyCat1.3后增加了Druid解析器,所以要设置defaultSqlParser属性来指定默认的解析器。解析器有两个:druidparser 和 fdbparser,在MyCat1.4之后,默认是druidparser,fdbparser已经废除了

processors

1,2,…

指定系统可用的线程数量,默认值为CPU核心 x 每个核心运行线程数量;processors 会影响processorBufferPool,processorBufferLocalPercent,processorExecutor属性,所以,在性能调优时,可以适当地修改processors值

processorBufferChunk

指定每次分配Socket Direct Buffer。默认值为4096字节,也会影响BufferPool长度,如果一次性获取字节过多而导致buffer不够用,则会出现警告。可以调大该值

processorExecutor

指定NIOProcessor上共享businessExecutor固定线程池的大小;MyCat把异步任务交给 businessExecutor 线程池。在新版本的MyCat中这个连接池使用频次不高,可以适当地把该值调小

packetHeaderSize

指定MySQL协议中的报文头长度,默认4个字节

maxPacketSize

指定MySQL协议可以携带的数据最大大小,默认值为16M

idleTimeout

指定连接的空闲时间的超时长度。如果超时,将关闭资源并回收,默认30分钟

txIsolation

1,2,3,4

初始化前端连接的事务隔离级别。默认为REPEATED_READ,对应数字为3。READ_UNCOMMITED=1;READ_COMMITTED=2;REPEATED_READ=3;SERIALIZABLE=4

sqlExecuteTimeout

执行SQL的超时时间。如果SQL语句执行超时,将关闭连接,默认300秒

serverPort

端口号

定义MyCat的使用端口。默认8066

managerPort

端口号

定义MyCat的管理端口。默认9066

user标签

配置MyCat中的用户访问密码,以及用户针对于逻辑库、逻辑表的权限信息。具体的权限描述方式及配置说明如下:

在测试权限操作时,只需要将 privileges 标签的注释取消。在 privileges 下的 schema 标签中配置的 dml 属性是逻辑库的权限。在 privileges 的 schema 下的 table 标签中配置的 dml 属性是逻辑表的权限。

垂直拆分示例

场景

在业务系统中,涉及以下表结构,但是由于用户与订单每天都会产生大量的数据,单台服务器的数据存储及处理能力有限,可以对数据库表进行拆分,原有的数据库表如下:

现在考虑将其进行垂直分库操作,将商品相关的表拆分到一个数据库服务器,订单表拆分到一个数据库服务器,用户及省市区表拆分到一个服务器。最终结构如下:

准备

准备三台服务器,IP地址如图所示:

且在192.168.200.210,192.168.200.213,192.168.200.214上面创建数据库shopping。

配置

schema.xml

<schema name="SHOPPING" checkSQLschema="true" sqlMaxLimit="100">
    <table name="tb_goods_base" dataNode="dn1" primaryKey="id" />
    <table name="tb_goods_brand" dataNode="dn1" primaryKey="id" />
    <table name="tb_goods_cat" dataNode="dn1" primaryKey="id" />
    <table name="tb_goods_desc" dataNode="dn1" primaryKey="goods_id" />
    <table name="tb_goods_item" dataNode="dn1" primaryKey="id" />
    <table name="tb_order_item" dataNode="dn2" primaryKey="id" />
    <table name="tb_order_master" dataNode="dn2" primaryKey="order_id" />
    <table name="tb_order_pay_log" dataNode="dn2" primaryKey="out_trade_no" />
    <table name="tb_user" dataNode="dn3" primaryKey="id" />
    <table name="tb_user_address" dataNode="dn3" primaryKey="id" />
    <table name="tb_areas_provinces" dataNode="dn3" primaryKey="id"/>
    <table name="tb_areas_city" dataNode="dn3" primaryKey="id"/>
    <table name="tb_areas_region" dataNode="dn3" primaryKey="id"/>
</schema>

<dataNode name="dn1" dataHost="dhost1" database="shopping" />
<dataNode name="dn2" dataHost="dhost2" database="shopping" />
<dataNode name="dn3" dataHost="dhost3" database="shopping" />

<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="master" url="jdbc:mysql://192.168.200.210:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
</dataHost>

<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="master" url="jdbc:mysql://192.168.200.213:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
</dataHost>

<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="master" url="jdbc:mysql://192.168.200.214:3306?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
</dataHost>

server.xml

<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">SHOPPING</property>
    <!-- 表级 DML 权限设置 -->
    <!--
    <privileges check="true">
        <schema name="DB01" dml="0110" >
            <table name="TB_ORDER" dml="1110"></table>
        </schema>
    </privileges>
    -->
</user>

    <user name="user">
        <property name="password">123456</property>
        <property name="schemas">SHOPPING</property>
        <property name="readOnly">true</property>
</user>

全局表

将表结构及对应的测试数据导入之后,可以检查一下各个数据库服务器中的表结构分布情况。检查是否和准备工作中规划的服务器一致。

对于省、市、区/县表tb_areas_provinces,tb_areas_city,tb_areas_region,是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。

修改schema.xml中的逻辑表的配置,修改 tb_areas_provinces、tb_areas_city、tb_areas_region 三个逻辑表,增加 type 属性,配置为global,就代表该表是全局表,就会在所涉及到的dataNode中创建该表。对于当前配置来说,也就意味着所有的节点中都有该表了。

<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>

水平拆分示例

场景

在业务系统中,有一张日志表,业务系统每天都会产生大量的日志数据,单台服务器的数据存储及处理能力是有限的,可以对数据库表进行拆分。

准备

准备三台服务器,具体的结构如下:

配置

schema.xml

<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100">
    <table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" />
</schema>

<dataNode name="dn4" dataHost="dhost1" database="testdb" />
<dataNode name="dn5" dataHost="dhost2" database="testdb" />
<dataNode name="dn6" dataHost="dhost3" database="testdb" />

tb_log表最终落在3个节点中,分别是 dn4、dn5、dn6 ,而具体的数据分别存储在 dhost1、dhost2、dhost3的testdb数据库中。

server.xml

<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">SHOPPING,TESTDB</property>

    <!-- 表级 DML 权限设置 -->
    <!--
    <privileges check="true">
        <schema name="DB01" dml="0110" >
            <table name="TB_ORDER" dml="1110"></table>
        </schema>
    </privileges>
    -->
</user>

配置root用户既可以访问 SHOPPING 逻辑库,又可以访问 TESTDB 逻辑库。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器