QueryCachingStrategies query-caching-strategies

这篇文章介绍了如何通过使用Redis、Memcached和数据库级别的缓存来实施多级查询缓存策略,以优化查询结果缓存、高读负载、减少数据库负载、提高响应时间等。包括缓存失效、TTL策略、缓存预热模式等关键技术点。

后端开发 0 次安装 0 次浏览 更新于 3/4/2026

实施查询缓存策略以提高性能。在设置缓存层、配置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

资源