分库分表介绍
1. 什么是分库分表?
分库分表是一种数据库分片技术,用于解决大规模应用中单一数据库容量不足以支持高并发和大数据量的问题。它将一个大型的数据库拆分成多个小型数据库,每个小型数据库称为一个分片。每个分片存储部分数据,从而降低了单个数据库的负担。
简单来说,分库是将原本的单库拆分为多个库,分表是将原来的单表拆分为多个表。
很多情况下,分库分表并不是从系统设计开始就存在的,而是系统运行过程中,出现数据量庞大或者查询性能慢等问题延伸而来。
如果你在业务功能开发时,已经预知业务数据库量,应提前进行分库或者分表,做好分片规范,避免系统运行时拆分。
1.1 什么是分库?
1)垂直分片
比如:电商库 mall_db,业务拆分后就是 user_db、order_db、pay_db...
按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。
优点:
- 可以针对不同业务场景优化数据库,提高性能。
- 提高了数据库的并发能力。
缺点:
- 需要处理分布式事务的问题。
- 增加了系统的复杂度。
2)水平分片
比如:用户库 user_db,分片库后就是 user_db_0、user_db_1、user_db_xx。
水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。
优点:
- 提高了单库的读写性能,降低了单库数据量。
- 可以将库存储在不同的物理服务器上,提高了查询效率。
缺点:
- 需要处理跨库查询的问题。
- 对分库规则的设计需要谨慎,避免热点数据集中在某个库中。
1.2 什么是分表?
1)垂直分片
比如:订单表 order_table,拆分后就是 order_table 以及 order_ext_table。
同分库的概念,只不过将数据库维度降低为数据库表维度。
优点:
- 拆分后每个表的数据量变小,查询时涉及的磁盘 I/O 次数相对减少,提高了查询效率。
- 每个小表的并发写入操作相对较少,减少了数据库锁的竞争,提高了并发能力。
缺点:
- 需要在应用层面处理跨表查询的逻辑,增加了开发的复杂性。
- 如果一个事务涉及多个小表,可能需要在应用层面进行事务管理,增加了代码的复杂性。
- 需要额外的措施来保证拆分后的小表之间的数据一致性。
2)水平分片
比如:订单表 order_table,拆分后就是 order_table_0、 order_table_1、order_table_xxx。
同分库的概念,只不过将数据库维度降低为数据库表维度。
优点:
-
可以根据数据量的增长动态地增加分表,从而扩展数据库的存储能力。
-
每个小表的数据量减少,可以提高查询速度,尤其是在频繁查询的场景下。
-
拆分后,每个小表的并发写入操作相对减少,降低了数据库锁的竞争,提高了并发性能。
缺点:
- 对业务存在一定限制,如果没有按照分片键查询,会造成读扩散问题。
- 对分表规则的设计需要谨慎,避免热点数据集中在某个表中。
2. 分库分表场景
2.1 什么场景分表?
数据量过大或者数据库表对应的磁盘文件过大。
Q:多少数据量进行分表?
A:单表 1000w 是否要分表?回答不够标准。假设一个表里 15 个字段,没有特别大的值(不包含 text 或其它超长度的列)数据量超过 5000 万了,依然很丝滑,因为走索引。
真正需要考虑的是:业务的增长量以及历史数量。
Q:物理文件过大,会有什么问题?
A:会影响公司对数据库表的一个备份。数据库表文件过大,也间接证明表数据过大,增加或删除字段导致锁表的时间过长。
2.2 什么场景分库?
当数据库的连接不够客户端使用时,可以考虑分库或读写分离。
如果说当数据库的 QPS 越来越高以及数据量越来越大的时候,就需要考虑分库分表。
Q:为什么说连接不够用?
A:假设 MySQL Server 能支持 4000 个数据库连接。我们有 10 个服务,40 个节点,一个节点呢数据库连接池最多 10 个。这样就把一个 MySQL Server 的连接数压榨干净了。
当 MySQL 连接不够用时,可能会报错 "Too many connections" 或者类似的错误。这是因为 MySQL 服务器同时可以处理的连接数量是有限制的,当连接数达到这个限制时,服务器就会拒绝新的连接请求,并返回这个错误消息。
2.3 什么场景分库分表?
- 高并发写入场景:当应用面临高并发的写入请求时,单一数据库可能无法满足写入压力,此时可以将数据按照一定规则拆分到多个数据库中,每个数据库处理部分数据的写入请求,从而提高写入性能。
- 数据量巨大场景:随着数据量的不断增加,单一数据库的存储和查询性能可能逐渐下降。此时,可以将数据按照一定的规则拆分到多个表中,每个表存储部分数据,从而分散数据的存储压力,提高查询性能。
3. 分片键选择原则
选择分库分表中的分片键(Sharding Key)是一个关键决策,它直接影响了分库分表的性能和可扩展性。以下是一些选择分片键的关键因素:
- 访问频率:选择分片键应考虑数据的访问频率。将经常访问的数据放在同一个分片上,可以提高查询性能和降低跨分片查询的开销。
- 数据均匀性:分片键应该保证数据的均匀分布在各个分片上,避免出现热点数据集中在某个分片上的情况。
- 业务关联性:分片键应该与业务关联紧密,这样可以避免跨分片查询和跨库事务的复杂性。
- 数据不可变:一旦选择了分片键,它应该是不可变的,不能随着业务的变化而频繁修改。
数据分片核心概念
1. 表
表是透明化数据分片的关键概念。 Apache ShardingSphere 通过提供多样化的表类型,适配不同场景下的数据分片需求。
1.2 逻辑表
相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。 例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0
到 t_order_9
,他们的逻辑表名为 t_order
。
1.3 真实表
在水平拆分的数据库中真实存在的物理表。 即上个示例中的 t_order_0
到 t_order_9
。
1.4 绑定表
指分片规则一致的一组分片表。 使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。 例如:t_order
表和 t_order_item
表,均按照 order_id
分片,并且使用 order_id
进行关联,则此两张表互为绑定表关系。 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。 举例说明,如果 SQL 为:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
在不配置绑定表关系时,假设分片键 order_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
在配置绑定表关系,并且使用 order_id
进行关联后,路由的 SQL 应该为 2 条:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
1.5 广播表
指所有的数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
1.6 单表
指所有的分片数据源中仅唯一存在的表。 适用于数据量不大且无需分片的表。
注意:符合以下条件的单表会被自动加载:
- 数据加密、数据脱敏等规则中显示配置的单表
- 用户通过 ShardingSphere 执行 DDL 语句创建的单表
其余不符合上述条件的单表,ShardingSphere 不会自动加载,用户可根据需要配置单表规则进行管理。
2. 数据节点
数据分片的最小单元,由数据源名称和真实表组成。 例:ds_0.t_order_0。 逻辑表与真实表的映射关系,可分为均匀分布和自定义分布两种形式。
2.1 均匀分布
指数据表在每个数据源内呈现均匀分布的态势, 例如:
db0
├── t_order0
└── t_order1
db1
├── t_order0
└── t_order1
数据节点的配置如下:
db0.t_order0, db0.t_order1, db1.t_order0, db1.t_order1
2.2 自定义分布
指数据表呈现有特定规则的分布, 例如:
db0
├── t_order0
└── t_order1
db1
├── t_order2
├── t_order3
└── t_order4
数据节点的配置如下:
db0.t_order0, db0.t_order1, db1.t_order2, db1.t_order3, db1.t_order4
3. 分片
3.1 分片键
用于将数据库(表)水平拆分的数据库字段。 例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Apache ShardingSphere 也支持根据多个字段进行分片。
3.2 分片算法
用于将数据分片的算法,支持 =
、>=
、<=
、>
、<
、BETWEEN
和 IN
进行分片。 分片算法可由开发者自行实现,也可使用 Apache ShardingSphere 内置的分片算法语法糖,灵活度非常高。
3.3 自动化分片算法
分片算法语法糖,用于便捷的托管所有数据节点,使用者无需关注真实表的物理分布。 包括取模、哈希、范围、时间等常用分片算法的实现。
3.4 自定义分片算法
提供接口让应用开发者自行实现与业务实现紧密相关的分片算法,并允许使用者自行管理真实表的物理分布。 自定义分片算法又分为:
-
标准分片算法:用于处理使用单一键作为分片键的
=
、IN
、BETWEEN AND
、>
、<
、>=
、<=
进行分片的场景。 -
复合分片算法:用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。
-
Hint 分片算法:用于处理使用
Hint
行分片的场景。
3.5 分片策略
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。 真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。
3.6 强制分片路由
对于分片字段并非由 SQL 而是其他外置条件决定的场景,可使用 SQL Hint 注入分片值。 例:按照员工登录主键分库,而数据库中并无此字段。 SQL Hint 支持通过 Java API 和 SQL 注释两种方式使用。 详情请参见强制分片路由。
4. 行表达式
行表达式是为了解决配置的简化与一体化这两个主要问题。在繁琐的数据分片规则配置中,随着数据节点的增多,大量的重复配置使得配置本身不易被维护。 通过行表达式可以有效地简化数据节点配置工作量。
对于常见的分片算法,使用 Java 代码实现并不有助于配置的统一管理。 通过行表达式书写分片算法,可以有效地将规则配置一同存放,更加易于浏览与存储。
行表达式作为字符串由两部分组成,分别是字符串开头的对应 SPI 实现的 Type Name 部分和表达式部分。 以 <GROOVY>t_order_${1..3}
为例,字符 串<GROOVY>
部分的子字符串 GROOVY
为此行表达式使用的对应 SPI 实现的 Type Name,其被 <>
符号包裹来识别。而字符串 t_order_${1..3}
为此行表达式的表达式部分。当行表达式不指定 Type Name 时,例如 t_order_${1..3}
,行表示式默认将使用 InlineExpressionParser
SPI 的 GROOVY
实现来解析表达式。
以下部分介绍 GROOVY
实现的语法规则。
行表达式的使用非常直观,只需要在配置中使用 ${ expression }
或 $->{ expression }
标识行表达式即可。 目前支持数据节点和分片算法这两个部分的配置。 行表达式的内容使用的是 Groovy 的语法,Groovy 能够支持的所有操作,行表达式均能够支持。 例如:
${begin..end}
表示范围区间 ${[unit1, unit2, unit_x]}
表示枚举值
行表达式中如果出现连续多个 ${ expression }
或 $->{ expression }
表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。
例如,以下行表达式:
${['online', 'offline']}_table${1..3}
最终会解析为:
online_table1, online_table2, online_table3, offline_table1, offline_table2, offline_table3
5. 分布式主键
传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等。 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。
目前有许多第三方解决方案可以完美解决这个问题,如 UUID 等依靠特定算法自生成不重复键,或者通过引入主键生成服务等。为了方便用户使用、满足不同用户不同使用场景的需求, Apache ShardingSphere 不仅提供了内置的分布式主键生成器,例如 UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。