数据库高可用
问题
如何实现数据库高可用?主从复制、读写分离、分库分表怎么做?
答案
高可用架构演进
主从复制
读写分离
read-write-split.ts
// 基于 Prisma 的读写分离
import { PrismaClient } from '@prisma/client';
const writeClient = new PrismaClient({
datasources: { db: { url: process.env.MASTER_DATABASE_URL } },
});
const readClient = new PrismaClient({
datasources: { db: { url: process.env.SLAVE_DATABASE_URL } },
});
class UserService {
// 写操作走主库
async createUser(data: CreateUserDto) {
return writeClient.user.create({ data });
}
// 读操作走从库
async getUser(id: string) {
return readClient.user.findUnique({ where: { id } });
}
// 需要强一致性的读走主库
async getUserAfterUpdate(id: string) {
return writeClient.user.findUnique({ where: { id } });
}
}
分库分表
| 方式 | 策略 | 场景 |
|---|---|---|
| 垂直分库 | 按业务拆分库 | 用户库、订单库、商品库 |
| 水平分表 | 按规则拆分同一张表 | 按 user_id 取模、按日期分区 |
sharding.ts
// 水平分表:按 user_id 取模
function getTableName(userId: number): string {
const shardId = userId % 4; // 分 4 张表
return `orders_${shardId}`; // orders_0, orders_1, orders_2, orders_3
}
// 查询路由
async function getOrders(userId: number) {
const table = getTableName(userId);
return db.$queryRaw(`SELECT * FROM ${table} WHERE user_id = ?`, [userId]);
}
连接池配置
connection-pool.ts
// Prisma 连接池
const prisma = new PrismaClient({
datasources: {
db: {
url: `${process.env.DATABASE_URL}?connection_limit=20&pool_timeout=10`,
},
},
});
// TypeORM 连接池
const dataSource = new DataSource({
type: 'mysql',
extra: {
connectionLimit: 20, // 最大连接数
waitForConnections: true,
queueLimit: 0,
},
});
常见面试问题
Q1: 主从延迟怎么处理?
答案:
- 关键业务读主库:写后立即读走主库
- 半同步复制:主库等至少一个从库确认
- 延迟监控:
SHOW SLAVE STATUS查看Seconds_Behind_Master - 业务层缓存:写入后更新缓存
Q2: 分库分表后的问题?
答案:
| 问题 | 解决方案 |
|---|---|
| 跨表 JOIN | 宽表冗余 / 应用层聚合 |
| 分布式事务 | Saga / Event Sourcing |
| 全局排序 | 各分片取 TopN 再合并 |
| 全局 ID | 雪花算法 / 号段模式 |
Q3: 什么时候该分库分表?
答案:
- 单表超过 500 万行 或 2GB
- 单库 QPS 超过 5000
- 先考虑:加索引 → 读写分离 → 缓存 → 最后再分库分表