数据库扩展对于处理海量数据和高流量的系统至关重要。扩展之所以必要,主要有几个原因。首先,随着数据量和并发用户请求的增加,性能会下降。曾经快速的查询可能会变慢,从而导致糟糕的用户体验。其次,应用程序需要高可用性。
先判断瓶颈,再选择策略
数据库性能优化的第一原则是先定位瓶颈,再选择手段。索引、缓存、复制、分片都能让某些场景更快,但它们不是免费午餐:有的会拖慢写入,有的会引入数据不一致,有的会显著增加系统复杂度。
本文介绍七类常见策略:
- 索引:减少查询扫描范围,优先解决读查询慢的问题。
- 物化视图:预先保存复杂查询结果,适合重复执行的聚合和连接查询。
- 反规范化:用冗余换读取性能,适合高频读取且字段变化不频繁的场景。
- 纵向扩展:增加单机 CPU、内存和磁盘能力,适合作为早期扩容手段。
- 数据库缓存:把热点数据放到 Redis、Memcached 等快速存储中。
- 复制:通过副本分担读请求,并提供一定的高可用能力。
- 分片:把数据水平拆分到多台数据库服务器上,解决单库容量和吞吐瓶颈。
| 策略 | 主要解决什么 | 主要代价 |
|---|---|---|
| 索引 | 点查、范围查询、排序慢 | 写入变慢、占用磁盘 |
| 物化视图 | 高频复杂聚合或连接 | 刷新成本、数据可能滞后 |
| 反规范化 | 读取时避免昂贵连接 | 数据冗余、一致性维护 |
| 纵向扩展 | 单机资源不足 | 成本高、仍有单点风险 |
| 数据库缓存 | 热点数据读压力 | 缓存失效、一致性问题 |
| 复制 | 读扩展和高可用 | 复制延迟、写瓶颈仍在 |
| 分片 | 单库容量和吞吐上限 | 路由、跨分片查询、再平衡复杂 |
常见数据库性能优化策略
可以采用多种技术(通常结合使用)来扩展数据库。
1. 索引
索引通过创建指向行的指针来加快数据检索速度,这类似于书籍的索引。数据库无需扫描整个表(全表扫描)来查找特定行,而是可以使用索引快速定位数据。
例如,假设有一个包含数百万行的 orders 表。如果没有索引,通过 order_id 搜索订单需要扫描每一行。在 order_id 列上创建索引后,数据库可以更快定位用户的记录。
权衡
索引可以加快读取查询(SELECT)的速度,但会减慢写入操作(INSERT, UPDATE, DELETE)的速度,因为写入时索引也必须更新。它们还会占用磁盘空间。应根据常见的查询模式谨慎选择索引。

2. 物化视图
物化视图存储查询的预计算结果。对于频繁运行的涉及连接或聚合的复杂查询,计算一次结果并存储可以减少查询时间。
例如,分析仪表板显示按产品类别聚合的每日总销售额。此查询涉及连接 orders、order_items 和 products 表并执行聚合。创建一个存储这些每日总数的物化视图 daily_sales_by_category,可以让仪表板通过查询这个简单视图来更快地加载。
权衡
物化视图中的数据可能会过时。它需要一种会消耗资源的刷新策略(例如,夜间刷新或由数据变化触发刷新)。物化视图还会占用存储空间。

3. 反规范化
规范化是组织数据库表以减少数据冗余并提高数据完整性,通常涉及将数据拆分为多个相关表。非规范化是相反的过程:有意将冗余数据添加到一个或多个表中,以避免查询时代价高昂的连接。
例如,电子商务应用程序必须经常显示产品名称和订单详细信息。规范化设计可能需要将订单表与产品表连接起来。为了加快速度,您可以通过直接向订单表添加 product_name 列来进行反规范化。这避免了读取期间的连接。
权衡
反规范化会增加存储需求。更重要的是,它会使数据更新变得复杂,并增加数据不一致的风险。如果产品名称发生更改,就必须在所有引用该名称的订单记录中进行更新,而不仅仅是在中心产品表中更新。
4. 纵向扩展
这包括增加现有数据库服务器的资源,例如添加更多的 CPU 处理能力、内存或更快的磁盘存储(如固态硬盘)。
权衡
单台机器的扩展存在物理限制。高端硬件价格昂贵。它并不能显著提高容错能力,因为仍然存在单点故障。升级通常需要停机时间。

5. 数据库缓存
缓存指的是在应用程序和数据库之间的临时、快速访问存储层(如 Redis 或 Memcached)中存储频繁访问的数据。后续对相同数据的请求可以直接从缓存中获取,完全绕过数据库。
例如,新闻网站会缓存热门文章。当用户请求一篇文章时,应用程序首先检查缓存。如果该文章已被缓存,就会立即返回。如果没有,应用程序会查询数据库,将文章返回给用户,并在缓存中存储一份副本,以备将来的请求使用。
权衡
缓存失效是一个复杂的问题。缓存增加了另一个需要管理的组件,并引入了潜在的一致性问题(过期数据)。它主要对读密集型工作负载有帮助。

6. 复制
复制指的是创建主数据库服务器的一个或多个副本(复制品)。写入操作(INSERT, UPDATE, DELETE)在主服务器上进行。读取操作(SELECT)可分配到各个副本上执行。这有助于分散读取负载,提高读取吞吐量。复制还能提供高可用性:如果主服务器发生故障,可以将某个副本提升为新的主服务器。
权衡
主库上的更改在同步到从库之前,通常会存在复制延迟。这可能导致读取到略微过时的数据。它会增加基础设施成本和复杂性,且无法解决主服务器上高写入量带来的瓶颈问题。
7. 分片
分片指的是将数据库数据水平分区到多个独立的数据库服务器(称为分片)上。每个分片包含总数据的一个子集。应用程序逻辑或代理层会根据分区键将查询路由到相应的分片。这样可以在多台机器上分配读写负载。
权衡
分片增加了应用程序开发和数据库操作的复杂性。需要从多个分片中获取数据的查询会变得复杂且缓慢。数据再平衡具有挑战性。选择合适的分片键对于避免数据分布不均至关重要。

什么时候才应该考虑扩展?
扩展会带来复杂性和成本,因此不应过早实施。尽早开始监控关键数据库指标。在实施复杂的扩展解决方案之前,务必先研究通过查询优化或架构改进是否能解决瓶颈问题。重写低效查询或添加适当的索引有时无需进行基础设施变更就能缓解问题。
一个更稳妥的顺序是:
- 先确认慢查询、锁等待、连接数、CPU、内存、磁盘 IO 等基础指标。
- 优先优化查询和索引,因为这是成本最低、收益最直接的路径之一。
- 如果热点读取明显,再考虑缓存或读副本。
- 如果单机资源不足,再考虑纵向扩展。
- 如果单库容量或吞吐已经接近上限,再考虑分片。
FAQ
Q1:提高数据库性能是不是先上缓存?
不一定。缓存主要适合读密集和热点数据明显的场景。如果瓶颈是慢 SQL、缺索引、锁等待或写入过载,先上缓存可能只是绕开一部分问题,并不能解决根因。
Q2:复制能解决所有性能问题吗?
不能。复制可以分担读请求,也能提供一定高可用能力,但它无法解决主库写入压力过高的问题,还会引入复制延迟。
Q3:分片是不是最终方案?
分片能突破单库容量和吞吐限制,但会带来查询路由、跨分片查询、数据再平衡和运维复杂度。只有当更简单的优化手段无法满足需求时,才值得引入。
原文:https://newsletter.scalablethread.com/p/how-to-improve-performance-of-your