MySQL 分库分表(Sharding)是在数据库层面进行水平切分,以应对数据量庞大、访问压力高的场景。通过将数据分布到多个数据库实例或表中,分库分表可以有效提升系统的性能、扩展性和可用性。以下是关于 MySQL 分库分表的详细介绍:
将数据按照一定的规则分布到多个数据库实例中。每个数据库实例称为一个“分片”(Shard),每个分片存储一部分数据。
在单个数据库内,将一个大表拆分成多个小表,每个小表存储一部分数据。分表可以是垂直分表或水平分表。
结合分库和分表的方法,既在数据库层面进行切分,又在表层面进行切分,以应对更复杂的数据量和访问需求。
将一个表按照列的不同进行拆分,把经常一起查询的列放在一个表中,不常用的列放在另一个表中。
示例:
假设有一个用户表 user
,包含基本信息和详细信息:
sql
复制
-- 基本信息表
CREATE TABLE user_basic (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(100),
email VARCHAR(100)
);
-- 详细信息表
CREATE TABLE user_detail (
id INT PRIMARY KEY,
address VARCHAR(255),
phone VARCHAR(20),
FOREIGN KEY (id) REFERENCES user_basic(id)
);
将一个表的数据按照某种规则拆分到多个表中,每个表包含部分数据。常见的水平分表策略包括基于范围、哈希、列表等。
示例:
基于用户ID的范围进行分表,创建 user_0
到 user_9
十个表,用户ID尾数为0的数据存放在 user_0
表中,尾数为1的存放在 user_1
表中,以此类推。
sql
复制
-- 用户表分表示例
CREATE TABLE user_0 (
id INT PRIMARY KEY,
username VARCHAR(50),
...
);
CREATE TABLE user_1 (
id INT PRIMARY KEY,
username VARCHAR(50),
...
);
-- 依此类推,直到 user_9
将不同的表按照业务模块划分到不同的数据库中。例如,将用户相关的表放在一个数据库,订单相关的表放在另一个数据库。
示例:
db_user
数据库包含 user
、profile
等表。db_order
数据库包含 order
、order_item
等表。将同一个表的数据按照某种规则分布到多个数据库中。例如,基于用户ID的哈希值将用户数据分布到不同的数据库实例。
示例:
假设有4个数据库实例 db_0
到 db_3
,通过用户ID取模决定数据存放的数据库:
sql
复制
-- 用户ID为123的用户存放在 db_1 (123 % 4 = 1)
INSERT INTO db_1.user (id, username, ...) VALUES (123, 'alice', ...);
在应用程序代码中实现分库分表的逻辑,通过代码控制数据的路由和分布。
优点:
缺点:
示例:
在Java应用中,可以通过中间件如ShardingSphere,或者自行编写分片逻辑来路由SQL语句到不同的数据库或表。
使用专门的中间件来管理分库分表的逻辑,应用程序只需与中间件交互,无需关心具体的分片细节。
常见中间件:
优点:
缺点:
部分数据库管理系统提供了原生的分片功能,如TiDB(兼容MySQL协议)等分布式数据库。
优点:
缺点:
通过对某个字段(如用户ID)进行哈希运算,再取模决定数据存放的分片。
优点:
缺点:
根据某个字段的范围进行分片,如按时间范围、ID范围等。
优点:
缺点:
根据预定义的列表将数据分配到不同的分片,如按地区、业务类型等。
优点:
缺点:
挑战:在多个分片上保证事务的原子性、一致性、隔离性和持久性(ACID)非常复杂。
解决方案:
挑战:在分片数量变化时,需要重新分配数据,过程复杂且风险高。
解决方案:
挑战:跨分片查询难以优化,可能导致性能瓶颈。
解决方案:
挑战:在分布式环境下,保证数据的一致性更加困难。
解决方案:
MySQL 分库分表是应对大规模数据和高并发访问的有效手段,但同时也带来了系统复杂性和维护成本的增加。在实际应用中,应根据业务需求、数据规模和技术团队的能力,合理选择分库分表的策略和工具,并注重数据一致性、查询优化和系统的可扩展性。通过科学的规划和设计,分库分表可以显著提升系统的性能和可用性,为业务的持续发展提供坚实的数据库基础。
分库分表是应对大数据量和高并发场景的核心技术方案,通过将数据分散到多个数据库或表中,提升系统的扩展性、性能和容灾能力。以下是分库分表的常见策略、框架及实现细节:
sql
复制
-- 按用户ID取模分库
CREATE
TABLE user_0
(id
INT, name
VARCHAR(50))
ENGINE=InnoDB;
CREATE
TABLE user_1
(id
INT, name
VARCHAR(50))
ENGINE=InnoDB;
-- 插入数据时根据 user_id % 2 决定库
INSERT
INTO user_${user_id
%
2}
(id
, name
)
VALUES
(1,
'Alice');
sql
复制
-- 订单库(order_db)
CREATE
TABLE orders
(order_id
INT, user_id
INT, amount
DECIMAL);
-- 用户库(user_db)
CREATE
TABLE users
(user_id
INT, name
VARCHAR(50));
sql
复制
-- 按地域分库(北京、上海),按用户ID取模分表
CREATE
DATABASE bj_db
;
CREATE
DATABASE sh_db
;
USE bj_db
;
CREATE
TABLE user_0
(id
INT, name
VARCHAR(50));
USE sh_db
;
CREATE
TABLE user_0
(id
INT, name
VARCHAR(50));
yaml
复制
# application.yml
spring:
shardingsphere:
datasource:
names: ds0
, ds1
ds0:
url: jdbc
:mysql
://localhost
:3306/db0
username: root
ds1:
url: jdbc
:mysql
://localhost
:3306/db1
username: root
sharding:
tables:
user:
actual-data-nodes: ds$
{0..1
}.user_$
{0..1
}
table-strategy:
inline:
sharding-column: user_id
algorithm-expression: user_$
{user_id % 2
}
java
复制
// 使用注解指定分片键
@TableSharding(value
=
"user", shardingColumn
=
"user_id")
public
class
User
{
private
Long userId
;
private
String name
;
}
xml
复制
<schema name="my_schema" checkSQLschema="false">
<table name="user" dataNode="dn0,dn1" rule="mod-user-id"/>
schema>
<dataNode name="dn0" dataHost="host1" database="db0"/>
<dataNode name="dn1" dataHost="host2" database="db1"/>
rule
字段定义分片逻辑(如取模、范围分片)。bash
复制
# 启动 vitess 控制平面
vtctllocal init_cluster --num_cells
=1 cluster_name
# 创建分片表
vtctlclient ApplySchema -sql
"CREATE TABLE user (id INT, name VARCHAR(50))" test_keyspace
sql
复制
-- 按时间范围分片(适用于时间敏感场景)
CREATE
TABLE orders_2023
(order_id
INT, user_id
INT);
CREATE
TABLE orders_2024
(order_id
INT, user_id
INT);
pt-online-schema-change
(MySQL 在线表结构变更工具)。 场景 |
推荐框架 |
理由 |
高并发 OLTP |
ShardingSphere |
支持复杂分片规则,社区活跃 |
MySQL 离线分片 |
MyCat |
配置简单,适合中小规模 |
超大规模集群管理 |
Vitess |
专为 MySQL 设计,支持自动弹性伸缩 |
需要强一致性事务 |
Seata + 分库分表 |
解决跨分片分布式事务问题 |
注意事项:
在分库分表后,聚合查询的实现需要结合数据分布特点和业务需求,通过 跨分片聚合、分片内聚合 或 全局聚合 等方式完成。以下是详细的解决方案和实现方法:
sql
复制
-- 假设按 user_id 分片,分片键为 user_id % 2
SELECT
SUM(amount
)
FROM orders
WHERE user_id
%
2
=
0;
-- 仅查询分片 0
java
复制
// 示例:Java + ShardingSphere
List<Order> ordersFromShard0
=
queryShard("ds0",
"SELECT SUM(amount) FROM orders");
List<Order> ordersFromShard1
=
queryShard("ds1",
"SELECT SUM(amount) FROM orders");
int totalAmount
= ordersFromShard0
.get(0).getSum()
+ ordersFromShard1
.get(0).getSum();
sql
复制
-- ShardingSphere 示例
SELECT
SUM(amount
)
FROM orders
GROUP
BY user_id
;
sql
复制
-- 创建全局统计表
CREATE
TABLE global_sales
(
total_amount
DECIMAL,
update_time
TIMESTAMP
);
-- 定时任务更新全局表
INSERT
INTO global_sales
(total_amount
)
SELECT
SUM(amount
)
FROM orders
;
MATERIALIZED VIEW
)。yaml
复制
# ShardingSphere 配置示例
rules:
-
SHARDING:
tables:
orders:
actual-data-nodes: ds$
{0..1
}.orders_$
{0..1
}
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: mod
key-generate-strategy:
column: order_id
key-generator-name: snowflake
GROUP BY
和简单聚合函数。xml
复制
<table name="orders" dataNode="dn0,dn1" rule="mod-user-id"/>
sql
复制
-- Vitess 示例
SELECT
SUM(amount
)
FROM orders
WHERE user_id
=
123;
java
复制
// Java 并行查询示例
ExecutorService executor
=
Executors.newFixedThreadPool(4);
List<Future<Integer>> futures
=
new
ArrayList<>();
for
(String shard
: shards
)
{
futures
.add(executor
.submit(()
->
queryShard(shard
)));
}
int total
=
0;
for
(Future<Integer> future
: futures
)
{
total
+= future
.get();
}
java
复制
// Redis 缓存示例
String key
=
"global_sales_total";
redisTemplate
.opsForValue().set(key
, totalAmount
,
1,
TimeUnit.HOURS
);
user_id
user_id
的分片规则,确定需要查询的分片。order_date
分库分表后的聚合查询需结合 分片策略、中间件支持 和 优化手段,核心目标是减少跨分片开销并提升查询效率。通过合理设计分片键、利用框架功能(如 ShardingSphere)和优化缓存策略,可以有效应对大规模数据的聚合需求。