死亡突围僵尸战争苹果版
215.8M · 2025-10-14
单体项目在构建之初,数据库的负载和数据量都不大,所以不需要对数据库做拆分,小型财务系统、文书系统、ERP系统、OA系统,用一个MySQL数据库实例基本就够用了。
就像《淘宝技术这十年》里面说到的,电商业务的数据量增长飞快,所以最开始的PHP+MySQL的架构已经不能满足实际要求了,于是淘宝想到的第一个办法就是把MySQL替换成Oracle。但是没过了多久,在08年前后,单节点的Oracle数据库也不好用了,于是淘宝终于告别了单节点数据库,开始拆分数据库。从一个节点,变成多个节点。
拆分数据库是有讲究的,比如说拆分方法有两种:垂直切分和水平切分。那你是先水平切分还是垂直切分呢?顺序无所谓?不,顺序有所谓,次序绝对不能错:先水平切分,然后垂直切分。
真实数据:
sql
-- 拆分前:单表6000万数据
SELECT * FROM orders WHERE user_id = 123456 ORDER BY create_time DESC LIMIT 20;
-- 执行时间:8.2秒
-- 拆分后:单表50万数据
SELECT * FROM orders_01 WHERE user_id = 123456 ORDER BY create_time DESC LIMIT 20;
-- 执行时间:0.05秒
事故场景:
团队痛点:
需求差异:
成本对比:
核心思想:不同业务使用不同数据库
拆分前架构:
单一数据库:shop_db
├── 用户表:users
├── 商品表:products
├── 订单表:orders
├── 支付表:payments
└── 日志表:logs
拆分后架构:
// 用户库:user_db
@Component
@DataSource("userDataSource")
public class UserMapper {
// 用户相关表:users, user_profile, user_address
}
// 商品库:product_db
@Component
@DataSource("productDataSource")
public class ProductMapper {
// 商品相关表:products, categories, inventory
}
// 订单库:order_db
@Component
@DataSource("orderDataSource")
public class OrderMapper {
// 订单相关表:orders, order_items, order_logs
}
配置示例:
spring:
datasource:
user:
url: jdbc:mysql://user-db:3306/user_db
product:
url: jdbc:mysql://product-db:3306/product_db
order:
url: jdbc:mysql://order-db:3306/order_db
核心思想:相同业务,不同数据分布在不同数据库
分片策略:
public class DatabaseShardingStrategy {
// 按用户ID分库:user_id % 4
public String getDataSourceName(Long userId) {
int dbIndex = userId % 4; // 分4个库
return "order_db_" + dbIndex;
}
// 按时间分库:每月一个库
public String getDataSourceName(Date createTime) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM");
return "order_db_" + sdf.format(createTime);
}
}
拆分场景:表字段过多,冷热数据分离
拆分前:
-- 用户表(40+字段)
CREATE TABLE users (
id BIGINT,
username VARCHAR(50),
password VARCHAR(100),
-- ... 20个常用字段
last_login_ip VARCHAR(45),
last_login_time DATETIME,
-- ... 20个不常用字段
personal_signature TEXT,
background_image VARCHAR(200)
);
拆分后:
-- 热表:频繁查询的字段
CREATE TABLE users_hot (
id BIGINT,
username VARCHAR(50),
password VARCHAR(100),
email VARCHAR(100),
phone VARCHAR(20),
-- ... 其他常用字段
PRIMARY KEY (id)
);
-- 冷表:不常查询的字段
CREATE TABLE users_cold (
user_id BIGINT,
personal_signature TEXT,
background_image VARCHAR(200),
register_source VARCHAR(50),
-- ... 其他不常用字段
PRIMARY KEY (user_id)
);
核心思想:大表拆小表,减少单表数据量
分表策略:
public class TableShardingStrategy {
// 按用户ID分表:user_id % 16
public String getTableName(Long userId, String logicTable) {
int tableIndex = userId % 16; // 分16张表
return logicTable + "_" + tableIndex;
}
// 按时间分表:每月一张表
public String getTableName(Date createTime, String logicTable) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM");
return logicTable + "_" + sdf.format(createTime);
}
}
SQL改写:
-- 拆分前:
SELECT * FROM orders WHERE user_id = 123456;
-- 拆分后(需要应用层改写):
SELECT * FROM orders_8 WHERE user_id = 123456; -- 123456 % 16 = 8
核心思想:写操作走主库,读操作走从库
架构设计:
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource routingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave1", slave1DataSource());
targetDataSources.put("slave2", slave2DataSource());
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
}
// 通过注解控制数据源
@Service
public class OrderService {
@ReadOnly // 读从库
public Order getOrder(Long orderId) {
return orderMapper.selectById(orderId);
}
// 默认写主库
public void createOrder(Order order) {
orderMapper.insert(order);
}
}
真实案例:大型电商平台架构
public class HybridShardingStrategy {
public ShardingResult resolve(String logicTable, Long userId, Date createTime) {
// 1. 先按业务垂直分库
String dbName = "order_db";
// 2. 再按用户ID水平分库
int dbShard = userId % 8; // 分8个库
dbName = dbName + "_" + dbShard;
// 3. 最后按时间水平分表
String tableName = logicTable + "_" + getMonthSuffix(createTime);
return new ShardingResult(dbName, tableName);
}
}
解决方案:
// 使用Seata分布式事务框架
@GlobalTransactional
public void createOrder(Order order) {
// 1. 扣减库存(库存库)
inventoryService.deduct(order);
// 2. 创建订单(订单库)
orderService.create(order);
// 3. 扣减余额(用户库)
accountService.deductBalance(order);
}
解决方案:
// 1. 字段冗余
public class OrderVO {
private Long orderId;
private Long userId;
private String userName; // 冗余用户名字段
private String productName; // 冗余商品名字段
}
// 2. 使用Elasticsearch构建查询视图
@Component
public class OrderSearchService {
public List<OrderVO> searchOrders(OrderQuery query) {
// 从ES查询,避免跨库JOIN
return elasticsearchTemplate.query(query);
}
}
解决方案:
// 雪花算法生成分布式ID
@Component
public class SnowflakeIdGenerator {
public Long nextId() {
// 时间戳 + 机器ID + 序列号
return snowflake.nextId();
}
}
// 数据库号段模式
@Service
public class SegmentIdGenerator {
public Long nextId(String businessType) {
// 从数据库获取号段,内存中分配
return idSegmentService.getNextSegment(businessType);
}
}
思考题:在你的项目中,如果订单表需要同时支持按用户查询和按商家查询,分片键应该怎么设计?欢迎在评论区分享你的架构方案!