使用ORM映射工具对数据库进行操作,由于对象关系(数据库表关系)间的复杂关系,往往导制N+1次数据查询,即:本来是查询某一数据对象,却因为对象关系把与对象相关的所有数据都从数据库加载到内存,而这些数据并没有被使用到,然后又马上丢弃,有时甚至只是一个简单的查询,却将整个数据库读取到内存后又丢弃,如此反反复复,整个数据库服务器都在不停的运转,高磁盘IO,高CPU占用率,完全是无用功。
数据库缓存的作用是只在数据第一次被访问时才从数据库中读取数据,将数据放在存储介质中,以后查询相同的数据则直接从存储介质(内存)中返回,这样速度有明显的提升。基本原理是用空间换时间,理论性的知识这里不必一一列出,可以在网络上搜索。我们重点讲述的是应该如何做。
NHibernate的数据缓存包含下面几个层次:
1)事务级缓存
2)应用级缓存
3)分布式缓存
具体针对NHibernate而言,采用两级缓存策略,其过程为:
(1)根据条件查询数据的时候,总是发出一条select * from table_name where …. 这样的SQL语句查询数据库,一次获得符合条件的数据对象。
(2) 把获得的所有数据对象根据ID放入到第二级缓存中。
(3) 当NHibernate根据ID访问数据对象的时候,首先从Session一级缓存中查询;如果查不到,在配置了二级缓存的情况下,那么从二级缓存中查;如果查不到,再查询数据库,把结果按照ID放入到缓存。
(4) 删除、更新、增加数据的时候,同时将数据更新至缓存。
CastleActiveRecord类如何定义才能提供缓存支持?
如果对象有一对多的关系,一般都是配置了延迟加载来避免读取无用数据,同时应该打上缓存标记:
如此这般,您的数据访问实体对象即支持了缓存(在配置了缓存的前提下,如果没有配置缓存,则完全没有副作用)。
NHibernate支持查询缓存,即相同条件的查询不必重复的读取数据库,代码片断如下:
/// <summary>
/// 查找指定字典分类的字典
/// </summary>
/// <param name="ownerID">所有者编号</param>
/// <param name="category">字典分类</param>
/// <returns>字典</returns>
public static DALDictionaries[] FindByCategory(int ownerID, string category)
{
//常规写法
string queryString = @"select distinct dics from DALDictionaries as dics where dics.OwnerID=:OwnerID and dics.Category=:Category order by dics.SortIndex";
SimpleQuery<DALDictionaries> sq = new SimpleQuery<DALDictionaries>(queryString);
sq.SetParameter("OwnerID", ownerID);
sq.SetParameter("Category", category);
return sq.Execute();
//查询缓存的写法;
//string queryString = @"select distinct dics from DALDictionaries as dics where dics.OwnerID=:OwnerID and dics.Category=:Category order by dics.SortIndex ";
//ISessionFactoryHolder sessionFactory = ActiveRecordMediator.GetSessionFactoryHolder();
//ISession session = sessionFactory.CreateSession(typeof(DALDictionaries));
//IQuery query = session.CreateQuery(queryString);
//query.SetParameter("OwnerID", ownerID);
//query.SetParameter("Category", category);
//query.SetCacheable(true);
//IList<DALDictionaries> list = query.List<DALDictionaries>();
//return list.ToArray();
}
配置CastleActiveRecord类启用二级缓存的代码如下:
手动配置代码片断:
properties.Add("adonet.batch_size", "50");
properties.Add("use_outer_join", "true");
properties.Add("cache.provider_class", "NHibernate.Cache.HashtableCacheProvider,NHibernate");
properties.Add("cache.use_second_level_cache", "true");
properties.Add("cache.use_query_cache", "true");
properties.Add("cache.default_expiration", "300");
properties.Add("default_expiration", "300");
properties.Add("expiration", "300");
配置文件代码片断:
<activerecord>
<config>
<add key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
<add key="connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
<add key="dialect" value="NHibernate.Dialect.MsSql2005Dialect"/>
<add key="connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
<add key="connection.connection_string" value="Data Source=.;Initial Catalog=DateBaseName;UID=sa;Password="/>
<add key="adonet.batch_size" value="50"/>
<!--
<add key="cache.provider_class" value="NHibernate.Cache.HashtableCacheProvider,NHibernate"/> -->
<!-- -->
<add key="cache.provider_class" value="NHibernate.Caches.MemCache.MemCacheProvider,NHibernate.Caches.MemCache"/>
<add key="cache.use_second_level_cache" value="true"/>
<add key="cache.use_query_cache" value="true"/>
<add key="cache.default_expiration" value="300"/>
<add key="default_expiration" value="300"/>
<add key="expiration" value="300"/>
</config>
</activerecord>
NHibernate提供了好几种缓存驱动,不同的缓存实现在用途及性能上有很大的区别,具体内容搜索网络资源。
为了更好的使用数据缓存,应注意以下几点:
1、如果一个实体标记了缓存属性,则无论该类是 通过ID查询还是其它方式的查询得到的结果,都会自动缓存。 所以,不必担心结果是否能够按照预期的需要缓存。
2、查询缓存如何使用? 在CastleActiveRecord中的查询类没有提供对查询缓存的支持,只能使用NHibernate的查询才可以,例子如上所述。
3、缓存的性能,缓存在一定程度上可以提高应用的性能,但需要正确使用,如果使用不慎,缓存反而成为负担,比如,在应用中如果使用NHibernate.Caches.Prevalence 作为缓存提供程序,如果数据量大,它要在指定目录下写入缓存文件,IO消耗相当大,虽然数据库访问少了,但是应用的IO却增长,还不如不使用缓存。因此,使用缓存时应尽量避免使用文件型缓存,应使用内存型缓存。
4、缓存的策略。查询缓存应只对只读性数据进行缓存,如果是经常读写的数据,可能造成数据不一致,至于造成数据不一致的原因没有花时间根究。
5、如果实体有继承关系,必须在被继承的类上也标记使用 缓存,否则,子类的缓存无效。
6、如果对查询进行缓存,必须实体也要标记缓存,否则查询缓存无效。
7、如果对象间存在一对多关系时,在多端新增数据时,为了维护对象的一致性,应在多端添加对新增对象的引用,否则从缓存取父对象时并没有包含多端的新增数据。代码例子如下:
if (!party.ContactMechanisms.Contains(contactMechanism))
{
party.ContactMechanisms.Add(contactMechanism);
}