文章

数据库架构设计:数据切分与分区详解

数据库架构设计:数据切分与分区详解

随着业务量的增长,单一数据库最终会面临性能瓶颈。数据切分(Sharding)是将数据分散到多个数据库或服务器上,以分担负载、提高系统可扩展性的关键技术。

1. 什么是数据切分?

数据切分是指通过特定的规则,将存储在单个数据库中的数据分散到多个数据库中,从而分散单台设备的负载。主要有两种切分方式:垂直切分和水平切分。


2. 垂直切分 (Vertical Splitting)

垂直切分是按照业务或表将数据分散到不同的数据库上。

  • 核心思想:将不同业务模块的表拆分到不同的数据库中。例如,一个电商系统可以拆分为用户库、商品库和订单库。
  • 实施方式:基于业务逻辑进行拆分,耦合度低的业务模块适合此方法。

优点

  • 业务逻辑清晰:每个库对应独立的业务模块,职责分明。
  • 实施简单:拆分规则清晰,对应用程序改动较小。
  • 易于维护:不同业务团队可以独立维护各自的数据库。

缺点

  • 无法解决单表瓶颈:如果某个业务的单张表数据量巨大,垂直切分无法解决其性能问题。
  • 可能存在跨库 Join:如果业务之间存在关联查询,会引入复杂性。

3. 水平切分 (Horizontal Splitting)

水平切分是根据表内数据的逻辑关系,将同一张表的数据按某种条件拆分到多个数据库中。

  • 核心思想:将一张大表的数据行分散到多个库或表中。例如,将用户表按照用户 ID 的范围或哈希值进行拆分。
  • 适用场景
    • 单表数据量巨大(例如,千万或亿级记录)。
    • 表的访问非常频繁,并发压力大。

优点

  • 解决单表瓶颈:有效分散单张大表的存储和访问压力。
  • 提高系统稳定性:避免因单库故障导致整个业务不可用。
  • 良好的扩展性:可以通过增加数据库节点来线性提升系统性能。

缺点

  • 拆分规则复杂:需要设计合理的分片键(Shard Key)和路由算法,后期难以更改。
  • 数据维护困难:数据分散在多处,查询、统计和维护变得复杂。
  • 跨分片 Join 困难:通常需要应用层代码或中间件来处理跨库 Join。

4. 数据切分的共同挑战及解决方案

无论是垂直切分还是水平切分,都会引入分布式系统的一些共同挑战。

挑战 1:分布式事务

跨多个数据库的操作需要保证事务的一致性。

解决方案:

  1. 两阶段提交/三阶段提交 (2PC/3PC):传统分布式事务协议,但性能较差,实现复杂。
  2. TCC (Try-Confirm-Cancel):由业务代码自行实现 TryConfirmCancel 三个操作,对代码侵入性强。
  3. 本地消息表/可靠消息队列:通过消息队列(如 RocketMQ, Kafka)实现最终一致性,是目前业界主流方案。
  4. SOA/微服务架构:将跨库操作封装在服务层,由服务内部保证事务。

挑战 2:跨节点 Join、排序与分页

跨库查询无法直接使用 SQL 的 JOIN,聚合和排序操作也变得复杂。

解决方案:

  1. 应用层聚合:在应用程序中多次查询各个节点,然后将结果合并、排序。
  2. 数据异构到中间库:将需要 Join 的数据同步到一个专用的数据仓库或搜索引擎(如 Elasticsearch)中,用于复杂查询。
  3. 数据库中间件:使用如 MyCatShardingSphere 等中间件,它们能够代理 SQL 请求,自动处理跨分片的查询和聚合。

挑战 3:多数据源管理

应用程序需要管理多个数据库连接。

解决方案:

  1. 数据库中间件:对应用层透明,应用只需连接中间件即可。
  2. 客户端分片库:在应用程序中集成Sharding-JDBC等客户端库,由其在代码层管理多数据源。

5. 表分区 (Table Partitioning)

表分区是一种在数据库内部对表进行逻辑拆分的技术,它与数据切分(Sharding)不同,后者是在数据库外部进行物理拆分。分区对于应用层是透明的。

以 Oracle 为例,分区可以将一张大表的数据存储在不同的物理段(分区)中,但在逻辑上仍是一张表。

Oracle 表分区类型

  • 范围分区 (Range Partitioning):按列值的范围进行分区,常用于日期或连续的数字。
  • 哈希分区 (Hash Partitioning):按列值的哈希值进行分区,用于均匀分布数据。
  • 列表分区 (List Partitioning):按列值的离散列表进行分区,常用于状态、地区等字段。
  • 复合分区 (Composite Partitioning):以上几种方式的组合。

分区表示例 (Range)

1
2
3
4
5
6
7
8
9
10
11
-- 创建一个按月分区的销售表
CREATE TABLE sales (
    sale_id    NUMBER,
    sale_date  DATE,
    amount     NUMBER
)
PARTITION BY RANGE (sale_date) (
    PARTITION p_2023_01 VALUES LESS THAN (TO_DATE('2023-02-01', 'YYYY-MM-DD')),
    PARTITION p_2023_02 VALUES LESS THAN (TO_DATE('2023-03-01', 'YYYY-MM-DD')),
    PARTITION p_max     VALUES LESS THAN (MAXVALUE)
);

分区管理

  • 查看分区SELECT * FROM user_tab_partitions WHERE table_name = 'SALES';
  • 查询分区数据SELECT * FROM sales PARTITION (p_2023_01);
  • 添加分区ALTER TABLE sales ADD PARTITION p_2023_03 VALUES LESS THAN (TO_DATE('2023-04-01', 'YYYY-MM-DD'));
  • 删除分区ALTER TABLE sales DROP PARTITION p_2023_01;

普通表转换为分区表

对于已存在的普通表(堆表),可以通过在线重定义(DBMS_REDEFINITION)或 CREATE TABLE AS SELECT 的方式将其转换为分区表。在线重定义可以在不影响业务的情况下完成转换。

分区索引

为了进一步提高了查询效率,可以为分区表创建索引。

  • 本地索引 (Local Index):索引与其对应的表分区一一对应,管理简单。推荐使用。
    1
    
    CREATE INDEX idx_sales_date ON sales(sale_date) LOCAL;
    
  • 全局索引 (Global Index):一个索引可以指向多个分区,管理复杂。仅在特定场景下使用。

注意:分区键的选择至关重要,通常应选择查询中最常用的过滤条件字段。

本文由作者按照 CC BY 4.0 进行授权