pgmemcache 是 PostgreSQL 的一个扩展,通过用户自定义函数(UDF)将 Memcached 的读写能力引入 SQL 环境。 它的核心价值并非提供更快的缓存访问速度,而是让数据库可以直接操作外部缓存,并将缓存逻辑与 SQL 查询、存储过程和触发器结合起来。
一、pgmemcache 是什么
安装并启用扩展后,可以在 SQL 中调用 memcache_get、memcache_set、memcache_delete、memcache_incr、memcache_decr、memcache_get_multi 等函数,直接读写 Memcached。
CREATE EXTENSION pgmemcache;
SELECT memcache_set('site:config', '{"version":1}');
SELECT memcache_get('site:config');
pgmemcache 通常基于 libmemcached 与 Memcached 通信,也支持 OMcache 等后端实现。可以通过 postgresql.conf 中的 pgmemcache.default_servers 配置默认服务器列表,避免逐个建立服务器配置。
典型应用场景包括热点查询结果、用户会话、配置项和计数器缓存。对于读多写少的业务,可以优先查询缓存,未命中时回源数据库;数据发生变化时,再通过触发器维护缓存。
二、pgmemcache 的核心优势
pgmemcache 增加了 PostgreSQL 扩展依赖和数据库到缓存的网络调用。与应用直接访问 Memcached 相比,它还多了一层数据库函数调用。因此,如果目标只是降低缓存访问延迟,pgmemcache 通常不是最优选择。
它真正的价值在于:将缓存操作纳入数据库的逻辑编排,使缓存能够参与 SQL 组合、触发器执行和数据库权限管理。
1. 在 SQL 中组合缓存与业务数据
应用可以通过数据库函数完成缓存读写,不必在每种编程语言中重复实现缓存访问逻辑。
SELECT memcache_set(
'order:1',
row_to_json(o)::text
)
FROM orders AS o
WHERE o.id = 1;
这使缓存操作能够直接使用 SQL 查询结果,也便于在存储过程或业务数据库逻辑中集中编排。
但需要明确:SQL 中的操作顺序不等于事务原子性。即使业务表更新与缓存写入位于同一个 PostgreSQL 事务中,Memcached 也不会参与 PostgreSQL 的事务提交与回滚。
2. 通过触发器集中维护缓存
触发器是 pgmemcache 最有辨识度的应用场景之一。数据库可以在业务表发生变化时,自动执行缓存更新或失效操作。
CREATE OR REPLACE FUNCTION sync_user_cache()
RETURNS trigger AS $$
BEGIN
PERFORM memcache_set(
'user:' || NEW.id,
row_to_json(NEW)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_cache_sync
AFTER INSERT OR UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION sync_user_cache();
这种设计将缓存维护逻辑集中在数据库层。Web 服务、管理后台和其他客户端只要通过数据库执行相关写操作,就可以复用同一套缓存维护规则。
不过,触发器执行成功并不代表缓存与数据库具备强一致性。如果事务随后回滚,已经写入 Memcached 的数据不会自动撤销;如果缓存访问失败并导致触发器报错,业务写入也可能失败。
对于不能容忍缓存故障影响主业务的系统,更稳妥的方案是采用缓存失效、事务发件箱(Transactional Outbox)或异步事件处理,并根据业务一致性要求设计补偿机制。
3. 在 SQL 中组织批量缓存操作
pgmemcache 可以将多个缓存键的处理组织在一条 SQL 语句中。例如:
SELECT key, memcache_get(key)
FROM unnest(
ARRAY['key1', 'key2', 'key3']
) AS t(key);
这种方式便于使用 SQL 处理结果集、过滤数据以及关联业务表。
但需要区分两个概念:一条 SQL 语句可以包含多次函数调用,并不意味着这些调用一定会合并成一次 Memcached 网络请求。实际性能取决于扩展的函数实现和底层客户端行为,不能直接认为它比应用层 pipeline 更高效。
4. 让缓存与 SQL 查询协同工作
SQL 可以将缓存读取与数据库回源逻辑组合起来,减少应用层的条件分支。
WITH cache_hit AS (
SELECT memcache_get('hot_config') AS cached
)
SELECT CASE
WHEN cached IS NOT NULL
THEN cached::jsonb
ELSE (
SELECT to_jsonb(c)
FROM config AS c
WHERE c.id = 'hot_config'
)
END
FROM cache_hit;
这种方式适用于缓存优先、数据库回源的逻辑。不过,复杂查询需要关注缓存未命中、过期数据、并发更新及查询执行计划等问题,不能仅凭 SQL 语句简短就认定其性能更好。
5. 复用数据库权限与访问入口
pgmemcache 允许通过 PostgreSQL 函数权限控制哪些角色可以调用相关操作,并复用现有的连接认证、审计和连接池体系。
但数据库权限不等于完整的缓存访问隔离。Memcached 通常不会自动继承 PostgreSQL 的角色、行级安全策略(RLS)或审计规则;共享缓存键的命名、租户隔离和敏感数据保护仍需单独设计。
三、引入 pgmemcache 的代价
| 维度 | 主要影响 |
|---|---|
| 调用链路 | 应用请求需要经过 PostgreSQL 函数层,再访问 Memcached,增加额外开销 |
| 连接管理 | 缓存连接和资源使用方式受扩展实现影响,需要关注数据库连接数与后端资源 |
| 事务一致性 | Memcached 不参与 PostgreSQL 事务,回滚无法撤销已经完成的缓存写入 |
| 故障传播 | 缓存访问失败可能导致 SQL 或触发器执行失败,具体取决于错误处理方式 |
| 维护成本 | 增加扩展、Memcached 服务及其网络链路的部署和运维复杂度 |
| 缓存能力 | Memcached 以易失性键值缓存为主,不适合承担持久化数据存储职责 |
其中最需要重视的是事务一致性与故障传播。缓存虽然通常被视为可丢失的加速层,但如果缓存访问被放进关键业务写入路径,就可能反过来成为业务可用性的制约因素。
四、什么场景值得使用
适合使用 pgmemcache 的场景:
- 希望通过数据库触发器统一执行缓存更新或失效。
- 多种语言、多个应用共享同一套缓存维护逻辑。
- 缓存操作需要与 SQL 查询、存储过程和结果集处理紧密结合。
- 已经具备 PostgreSQL 扩展管理能力,并且能够接受额外的网络调用和一致性边界。
不适合使用 pgmemcache 的场景:
- 核心目标是获得最低的缓存访问延迟。
- 高并发热点路径对额外的数据库负载非常敏感。
- 业务要求数据库与缓存之间具备严格的事务原子性。
- Memcached 故障绝不能影响主业务写入,而系统又缺少隔离、降级和补偿机制。
五、总结
pgmemcache 的定位不是替代 Memcached 客户端,也不是提供事务型缓存,而是为 PostgreSQL 增加一种直接访问外部内存缓存的能力。
它的主要优势是数据库层的逻辑组合、触发器集成和统一访问入口;主要代价则是额外的调用开销、运维依赖以及数据库与缓存之间的一致性问题。
如果不需要触发器和 SQL 组合能力,优先考虑应用层直接访问 Memcached;如果希望集中管理缓存维护逻辑,pgmemcache 才具有明确的架构价值。