You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

PostgreSQL:如何提升低缓存命中率?当数据库缓存命中率低于50%时,除增大shared_buffers外还有其他优化方式吗?

如何提升PostgreSQL的缓存命中率?

嘿,这个问题我在日常运维和调优中碰到过太多次了——尤其是当业务数据量增长或者查询复杂度上来的时候,缓存命中率掉下去真的让人头疼。咱们先不说调参数,先从最影响命中率的几个核心点聊起,再一步步解决。

先搞懂:怎么判断缓存命中率?

在动手之前,先确认你的命中率到底有多低,用这个查询就能快速拿到全局的表和索引缓存命中率:

SELECT 
  sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read))::FLOAT AS table_hit_ratio,
  sum(idx_blks_hit) / (sum(idx_blks_hit) + sum(idx_blks_read))::FLOAT AS index_hit_ratio
FROM pg_stat_user_tables;

如果表或者索引的命中率低于50%,那确实得好好调了。

除了增大shared_buffers,这些方法更关键

1. 先给查询“瘦个身”——优化慢查询与全表扫描

很多时候命中率低,根本不是缓存不够,而是每次查询都在扫磁盘!比如:

  • 避免SELECT *,只查需要的字段,减少缓存需要加载的数据量
  • 干掉不必要的全表扫描:用EXPLAIN ANALYZE看执行计划,把全表扫描改成索引扫描
  • 优化JOIN和子查询:尽量让小表驱动大表,减少中间结果集的大小
  • 开启pg_stat_statements插件,找出那些占比高、重复执行的慢查询,逐个优化

举个例子:如果你的查询是SELECT * FROM orders WHERE create_time > '2024-01-01',而create_time上没建索引,那每次都会全表扫磁盘,缓存根本留不住有用的数据——先给create_time建个B-tree索引,命中率立马就能上去。

2. 建“有用”的索引,而不是“越多越好”的索引

索引是缓存的好帮手,但冗余索引会拖垮写入性能,还会挤占缓存空间:

  • 优先给过滤、排序、JOIN的字段建索引,比如经常用WHEREORDER BY的字段
  • 部分索引:如果你的查询只过滤某一部分数据(比如WHERE status = 'active'),那就建CREATE INDEX idx_orders_active ON orders(id) WHERE status = 'active';,这样索引更小,更容易被缓存
  • 表达式索引:如果经常用函数处理字段(比如WHERE date(create_time) = '2024-05-01'),直接建CREATE INDEX idx_orders_create_date ON orders(date(create_time));,避免每次计算函数后再扫数据
  • 定期清理冗余索引:用pg_stat_user_indexes查看那些几乎没被使用的索引,果断删掉

3. 利用操作系统的Page Cache

PostgreSQL其实非常依赖OS的页缓存——shared_buffers只是数据库层面的缓存,OS还会缓存数据文件。所以:

  • 不要把shared_buffers设得太满:一般建议设为系统内存的25%(比如32G内存的机器,设8Gshared_buffers),剩下的内存留给OS缓存
  • 确保服务器没有其他吃内存的进程:如果有Java应用或者其他服务占了大量内存,OS缓存会被挤掉,导致PostgreSQL不得不频繁读磁盘

4. 冷热数据分离,让缓存只装“有用的”

如果你的数据库里有大量历史冷数据(比如几年前的订单),这些数据很少被访问,但会占用缓存空间:

  • 分区表:把热数据(比如最近3个月)放在一个分区,冷数据放到单独的分区,查询时只访问热分区,缓存里就只会保留常用数据
  • 把冷数据归档到单独的存储(比如廉价的机械硬盘),或者用pg_dump导出后删除,只保留查询接口

5. 用pg_prewarm提前加载热数据

如果有一些大表或者索引是业务频繁访问的,可以提前把它们加载到shared_buffers里,不用等查询的时候再慢慢读:

-- 预加载整个表
SELECT pg_prewarm('orders');
-- 预加载特定索引
SELECT pg_prewarm('idx_orders_create_time');

可以把这个命令加到数据库启动脚本里,或者定期执行,确保热数据一直在缓存里。

6. 检查内存压力与SWAP使用

如果服务器开启了SWAP,当内存不足时,PostgreSQL的缓存会被换出到磁盘,命中率直接崩盘:

  • 尽量关闭SWAP,或者把SWAP设得很小
  • free -h或者top查看内存使用情况,确保数据库有足够的专属内存

关于shared_buffers的补充:不是越大越好

虽然增大shared_buffers是常见的方法,但要注意:

  • 超过系统内存的50%反而可能降低性能:因为OS需要内存来做自己的缓存,两者互相挤占会导致整体效率下降
  • 调整后需要重启数据库才能生效,而且要配合work_memmaintenance_work_mem等参数一起调,避免内存不足

总之,缓存命中率低往往是多个因素叠加的结果——先从查询和索引入手,再调整系统配置和数据布局,大部分情况都能把命中率拉到90%以上。

内容的提问来源于stack exchange,提问作者Fabrice Chapuis

火山引擎 最新活动