数据库扩展策略:只读副本、分片等
数据库性能是 78% Web 应用程序扩展问题的瓶颈。 应用程序可以轻松地水平扩展,但数据库无法横向扩展。您选择的数据库扩展策略决定了您的应用程序是为 100 个用户还是 100,000 个用户提供可接受的性能。
本指南涵盖了完整的数据库扩展策略,从延迟扩展需求的简单优化到水平分片等高级技术。
要点
- 在添加基础设施之前优化查询并添加索引 --- 这解决了 60% 的数据库性能问题
- 读取副本是风险最低的扩展策略,可处理 80% 的读取密集型工作负载
- 一旦您的应用程序运行超过 10 个实例,连接池是强制性的
- 水平分片是最后的手段,会带来显着的应用程序复杂性
缩放阶梯
按此顺序缩放。每一步都比下一步更便宜且风险更小:
第 1 步:查询优化(免费)
在添加基础架构之前,请确保现有数据库性能最佳。
-- Find slow queries in PostgreSQL
SELECT
calls,
mean_exec_time::numeric(10,2) AS avg_ms,
total_exec_time::numeric(10,2) AS total_ms,
query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
常见优化:
- 为经常过滤的列添加缺失的索引
- 将
SELECT *替换为特定的列列表 - 使用
EXPLAIN ANALYZE识别大表上的顺序扫描 - 为多列WHERE子句添加复合索引
- 使用键集分页而不是
OFFSET实现分页
-- Bad: OFFSET pagination (scans all skipped rows)
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 10000;
-- Good: Keyset pagination (index-only scan)
SELECT * FROM orders
WHERE created_at < '2026-03-15T10:00:00Z'
ORDER BY created_at DESC
LIMIT 20;
第 2 步:垂直缩放 ($)
增加现有数据库服务器上的 CPU、RAM 和存储。这会赢得时间并且需要零应用程序更改。
| 实例大小 | vCPU | 内存 | 连接 | 每月费用 (RDS) |
|---|---|---|---|---|
| db.t3.medium | db.t3.medium | 2 | 4GB | 100 |
| db.r6g.large | 2 | 16GB | 200 | 200 175 美元 |
| db.r6g.xlarge | 4 | 32GB | 400 | 350 美元 |
| db.r6g.2xlarge | db.r6g.2xlarge | 8 | 64GB | 800 |
大多数应用程序在 64 GB RAM 和 8 个 vCPU 时达到其限制。除此之外,垂直扩展的成本变得过高。
步骤 3:连接池 ($)
Application (50 pods x 20 connections = 1,000 connections)
|
v
PgBouncer (25 database connections, transaction pooling)
|
v
PostgreSQL (25 active connections, manageable)
PgBouncer配置:
[databases]
app = host=db.example.com port=5432 dbname=production
[pgbouncer]
listen_port = 6432
listen_addr = 0.0.0.0
auth_type = md5
pool_mode = transaction
default_pool_size = 25
max_client_conn = 1000
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
步骤 4:读取副本 ($$)
只读副本处理 SELECT 查询,从主数据库卸载 60-90% 的数据库负载。
架构:
Write queries --> Primary database
|
Replication (async)
|
+----+----+
| |
Read queries --> Replica 1 Replica 2
应用程序级路由(Drizzle ORM 示例):
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
const primaryPool = new Pool({ connectionString: process.env.DATABASE_URL });
const replicaPool = new Pool({ connectionString: process.env.DATABASE_REPLICA_URL });
export const primaryDb = drizzle(primaryPool);
export const replicaDb = drizzle(replicaPool);
// In service code:
// Write operations use primaryDb
async createOrder(data: OrderInput) {
return primaryDb.insert(orders).values(data).returning();
}
// Read operations use replicaDb
async getOrders(organizationId: string) {
return replicaDb.select().from(orders)
.where(eq(orders.organizationId, organizationId))
.orderBy(desc(orders.createdAt));
}
复制延迟注意事项:异步复制会引入延迟(通常为 10-100 毫秒)。写入后,立即从副本读取可能会返回过时的数据。使用主数据库进行同一用户流中写入之后的读取。
步骤 5:缓存 ($$)
Redis 缓存完全消除了重复的数据库查询。
async getProduct(id: string): Promise<Product> {
const cacheKey = `product:${id}`;
// Check cache first
const cached = await this.redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// Cache miss: query database
const product = await this.db.select().from(products)
.where(eq(products.id, id))
.limit(1);
// Cache for 5 minutes
await this.redis.setex(cacheKey, 300, JSON.stringify(product[0]));
return product[0];
}
缓存失效策略:写入时失效。当产品更新时,删除缓存密钥。使用缓存旁模式(应用程序管理缓存)而不是直写模式(数据库管理缓存)。
步骤 6:水平分片 ($$$)
分片根据分片键将数据分布到多个数据库实例。
| 分片策略 | 描述 | 最适合 |
|---|---|---|
| 基于哈希 | 散列分片键,均匀分布 | 均匀数据分布 |
| 基于范围的 | 为分片分配范围(例如,A-M、N-Z) | 时间序列、地理数据 |
| 以租户为基础 | 每个租户/组织一个分片 | 多租户 SaaS |
何时分片:
- 单个数据库超过1 TB并且还在不断增长
- 写入吞吐量超出单个主节点的处理能力
- 垂直扩展成本超过 2,000 美元/月且没有上升空间
何时不进行分片:
- 您尚未完成步骤 1-5
- 您的数据适合单个 500 GB 数据库
- 跨分片查询在您的应用程序中很常见
PostgreSQL 特定的优化
分区(分片之前)
PostgreSQL 表分区将大表拆分为较小的物理表,同时维护单个逻辑表:
-- Partition orders by month
CREATE TABLE orders (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
created_at TIMESTAMP NOT NULL,
total DECIMAL(10,2)
) PARTITION BY RANGE (created_at);
CREATE TABLE orders_2026_01 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE orders_2026_02 PARTITION OF orders
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE orders_2026_03 PARTITION OF orders
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
分区可将大型表上的时间范围查询的查询性能提高 10-100 倍,因为 PostgreSQL 仅扫描相关分区。
吸尘和维护
-- Check table bloat
SELECT
schemaname,
relname,
n_live_tup,
n_dead_tup,
round(n_dead_tup::numeric / greatest(n_live_tup, 1) * 100, 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
为高写入表积极配置 autovacuum:
ALTER TABLE orders SET (
autovacuum_vacuum_threshold = 100,
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_threshold = 50,
autovacuum_analyze_scale_factor = 0.02
);
监控数据库性能
跟踪这些指标以了解何时以及如何扩展:
| 公制 | 工具 | 警报阈值 |
|---|---|---|
| 查询延迟 (P95) | pg_stat_语句 | >500 毫秒 |
| 活跃连接 | pg_stat_activity | > 最大值的 80% |
| 缓存命中率 | pg_stat_数据库 | <95% |
| 复制滞后 | pg_stat_replication | pg_stat_replication >1 秒 |
| 表膨胀 | pg_stat_user_tables | pg_stat_user_tables |
| 磁盘 I/O 等待 | iostat/CloudWatch | >20ms |
缓存命中率低于 95% 是您需要更多内存的最有力指标。增加 shared_buffers 和 effective_cache_size 通常比添加只读副本更便宜且更快。
查询性能跟踪
-- Enable pg_stat_statements (postgresql.conf)
-- shared_preload_libraries = 'pg_stat_statements'
-- Find the top 10 most time-consuming queries
SELECT
queryid,
calls,
mean_exec_time::numeric(10,2) AS avg_ms,
total_exec_time::numeric(10,2) AS total_ms,
rows,
query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
每周查看前 10 个查询。即使优化单个频繁执行的查询也可以将数据库总体负载减少 10-30%。
常见问题
我们如何知道何时需要扩展?
监控三个指标:查询延迟 P95(500 毫秒时发出警报)、连接利用率(80% 时发出警报)和 CPU 利用率(持续 70% 时发出警报)。如果您经常达到这些阈值,请进入扩展阶梯的下一步。不要预先优化——当数据告诉你时进行扩展。
读取副本或缓存 --- 哪个先?
从缓存开始。 Redis 缓存实现起来更简单,消除了更多负载(缓存命中完全跳过数据库),并且成本更低。当缓存命中率已超过 80% 但主数据库仍面临缓存未命中和写入操作的压力时,请添加只读副本。
数据库扩展如何与 Odoo 配合使用?
Odoo 专门使用 PostgreSQL。从查询优化开始(Odoo 生成复杂的报告查询)。当并发用户数超过 50 时,添加 PgBouncer 以进行连接池。使用只读副本来报告查询(配置 Odoo 的 --db-replica 选项)。 ECOSIRE 提供 Odoo 性能优化,包括数据库调整。
托管数据库(RDS/Cloud SQL)值得溢价吗?
是的,对于大多数企业来说。托管数据库处理自动备份、修补、故障转移和监控。与自行管理的 PostgreSQL 相比,成本溢价 30-40%,但您节省的工程时间可以抵消这一点。大规模部署是个例外,其中大型实例的成本溢价超过了兼职 DBA 的成本。
接下来会发生什么
数据库扩展是更广泛的基础设施扩展策略的组成部分。将其与静态资产的 CDN 优化、应用程序 Pod 的 Kubernetes 自动扩展 以及 负载测试 结合起来,以在实际条件下验证您的扩展决策。
联系 ECOSIRE 获取数据库优化咨询,或参阅我们的 DevOps 指南 了解完整的基础架构路线图。
由 ECOSIRE 发布——帮助企业充满信心地扩展数据基础设施。
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
使用 Drizzle ORM 进行零停机数据库迁移
使用 Drizzle ORM 无需停机即可运行数据库迁移。涵盖 PostgreSQL 的扩展-契约模式、向后兼容的架构更改、回滚策略和 CI/CD 集成。
使用 PostgreSQL 进行 Drizzle ORM:完整指南
使用 PostgreSQL 进行 Drizzle ORM 的完整指南:模式设计、迁移、类型安全查询、关系、事务和 TypeScript 应用程序的生产模式。
面向 SaaS 和科技公司的 ERP:大规模运营
面向 SaaS 和技术公司的 ERP 完整指南 - 大规模管理订阅收入、员工增长、供应商采购和财务报告。