摘要:本文围绕分库分表展开,先分析单库性能瓶颈,介绍垂直与水平拆分策略及实现技术,再详述 MyCat 中间件的概述、环境准备、目录结构,讲解其入门配置与测试,深入说明核心配置文件,最后演示垂直和水平拆分实操,为数据库性能优化提供方案。
思维导图
1. 介绍
1.1 问题分析
随着互联网及移动互联网的发展,应用系统的数据量呈指数式增长,若采用单数据库进行数据存储,存在以下性能瓶颈:
-
IO 瓶颈:热点数据过多,数据库缓存不足,产生大量磁盘 IO,效率较低;请求数据太多,带宽不够,出现网络 IO 瓶颈。
-
CPU 瓶颈:排序、分组、连接查询、聚合统计等 SQL 会耗费大量的 CPU 资源,请求数太多时,CPU 出现瓶颈。
为解决上述问题,需对数据库进行分库分表处理。
分库分表的中心思想是将数据分散存储,减小单一数据库 / 表的数据量,缓解单一数据库的性能问题,从而提升数据库性能。
1.2 拆分策略
分库分表主要有垂直拆分和水平拆分两种形式,拆分粒度又分为分库和分表,最终组成的拆分策略如下:
1.3 垂直拆分
1. 垂直分库
-
定义:以表为依据,根据业务将不同表拆分到不同库中。
-
特点:
* 每个库的表结构都不一样。
* 每个库的数据也不一样。
* 所有库的并集是全量数据。
2. 垂直分表
-
定义:以字段为依据,根据字段属性将不同字段拆分到不同表中。
-
特点:
* 每个表的结构都不一样。
* 每个表的数据也不一样,一般通过一列(主键 / 外键)关联。
* 所有表的并集是全量数据。
1.4 水平拆分
1. 水平分库
-
定义:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。
-
特点:
* 每个库的表结构都一样。
* 每个库的数据都不一样。
* 所有库的并集是全量数据。
2. 水平分表
-
定义:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。
-
特点:
* 每个表的表结构都一样。
* 每个表的数据都不一样。
* 所有表的并集是全量数据。
在业务系统中,为缓解磁盘 IO 及 CPU 的性能瓶颈,需根据具体业务需求分析选择垂直拆分或水平拆分,以及具体是分库还是分表。
1.5 实现技术
技术 | 原理 | 支持语言 | 性能 |
---|---|---|---|
ShardingJDBC | 基于 AOP 原理,在应用程序中对本地执行的 SQL 进行拦截、解析、改写、路由处理,需自行编码配置实现 | 仅 Java | 较高 |
MyCat | 数据库分库分表中间件,不用调整代码即可实现分库分表 | 多种语言 | 不及 ShardingJDBC |
本次课程选择 MyCat 数据库中间件完成分库分表操作。
2. MyCat 概述
2.1 介绍
MyCat 是开源、活跃、基于 Java 语言编写的 MySQL 数据库中间件。开发人员可像使用 MySQL 一样使用 MyCat,无需关心底层数据库数量及数据存储情况,分库分表策略仅需在 MyCat 中配置即可。
优势:
-
性能可靠稳定
-
强大的技术团队
-
体系完善
-
社区活跃
2.2 下载
-
下载地址:http://dl.mycat.org.cn/
-
可下载版本包括 Mycat1.6 数据库中间件、Mycat-2.0 - 源码、Mycat-server-1.6.7.6 版本等,同时提供 GitHub 代码仓库、相关文档及问题搜索等资源。
2.3 安装
MyCat 支持 Windows 和 Linux 运行环境,以下介绍 Linux 环境搭建,需在准备好的服务器中安装 MySQL、JDK、MyCat。
服务器 | 安装软件 | 说明 |
---|---|---|
192.168.200.210 | JDK、MyCat、MySQL | MyCat 中间件服务器、分片服务器 |
192.168.200.213 | MySQL | 分片服务器 |
192.168.200.214 | MySQL | 分片服务器 |
具体安装步骤省略。
2.4 目录介绍
目录 | 说明 |
---|---|
bin | 存放可执行文件,用于启动、停止 MyCat |
conf | 存放 MyCat 的配置文件 |
lib | 存放 MyCat 的项目依赖包(jar) |
logs | 存放 MyCat 的日志文件 |
2.5 概念介绍
在MyCat的整体结构中,分为两个部分:上面的逻辑结构、下面的物理结构。
在MyCat的逻辑结构主要负责逻辑库、逻辑表、分片规则、分片节点等逻辑结构的处理,而具体的数据 存储还是在物理结构,也就是数据库服务器中存储的。 在后面讲解MyCat入门以及MyCat分片时,还会讲到上面所提到的概念。
3. MyCat 入门
3.1 需求
由于 tb_order
表数据量很大,磁盘 IO 及容量达瓶颈,需对 tb_order
表进行数据分片,分为三个数据节点,每个节点主机位于不同服务器。
3.2 环境准备
准备 3 台服务器:
-
192.168.200.210:MyCat 中间件服务器,同时作为第一个分片服务器。
-
192.168.200.213:第二个分片服务器。
-
192.168.200.214:第三个分片服务器。
在上述 3 台数据库中创建数据库 db01
。
3.3 配置
1. 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&serverTimezone=Asia/Shanghai&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&serverTimezone=Asia/Shanghai&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&serverTimezone=Asia/Shanghai&characterEncoding=utf8"
user="root" password="1234" /></dataHost>
</mycat:schema>
2. server.xml
在 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.4 测试
3.4.1 启动
配置完后,先启动 3 台分片服务器,再启动 MyCat 服务器。切换到 MyCat 安装目录,如下指令:
//启动
bin/mycat start//停止
bin/mycat stop
MyCat 启动后占用端口号 8066,可查看 logs
目录下的启动日志(如 wrapper.log
),确认 MyCat 是否启动成功,若日志中出现 “MyCAT Server startup successfully” 则启动成功。
3.4.2 测试
1. 连接 MyCat
通过如下指令连接并登录 MyCat:
mysql -h 192.168.200.210 -P 8066 -uroot -p123456
MyCat 底层模拟了 MySQL 协议,因此可通过 MySQL 指令连接。
2. 数据测试
在 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');
INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2');
INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3');INSERT INTO TB_ORDER(id,title) VALUES(1,'goods1');
INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2');
INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3');
INSERT INTO TB_ORDER(id,title) VALUES(5000000,'goods5000000');
INSERT INTO TB_ORDER(id,title) VALUES(10000000,'goods10000000');
INSERT INTO TB_ORDER(id,title) VALUES(10000001,'goods10000001');
INSERT INTO TB_ORDER(id,title) VALUES(15000000,'goods15000000');
INSERT INTO TB_ORDER(id,title) VALUES(15000001,'goods15000001');
测试结果:
-
id 在 1-500w 之间,数据存储在第一个分片数据库。
-
id 在 500w-1000w 之间,数据存储在第二个分片数据库。
-
id 在 1000w-1500w 之间,数据存储在第三个分片数据库。
-
id 超出 1500w,插入数据时报错。
数据存储的分片服务器由逻辑表配置的 rule
参数(分片规则)决定,后续会讲解分片规则配置。
`name`:指定自定义的逻辑库库名。`checkSQLschema`:SQL 语句操作指定数据库名称时,是否自动去除;`true` 自动去除,`false` 不自动去除。`sqlMaxLimit`:未指定 `limit` 查询时,列表查询模式返回的记录数。
4. MyCat 配置
4.1 schema.xml
schema.xml
是 MyCat 最重要的配置文件之一,涵盖 MyCat 的逻辑库、逻辑表、分片规则、分片节点及数据源配置,主要包含 schema
、dataNode
、dataHost
三组标签。
4.1.1 schema 标签
1. 定义逻辑库
核心属性:
`name`:指定自定义的逻辑库库名。`checkSQLschema`:SQL 语句操作指定数据库名称时,是否自动去除;`true` 自动去除,`false` 不自动去除。`sqlMaxLimit`:未指定 `limit` 查询时,列表查询模式返回的记录数。
MyCat 中逻辑库概念等同于 MySQL 中的 database
,操作某逻辑库下的表需切换逻辑库,一个 MyCat 实例可通过多个 schema
标签划分不同逻辑库。
2. 定义逻辑表
核心属性:
`name`:定义逻辑表表名,在该逻辑库下唯一。`dataNode`:定义逻辑表所属的 `dataNode`,需与 `dataNode` 标签中 `name` 对应;多个 `dataNode` 用逗号分隔。`rule`:分片规则的名字,在 `rule.xml` 中定义。`primaryKey`:逻辑表对应真实表的主键。`type`:逻辑表类型,目前有全局表和普通表,未配置则为普通表;全局表配置为 `global`。
所有需要拆分的表都需在 table
标签中定义。
4.1.2 dataNode 标签
核心属性:
`name`:定义数据节点名称。`dataHost`:数据库实例主机名称,引用自 `dataHost` 标签中 `name` 属性。`database`:定义分片所属数据库。
4.1.3 dataHost 标签
该标签是 MyCat 逻辑库的底层标签,直接定义具体的数据库实例、读写分离、心跳语句。
核心属性:
`name`:唯一标识,供上层标签使用。`maxCon/minCon`:最大连接数 / 最小连接数。`balance`:负载均衡策略,取值 0、1、2、3。`writeType`:写操作分发方式(0:写操作转发到第一个 `writeHost`,第一个挂了切换到第二个;1:写操作随机分发到配置的 `writeHost`)。`dbDriver`:数据库驱动,支持 `native`、`jdbc`。
4.2 rule.xml
rule.xml
中定义所有拆分表的规则,可灵活使用分片算法或对同一分片算法使用不同参数,实现分片过程可配置化,主要包含 tableRule
、Function
两类标签:
4.3 server.xml
server.xml
配置文件包含 MyCat 的系统配置信息,主要有 system
、user
两个重要标签。
4.3.1 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 版本号 |
4.3.2 user 标签
配置 MyCat 中的用户、访问密码,以及用户针对于逻辑库、逻辑表的权限信息,具体配置示例及说明如下:
在测试权限操作时,我们只需要将 privileges 标签的注释放开。 在 privileges 下的schema 标签中配置的dml属性配置的是逻辑库的权限。 在privileges的schema下的table标签的dml属性 中配置逻辑表的权限。
5 MyCat 分片
5.1 垂直拆分
5.1.1 场景
现在考虑将其进行垂直分库操作,将商品相关的表拆分到一个数据库服务器,订单表拆分的一个数据库 服务器,用户及省市区表拆分到一个服务器。最终结构如下:
5.1.2 准备
准备三台服务器,IP 地址及角色如下:
并且在192.168.200.210,192.168.200.213, 192.168.200.214上面创建数据库 shopping。
5.1.3 配置
1. 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&serverTimezone=Asia/Shanghai&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&serverTimezone=Asia/Shanghai&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&serverTimezone=Asia/Shanghai&characterEncoding=utf8"
user="root" password="1234" />
</dataHost>
2. server.xml
配置访问 SHOPPING
逻辑库的用户及权限,具体如下:
<user name="root" defaultAccount="true"><property name="password">123456</property><property name="schemas">SHOPPING</property></user>
<user name="user"><property name="password">123456</property><property name="schemas">SHOPPING</property><property name="readOnly">true</property>
</user>
5.1.4 测试
上传脚本与导入数据:
1.将测试 SQL 脚本(shopping-table.sql
表结构脚本、shopping-insert.sql
数据脚本)上传到服务器 /root
目录。
2.重启 MyCat 后,登录 MyCat 命令行,通过 source
指令导入脚本:
source /root/shopping-table.sql;
source /root/shopping-insert.sql;
验证跨表查询(问题发现):
执行多表联查 SQL(如查询订单及对应省市区地址):
SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o
, tb_areas_provinces p , tb_areas_city c , tb_areas_region r WHERE
o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND
o.receiver_region = r.areaid ;
问题:执行报错。原因是订单表(tb_order_master
)存储在 192.168.200.213,省市区表存储在 192.168.200.214,没有任何一个数据库服务器同时包含这两类表,导致 MyCat 无法路由 SQL。
5.1.5 全局表
1. 全局表介绍
省市区表(tb_areas_provinces
、tb_areas_city
、tb_areas_region
)属于数据字典表,在多个业务模块中被引用,可将其设置为全局表。全局表会在所有关联的数据节点中创建副本,确保每个分片服务器都包含该表,从而支持跨分片联查。
解决跨分片联查问题
2. 全局表配置
在逻辑表配置中增加 type="global"
属性,指定全局表类型,具体如下:(修改 schema.xml)
<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"/>
5.2 水平拆分
5.2.1 场景
业务系统中的日志表(tb_log
)每天产生大量数据,单台服务器存储及处理能力有限,需对 tb_log
表进行水平拆分,将数据分散到多台分片服务器,减轻单库压力。
5.2.2 准备
准备三台服务器,IP 地址及角色如下:
并且,在三台数据库服务器中分表创建一个数据库itcast。
5.2.3 配置
1. schema.xml
配置逻辑表 tb_log
与多个数据节点的关联,并指定水平分片规则(rule="mod-long"
),具体如下:
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100"><table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" />
</schema>
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
2. server.xml
配置用户可同时访问 SHOPPING
和 ITCAST
逻辑库,具体如下:
<user name="root" defaultAccount="true"><property name="password">123456</property><property name="schemas">SHOPPING,ITCAST</property>
</user>
5.2.4 测试
创建表与插入数据:
重启 MyCat 后,登录 MyCat 命令行,执行 SQL 创建 tb_log
表并插入测试数据,查看分片情况
大功告成!