实施查询缓存策略以提高性能。在设置缓存层、配置Redis或优化数据库查询响应时间时使用。
查询缓存策略
概览
使用Redis、Memcached和数据库级缓存实施多级缓存策略。涵盖缓存失效、TTL策略和缓存预热模式。
使用场景
- 查询结果缓存
- 高读负载优化
- 减少数据库负载
- 提高响应时间
- 缓存层选择
- 缓存失效模式
- 分布式缓存设置
应用级缓存
Redis缓存与PostgreSQL
设置Redis缓存层:
// Node.js示例,使用Redis
const redis = require('redis');
const client = redis.createClient({
host: 'localhost',
port: 6379,
db: 0
});
// 缓存获取用户
async function getUser(userId) {
const cacheKey = `user:${userId}`;
// 检查缓存
const cached = await client.get(cacheKey);
if (cached) return JSON.parse(cached);
// 查询数据库
const user = await db.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);
// 缓存结果(TTL:1小时)
await client.setex(cacheKey, 3600, JSON.stringify(user));
return user;
}
// 启动时缓存预热
async function warmCache() {
const hotUsers = await db.query(
'SELECT * FROM users WHERE active = true ORDER BY last_login DESC LIMIT 100'
);
for (const user of hotUsers) {
await client.setex(
`user:${user.id}`,
3600,
JSON.stringify(user)
);
}
}
查询结果缓存模式:
// 通用缓存模式
async function queryCached(
key,
queryFn,
ttl = 3600 // 默认1小时
) {
// 检查缓存
const cached = await client.get(key);
if (cached) return JSON.parse(cached);
// 执行查询
const result = await queryFn();
// 缓存结果
await client.setex(key, ttl, JSON.stringify(result));
return result;
}
// 使用
const posts = await queryCached(
'user:123:posts',
async () => db.query(
'SELECT * FROM posts WHERE user_id = $1 ORDER BY created_at DESC',
[123]
),
1800 // 30分钟TTL
);
Memcached缓存
PostgreSQL与Memcached:
// Node.js与Memcached
const Memcached = require('memcached');
const memcached = new Memcached(['localhost:11211']);
async function getProductWithCache(productId) {
const cacheKey = `product:${productId}`;
try {
// 先尝试缓存
const cached = await memcached.get(cacheKey);
if (cached) return cached;
} catch (err) {
// Memcached宕机,继续查询数据库
}
// 查询数据库
const product = await db.query(
'SELECT * FROM products WHERE id = $1',
[productId]
);
// 设置缓存(TTL:3600秒)
try {
await memcached.set(cacheKey, product, 3600);
} catch (err) {
// 失败静默,从数据库服务
}
return product;
}
数据库级缓存
PostgreSQL查询缓存
物化视图用于缓存:
-- 为昂贵的查询创建物化视图
CREATE MATERIALIZED VIEW user_statistics AS
SELECT
u.id,
u.email,
COUNT(o.id) as total_orders,
SUM(o.total) as total_spent,
AVG(o.total) as avg_order_value,
MAX(o.created_at) as last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.email;
-- 为物化视图创建索引以快速访问
CREATE INDEX idx_user_stats_email ON user_statistics(email);
-- 刷新策略(计划)
REFRESH MATERIALIZED VIEW CONCURRENTLY user_statistics;
-- 而不是基础表查询视图
SELECT * FROM user_statistics WHERE email = 'john@example.com';
部分索引用于查询优化:
-- 仅索引活跃用户(减少索引大小)
CREATE INDEX idx_active_users ON users(created_at DESC)
WHERE active = true AND deleted_at IS NULL;
-- 索引最近创建的记录
CREATE INDEX idx_recent_orders ON orders(user_id, total DESC)
WHERE created_at > NOW() - INTERVAL '30 days';
MySQL查询缓存
MySQL查询缓存配置:
-- 检查查询缓存状态
SHOW VARIABLES LIKE 'query_cache%';
-- 启用查询缓存
SET GLOBAL query_cache_type = 1;
SET GLOBAL query_cache_size = 268435456; -- 256MB
-- 监控查询缓存
SHOW STATUS LIKE 'Qcache%';
-- 查看缓存查询
SELECT * FROM performance_schema.table_io_waits_summary_by_table_io_type;
-- 使特定查询失效
FLUSH QUERY CACHE;
FLUSH TABLES;
缓存失效策略
基于事件的失效
PostgreSQL与触发器:
-- 创建函数以在写入时使缓存失效
CREATE OR REPLACE FUNCTION invalidate_user_cache()
RETURNS TRIGGER AS $$
BEGIN
-- 在生产中,这将发布到Redis/Memcached
-- PERFORM redis_publish('cache_invalidation', json_build_object(
-- 'event', 'user_updated',
-- 'user_id', NEW.id
-- ));
RAISE LOG 'Invalidating cache for user %', NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 附加到users表
CREATE TRIGGER invalidate_cache_on_user_update
AFTER UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION invalidate_user_cache();
-- 当用户更新时,触发器触发并使缓存失效
UPDATE users SET email = 'newemail@example.com' WHERE id = 123;
应用级失效:
// 在数据修改时使缓存失效
async function updateUser(userId, userData) {
// 更新数据库
const updatedUser = await db.query(
'UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING *',
[userData.name, userData.email, userId]
);
// 使相关缓存失效
const cacheKeys = [
`user:${userId}`,
`user:${userId}:profile`,
`user:${userId}:orders`,
'active_users_list'
];
for (const key of cacheKeys) {
await client.del(key);
}
return updatedUser;
}
基于时间的失效
基于TTL的缓存过期:
// 根据不同数据类型设置变量TTL
const CACHE_TTLS = {
user_profile: 3600, // 1小时
product_list: 1800, // 30分钟
order_summary: 300, // 5分钟(频繁变化)
category_list: 86400, // 1天(很少变化)
user_settings: 7200 // 2小时
};
async function getCachedData(key, type, queryFn) {
const cached = await client.get(key);
if (cached) return JSON.parse(cached);
const result = await queryFn();
const ttl = CACHE_TTLS[type] || 3600;
await client.setex(key, ttl, JSON.stringify(result));
return result;
}
LRU缓存淘汰
Redis LRU策略:
# redis.conf
maxmemory 1gb
maxmemory-policy allkeys-lru # 淘汰最近最少使用的键
# 或其他策略:
# volatile-lru: 淘汰任何带有TTL的键(LRU)
# allkeys-lfu: 淘汰最少使用的键
# volatile-ttl: 淘汰TTL最短的键
缓存预热
预加载热门数据:
// 在应用程序启动时预热缓存
async function warmApplicationCache() {
// 预热流行用户
const popularUsers = await db.query(
'SELECT * FROM users ORDER BY last_login DESC LIMIT 50'
);
for (const user of popularUsers) {
await client.setex(
`user:${user.id}`,
3600,
JSON.stringify(user)
);
}
// 预热顶级产品
const topProducts = await db.query(
'SELECT * FROM products ORDER BY sales DESC LIMIT 100'
);
for (const product of topProducts) {
await client.setex(
`product:${product.id}`,
1800,
JSON.stringify(product)
);
}
console.log('Cache warming complete');
}
// 在服务器启动时运行
app.listen(3000, warmApplicationCache);
分布式缓存
Redis集群设置:
# 多节点Redis用于分布式缓存
redis-server --port 6379 --cluster-enabled yes
redis-server --port 6380 --cluster-enabled yes
redis-server --port 6381 --cluster-enabled yes
# 创建集群
redis-cli --cluster create localhost:6379 localhost:6380 localhost:6381
跨数据中心缓存:
// 在区域间复制缓存
async function setCacheMultiRegion(key, value, ttl) {
const regions = ['us-east', 'eu-west', 'ap-south'];
await Promise.all(
regions.map(region =>
redisClients[region].setex(key, ttl, JSON.stringify(value))
)
);
}
// 从最近的缓存读取
async function getCacheNearest(key, region) {
const value = await redisClients[region].get(key);
if (value) return JSON.parse(value);
// 回退到其他区域
for (const fallbackRegion of ['us-east', 'eu-west', 'ap-south']) {
const fallbackValue = await redisClients[fallbackRegion].get(key);
if (fallbackValue) return JSON.parse(fallbackValue);
}
return null;
}
缓存监控
Redis缓存统计:
async function getCacheStats() {
const info = await client.info('stats');
return {
hits: info.keyspace_hits,
misses: info.keyspace_misses,
hitRate: info.keyspace_hits / (info.keyspace_hits + info.keyspace_misses)
};
}
// 监控命中率
setInterval(async () => {
const stats = await getCacheStats();
console.log(`Cache hit rate: ${(stats.hitRate * 100).toFixed(2)}%`);
}, 60000);
缓存策略选择
| 策略 | 最适合 | 缺点 |
|---|---|---|
| 应用级 | 灵活,细粒度控制 | 更多代码,一致性挑战 |
| 数据库级 | 透明,自动 | 灵活性较小 |
| 分布式缓存 | 高吞吐量,扩展 | 额外复杂性,网络延迟 |
| 物化视图 | 复杂查询,聚合 | 需要手动刷新 |
最佳实践
✅ 执行缓存预热 ✅ 监控缓存命中率 ✅ 使用适当的TTLs ✅ 实施缓存失效 ✅ 计划缓存故障 ✅ 测试缓存场景
❌ 不要缓存敏感数据 ❌ 不要没有失效策略就缓存 ❌ 不要忽视缓存不一致风险 ❌ 不要对所有数据使用相同的TTL