上篇文章《PostgreSQL 18 vs Redis:那天我发现缓存层可以删了》发出去之后,评论区的画风大概是这样的:

“这不就是纸上谈兵吗?数据呢?” “你那点测试能代表什么?生产环境试试?” “Redis 0.1ms 的响应你用 PG 替代?开玩笑呢?”

说实话,看到这些评论我还挺高兴的。质疑嘛,说白了就是要证据。

你们要数据是吧?行,这次管够。

先回顾一下上次说了啥

上篇文章的核心观点很简单:PostgreSQL 18 引入了异步 IO(io_uring)和 B 树跳跃扫描(Skip Scan)之后,很多原本必须靠 Redis 缓存才能达到的查询速度,现在数据库自己就能搞定。数据库性能的提升幅度,大到可以让你重新审视整个缓存架构。

当时给的数据是我们内部的测试结果,确实比较粗。今天这篇,把自己的、别人的、学术论文的数据全摆出来。看看异步IO和跳跃扫描到底能把数据库性能拉到什么程度。

第一组数据:真实生产环境,1K 并发

这组数据来自一个真实的 SaaS 后端系统,不是那种跑个 SELECT 1 就宣布"性能翻倍"的玩具测试。

测试条件:

  • 1000 个并发用户
  • 90% 读操作,10% 写操作
  • 数据量:1500 万订单,200 万客户
  • 查询类型:客户仪表盘(用户画像 + 近期订单 + 账户状态)

先看查询长啥样:

SELECT o.id, o.total_amount, o.status, o.created_at
FROM orders o
WHERE o.customer_id = $1
  AND o.status IN ('PAID', 'SHIPPED')
ORDER BY o.created_at DESC
LIMIT 20;

没啥花哨的,就是按客户查订单,按时间倒序,取最近 20 条。这种查询在电商系统里一天跑个几百万次太正常了。

测试结果:

方案P95 延迟缓存命中率备注
Redis + PostgreSQL72ms91%偶发缓存风暴
PostgreSQL 18(直连)54ms无外部缓存

你没看错。去掉 Redis 之后,P95 延迟反而降了 25%。

“等等,Redis 不是 0.1ms 吗?怎么加了 Redis 反而更慢?”

好问题。这就是很多人对缓存的最大误解 – 你看到的 0.1ms 是命中的情况,但缓存不是 100% 命中的。

91% 的命中率听着不错对吧?但剩下那 9% 没命中的时候会发生什么?请求先跑一趟 Redis 发现没有,再跑一趟 PostgreSQL 拿数据,拿完了还得写回 Redis。这一套下来,比直接查数据库还慢。

更要命的是那个"偶发缓存风暴"。热门数据的 TTL 同时过期,几百个请求同时穿透到数据库,Redis 和 PostgreSQL 双双飙高。这种事情在促销活动的时候特别容易发生。

而 PostgreSQL 18 直连呢?每次请求就走一条路,查完就完了。没有命中率的赌博,没有额外的网络跳转。路径简单了,反而更快。

第二组数据:冷热缓存对比

上篇文章里提过的数据,这次完整展开。

同一条查询(用户近 30 天活动聚合),不同 PostgreSQL 版本的表现:

SELECT date, sum(events)
FROM activity
WHERE user_id = $1
  AND date > now() - interval '30 days'
GROUP BY date;
场景PostgreSQL 17PostgreSQL 18
冷缓存(数据不在内存)12-18ms6-8ms
热缓存(数据在内存)4-6ms1.8-2.5ms

PostgreSQL 18 的热缓存查询 2ms 左右。

现在算一下 Redis 的真实延迟。不是那个理想化的 0.1ms,是加权平均:

  • 命中(73%):0.1ms + 0.3ms(序列化)+ 0.8ms(网络)= 1.2ms
  • 未命中(27%):1.2ms + 15ms(查数据库)+ 1.5ms(写回缓存)= 17.7ms
  • 加权平均:0.73 x 1.2 + 0.27 x 17.7 = 5.7ms

Redis 的加权平均延迟 5.7ms,PostgreSQL 18 热缓存 2ms。谁快?

“但我们的缓存命中率能到 95% 甚至 99% 啊!”

如果你真能稳定做到 99% 的命中率,那确实,Redis 在延迟上还有优势。但你得问问自己:维护 99% 命中率的代价是什么?预热策略、失效逻辑、双写一致性、TTL 调优……这些工程成本算进去了吗?

第三组数据:第三方基准测试

不信我的数据?没关系,看看其他人怎么说。

pganalyze 的测试(2025 年 5 月)

pganalyze 是 PostgreSQL 圈子里比较权威的监控服务商。PostgreSQL 18 Beta 那会儿他们就做了详细的异步 I/O 基准测试,结论是:io_uring 是在 Postgres 18 中最大化 I/O 性能的推荐设置。

PlanetScale 的对比测试

PlanetScale(就那个做 MySQL 托管的)也跑了 PostgreSQL 17 vs 18 的对比。有意思的是:

  • 顺序扫描和位图堆扫描:io_uring 明显更快
  • 索引扫描:io_uring 暂时还没覆盖,改进有限
  • 整体结论:读性能通过异步 I/O 得到了显著提升

一亿行数据的暴力测试

有人在 1 亿行数据上跑了一个 SELECT count(*) FROM test_data,就这么粗暴:

IO 方法耗时
worker(默认)~24,791ms
io_uring~7,237ms

3.4 倍的速度差距。一亿行全表扫描,从 25 秒干到 7 秒。

学术论文数据(PVLDB 2026)

连学术界都跟进了。PVLDB(数据库顶会)上有篇论文专门研究 PostgreSQL 的 io_uring 集成,应用优化指南后能获得 14% 的额外性能提升。

论文也指出了当前的局限:PostgreSQL 的多进程架构限制了 io_uring 的发挥,如果改成多线程,性能还能再上一个台阶。

为什么去掉 Redis 反而更快?(数据库性能的真实飞跃)

缓存架构隐性成本对比

这不是 Redis 慢,是整个缓存架构有隐性成本。就像你家楼下就有超市,你却非要跑到隔壁小区的便利店买东西 – 便利店东西确实便宜,但花在路上的时间比省下的钱多。

这些隐性成本你可能没细想过:

首先是网络往返。应用到 Redis 一个来回,典型延迟 0.5-1ms。Redis 在另一个可用区的话可能 2-3ms。缓存命不命中,这笔账都得付。

然后是序列化。把 JSON 对象塞进 Redis 要序列化,取出来要反序列化。复杂对象 0.3-0.5ms 不等。监控面板上看不到这个开销,但它实实在在地吃 CPU。

还有双路径分支的问题。代码里有两条路:命中走 A,没命中走 B。每个 API 的响应时间变成了概率分布而不是确定值。P99 延迟飘忽不定,调优起来让人想掀桌。

缓存风暴也是个大坑。热门 key 过期的瞬间,几十上百个请求同时穿透。你得写互斥锁、写预热逻辑、写降级策略。一层缓存带来三层防护代码。

最后是数据一致性。数据库更新了,缓存还是旧的。用户投诉"我刚改的信息怎么没变",客服工单一张接一张。

PostgreSQL 18 直连把这些成本全部归零。一个数据源,一条路径,一份代码。

配置其实很简单

上篇文章提过,这里再贴一遍,因为确实就这么几行:

# postgresql.conf

# 启用 io_uring 异步 IO(需要 Linux 内核 5.1+)
io_method = 'io_uring'

# 并发读取数量
effective_io_concurrency = 200
maintenance_io_concurrency = 50

# 共享缓冲区(根据你的内存调整,建议总内存的 25%)
shared_buffers = 16GB

# 预读合并
io_combine_limit = 512kB

改完重启就生效。不用改应用代码,不用搞什么六个月的迁移项目。异步 IO 这个特性属于"配置即享受",改几行配置就能吃到红利。

如果你的 Linux 内核版本低于 5.1,用不了 io_uring,也可以用 io_method = 'worker'。worker 模式也比 PostgreSQL 17 的同步 I/O 快不少,只是没有 io_uring 那么猛。

跳跃扫描(Skip Scan):被低估的杀手级特性

大家都盯着异步 IO 看,其实 B 树跳跃扫描对数据库性能的提升可能更直接影响业务。

举个具体例子。你有一张订单表,在 (user_id, status, created_at) 上建了复合索引。这是标准操作,没什么特别的。

现在有个查询要找所有"待处理"的订单,不按用户筛选:

SELECT * FROM orders
WHERE status = 'pending'
  AND created_at > '2025-01-01'
ORDER BY created_at DESC
LIMIT 50;

在 PostgreSQL 17 里,因为 WHERE 条件里没有索引的第一列 user_id,数据库没法高效利用这个复合索引。要么全表扫描,要么走一条歪路。怎么办?加 Redis 缓存,设 60 秒 TTL。

PostgreSQL 18 直接跳过索引第一列,对每个不同的 user_id 做范围扫描。结果:

  • PostgreSQL 17:45ms
  • PostgreSQL 18:8ms

从 45ms 到 8ms,就是数据库引擎变聪明了,没那么多弯弯绕绕。

什么时候你还是需要 Redis

我不是来踩 Redis 的。上篇文章说过,这篇再说一遍 – 以下场景 Redis 依然是更好的选择:

会话存储,需要亚毫秒级响应、零波动,用户登录状态必须瞬间返回。

限流,INCR + TTL 是 Redis 的看家本领,原子操作,天然适合。

发布订阅,跨服务的实时消息分发,Redis 的 Pub/Sub 比 PostgreSQL 的 NOTIFY 更成熟。

排行榜,ZADD + ZRANGE,有序集合就是为排名设计的。

分布式锁,SETNX 模式久经考验。

这些场景用的是 Redis 的数据结构能力,不是拿它当"PostgreSQL 的读缓存"。

“PostgreSQL 的读缓存"这个用途,在不少场景下已经不再必要了。

什么时候这招不灵

诚实说几句。以下情况别急着删 Redis:

  • P99 要求 2ms 以内:PostgreSQL 18 热缓存能做到 2ms 左右,但严格的亚毫秒级 SLA 还是得靠内存方案。
  • 单热点 key 百万级 QPS:所有请求打同一行数据,连接池都扛不住。
  • Linux 内核低于 5.1:io_uring 用不了,异步 I/O 的效果打折扣。
  • 机械硬盘:异步 I/O 能帮忙但不是魔法,SSD 是前提。
  • 云厂商限制:有些托管 PostgreSQL 服务还没开放 io_uring 配置,升级前先问你的云服务商。

一张图看明白

升级前的架构:

┌──────────────┐
│    应用程序    │
└──┬────────┬──┘
   │        │
   v        v
┌──────┐ ┌──────────┐
│Redis │ │PostgreSQL│
│缓存  │ │  存储     │
│~0.1ms│ │ ~5-15ms  │
└──┬───┘ └──────────┘
   │          ^
   └──────────┘
   缓存失效的噩梦

升级后的架构:

┌──────────────┐
│    应用程序    │
└──────┬───────┘
       v
┌────────────────┐
│ PostgreSQL 18  │
│ io_uring + AIO │
│   ~2-8ms       │
└────────────────┘

少了一个组件,少了一种失败模式。半夜被叫起来排查问题的概率,也跟着降了。

给质疑者的总结

上篇文章靠逻辑推理,这篇靠数据说话。汇总一下:

指标Redis + PG17PG18 直连来源
P95 延迟(1K并发)72ms54ms真实生产环境
热缓存查询4-6ms1.8-2.5ms内部测试
顺序扫描(1亿行)快 3.4 倍第三方基准
索引跳跃扫描45ms8ms内部测试
需要维护的组件数21常识

记住这句话:缓存层的真实延迟不是命中时的延迟,是命中和未命中的加权平均。

什么意思?你不能只看 Redis 命中时的 0.1ms 就觉得它快。把没命中时的慢、序列化的开销、网络往返的损耗、缓存风暴的偶发代价全算进去,算出的那个值,在很多场景下比 PostgreSQL 18 直连还高。

下一步该做什么

  1. 测你自己的场景:别信任何人的基准测试,包括我的。把你系统里跑得最多的查询拉出来,在 PostgreSQL 18 上跑一遍。
  2. 看你的缓存命中率:低于 95% 认真考虑直连,低于 80% 说明 Redis 大概率在拖后腿。
  3. 算一笔总账:Redis 基础设施成本 + 处理缓存 bug 的时间 + 开发效率损失。这笔账算清楚了,决策就不难了。
  4. 从低风险接口开始:挑一个读多写少、对延迟要求不极端的接口,暗启动测试。看图表说话。

你的系统里,哪个接口最适合第一个去掉缓存层?

下一篇打算聊聊 PostgreSQL 18 的虚拟生成列(Virtual Generated Columns)怎么把一些原本需要缓存的"计算型查询"直接搞定。有兴趣的话,关注一下。


参考资料:


觉得这篇有用,点个赞让更多人看到。有朋友也在纠结"到底该不该上 Redis"的,转给他看看,用数据说话比争论管用。关注梦兽编程,后续还有更多数据库实战内容。

上次那些质疑的朋友们,这次数据够不够?