数据库高可用
问题
如何实现数据库高可用?主从复制、读写分离、分库分表怎么做?
面试速答版
怎么实现数据库高可用? 架构是一步步演进的:单库 → 主从 → 读写分离 → 分库分表:
- 主从复制解决单点:主库 binlog 同步给从库,主挂了可以手动/自动切换。
- 读写分离解决读压力:写走主库、读分发到多个从库。
- 分库分表解决数据量和写压力:单表超过千万行 或 单库连接数不够用时才上。
主从复制怎么做?
- 主库把写操作写入 binlog;从库 IO 线程拉 binlog 写入 relay log;SQL 线程重放 relay log。
- 异步复制默认、可能丢数据;半同步(至少 1 个从库 ack)是常见折中;组复制(MGR)是强一致方案。
- 主从延迟是重点问题:大事务、从库硬件差、大表 DDL 都会造成;对一致性敏感的读可以「强制走主库」。
分库分表怎么做?
- 垂直拆:按业务拆库(用户库/订单库)、按点冷拆表(拆大字段)。
- 水平拆:同一张表按主键/哈希拆到多个表/库(如「订单表按 user_id % 16」)。
- 难点:跨库 JOIN、全局 ID(雪花/Leaf)、分布式事务、分页、扩容迁移。常用中间件:ShardingSphere、MyCAT。
答案
高可用架构演进
主从复制
读写分离
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
- 先考虑:加索引 → 读写分离 → 缓存 → 最后再分库分表