NHibernate 二级缓存:查询缓存未按预期工作
NHibernate second level cache: Query cache doesn't work as expected
我使用的包:
NHibernate 5.2.1
NHibernate.Caches.SysCache 5.5.1
NH 缓存配置:
<configuration>
<configSections>
<section name="syscache" type="NHibernate.Caches.SysCache.SysCacheSectionHandler,NHibernate.Caches.SysCache" />
</configSections>
<syscache>
<!-- 3.600s = 1h; priority 3 == normal cost of expiration -->
<cache region="GeoLocation" expiration="3600" sliding="true" priority="3" />
</syscache>
</configuration>
我想使用它们唯一的主键查询一堆位置。在此单元测试中,我使用不同的会话但使用相同的会话工厂模拟两个请求:
[TestMethod]
public void UnitTest()
{
var sessionProvider = GetSessionProvider();
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().Where(x => x.LocationId.IsIn(new[] {147643, 39020, 172262}))
.Cacheable()
.CacheRegion("GeoLocation")
.List();
Assert.AreEqual(3, locations.Count);
}
Thread.Sleep(1000);
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().Where(x => x.LocationId.IsIn(new[] { 39020, 172262 }))
.Cacheable()
.CacheRegion("GeoLocation")
.List();
Assert.AreEqual(2, locations.Count);
}
}
如果以完全相同的顺序查询完全相同的 ID,则第二次调用将从缓存中获取对象。但是,在此示例中,仅使用先前提交的 ID 中的两个来调用查询。虽然位置已被缓存,但第二个查询将从数据库中获取它们。
我希望缓存像 table 一样先被查询。只有尚未缓存的 ID 才应触发数据库调用。但显然整个查询似乎是缓存对象的哈希键。
有什么方法可以改变这种行为吗?
没有部分查询缓存的概念,它是全有或全无:如果找到此 exact 查询的结果 - 使用它们,否则查询数据库。这是因为查询缓存系统没有关于查询含义的特定知识(例如,它无法推断出特定查询的结果是某个缓存结果的子集这一事实)。
换句话说,NHibernate 中的查询缓存充当文档存储而不是关系 table 存储。文档的键是查询的 SQL(在 linq 的情况下是表达式树的一些文本表示)、所有参数类型和所有参数值的组合。
为了解决您的特殊情况,我建议您进行一些性能测试。根据测试和数据集大小,有一些可能的解决方案:在客户端过滤缓存结果(如下所示),或者不使用查询缓存,或者您可以在应用程序级别为特定查询实现一些缓存机制。
[TestMethod]
public void UnitTest()
{
var sessionProvider = GetSessionProvider();
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>()
.Cacheable()
.CacheRegion("GeoLocation")
.List()
.Where(x => new[] {147643, 39020, 172262}.Contains(x.LocationId))
.ToList();
Assert.AreEqual(3, locations.Count);
}
Thread.Sleep(1000);
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().
.Cacheable()
.CacheRegion("GeoLocation")
.List()
.Where(x => new[] {39020, 172262}.Contains(x.LocationId))
.ToList();
Assert.AreEqual(2, locations.Count);
}
}
有关 (N)Hibernate 查询缓存如何工作的更多信息,请参见 here。
我使用的包:
NHibernate 5.2.1
NHibernate.Caches.SysCache 5.5.1
NH 缓存配置:
<configuration>
<configSections>
<section name="syscache" type="NHibernate.Caches.SysCache.SysCacheSectionHandler,NHibernate.Caches.SysCache" />
</configSections>
<syscache>
<!-- 3.600s = 1h; priority 3 == normal cost of expiration -->
<cache region="GeoLocation" expiration="3600" sliding="true" priority="3" />
</syscache>
</configuration>
我想使用它们唯一的主键查询一堆位置。在此单元测试中,我使用不同的会话但使用相同的会话工厂模拟两个请求:
[TestMethod]
public void UnitTest()
{
var sessionProvider = GetSessionProvider();
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().Where(x => x.LocationId.IsIn(new[] {147643, 39020, 172262}))
.Cacheable()
.CacheRegion("GeoLocation")
.List();
Assert.AreEqual(3, locations.Count);
}
Thread.Sleep(1000);
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().Where(x => x.LocationId.IsIn(new[] { 39020, 172262 }))
.Cacheable()
.CacheRegion("GeoLocation")
.List();
Assert.AreEqual(2, locations.Count);
}
}
如果以完全相同的顺序查询完全相同的 ID,则第二次调用将从缓存中获取对象。但是,在此示例中,仅使用先前提交的 ID 中的两个来调用查询。虽然位置已被缓存,但第二个查询将从数据库中获取它们。
我希望缓存像 table 一样先被查询。只有尚未缓存的 ID 才应触发数据库调用。但显然整个查询似乎是缓存对象的哈希键。
有什么方法可以改变这种行为吗?
没有部分查询缓存的概念,它是全有或全无:如果找到此 exact 查询的结果 - 使用它们,否则查询数据库。这是因为查询缓存系统没有关于查询含义的特定知识(例如,它无法推断出特定查询的结果是某个缓存结果的子集这一事实)。
换句话说,NHibernate 中的查询缓存充当文档存储而不是关系 table 存储。文档的键是查询的 SQL(在 linq 的情况下是表达式树的一些文本表示)、所有参数类型和所有参数值的组合。
为了解决您的特殊情况,我建议您进行一些性能测试。根据测试和数据集大小,有一些可能的解决方案:在客户端过滤缓存结果(如下所示),或者不使用查询缓存,或者您可以在应用程序级别为特定查询实现一些缓存机制。
[TestMethod]
public void UnitTest()
{
var sessionProvider = GetSessionProvider();
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>()
.Cacheable()
.CacheRegion("GeoLocation")
.List()
.Where(x => new[] {147643, 39020, 172262}.Contains(x.LocationId))
.ToList();
Assert.AreEqual(3, locations.Count);
}
Thread.Sleep(1000);
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().
.Cacheable()
.CacheRegion("GeoLocation")
.List()
.Where(x => new[] {39020, 172262}.Contains(x.LocationId))
.ToList();
Assert.AreEqual(2, locations.Count);
}
}
有关 (N)Hibernate 查询缓存如何工作的更多信息,请参见 here。