C#中MongoDB查询缓存实现咨询:基于FilterExpression与预填充缓存
基于FilterExpression的缓存实现与优化方案
一、如何基于filterExpression实现缓存
核心思路是将过滤条件+查询参数映射为唯一缓存键,结合数据变更时的同步更新逻辑,实现缓存与数据库的一致性:
生成唯一缓存键
不同的过滤条件、分页参数对应不同的缓存结果,必须生成唯一标识:- 对于
Expression<Func<TDocument, bool>>:通过表达式树解析或序列化工具将表达式转为字符串,再结合分页参数(page/pageSize)、实体类型名称拼接成键;为避免键过长,可将表达式字符串哈希(SHA256/MD5)后再拼接。
示例代码:var filterStr = JsonConvert.SerializeObject(filterExpression); var filterHash = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(filterStr))); var cacheKey = $"Query_{typeof(TDocument).Name}_{filterHash}_Page{page}_Size{pageSize}"; - 对于
FilterDefinition<TDocument>:MongoDB的FilterDefinition可转为BsonDocument,再序列化字符串后哈希,作为缓存键的核心部分。
- 对于
查询时的缓存逻辑
查询前先检查缓存是否存在对应键:- 存在则直接返回缓存结果;
- 不存在则查询数据库,将结果存入缓存后返回。
数据变更时的同步更新
新增/更新/删除操作时,需同步维护关联缓存:- 反向索引关联:在缓存查询结果时,额外维护一个“文档ID-缓存键”的映射缓存(键格式如
DocLinks_{typeof(TDocument).Name}_{DocId}),记录该文档被哪些查询缓存包含。 - 更新/删除:从反向索引中取出该文档关联的所有缓存键,删除或重新查询数据库更新这些缓存条目,最后更新反向索引。
- 新增:由于新增文档不会存在于旧的查询缓存中,可直接缓存单个文档(键格式如
Doc_{typeof(TDocument).Name}_{DocId}),并标记那些“可能包含新文档”的查询缓存为过期(或在下次查询时自动刷新)。
- 反向索引关联:在缓存查询结果时,额外维护一个“文档ID-缓存键”的映射缓存(键格式如
二、缓存整张表是否合理?
分场景判断:
- 适合场景:表数据量极小(如配置表、字典表,数百条以内)、查询频率极高且增删改极少。这种情况下,缓存整张表后在内存中过滤/分页,性能开销极低,同步更新只需替换整个缓存的表数据即可,实现成本低。
- 不适合场景:表数据量大(上万条及以上)、数据更新频繁。此时缓存整张表会占用大量内存,每次更新都要替换全量缓存,IO和内存开销大;且大表内存过滤/分页的性能优势会被内存占用成本抵消。
三、优化建议
- 区分单个文档缓存与查询缓存
对于频繁按ID查询的场景,单独缓存单个文档,复杂过滤查询缓存结果集。增删改时优先更新单个文档缓存,再处理关联的查询缓存,减少不必要的全量更新。 - 设置缓存过期兜底
即使同步更新缓存,也可能因异常导致一致性问题。给所有缓存设置合理的过期时间(如1小时),作为兜底策略,避免缓存永久不一致。 - 避免过度缓存
对于低频查询或结果集极易变化的查询,无需缓存,直接查数据库即可,减少维护成本。
内容的提问来源于stack exchange,提问作者Mehmet Ceylan
相关产品推荐
相关产品推荐

