chiname

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

系统性能提升之道--内存镜像表

系统性能提升之道--内存镜像表

 提出问题

对于一个系统,我们在设计开发时,不得不考虑系统的性能问题,硬件的提速可以缓减系统日益增长的消耗,但我们也不能肆无忌惮的扩展系统而不考虑性能的提高,我们应该重视资源的有限性。

为了说明问题,我先举个例子,有两个表如下:

Items物料表

字段名

数据类型

描述

ID

Varchar(50)

主键(PK

Name

Varchar(50)

物料名称

CatalogId

Varchar(50)

物料组ID(FK,关联Catalog)

……

….

…..

 

Catalog物料组表

字段名

数据类型

描述

ID

Varchar(50)

主键(PK

Name

Varchar(50)

物料名称

 

物料表的CatalogId关联物料组表的主键

要求:我们在显示物料信息时,显示此物料的物料组名称

      传统的非ORM模式下,我们一般可以采用

Select Items.*,Catalog.Name as “catalogName” from Items,Catalog where Items.CatalogId=Catalog.ID

的语句来读取,然后可以在Grid上直接绑定”catalogName”来显示物料组名。这种非ORM模式开发的系统,系统扩展性非常差、数据库移植性差、开发效率低下,所以大家也都开始使用ORM的开发模式。此文章不推荐、不讲述非ORM的开发。

      ORM模式下,我们把这两个表映射为“ItemsEntity物料实体类”与“CatalogEntity物料组实体类”。然后我们在Retrieve到一个物料对象aItem后,在显示时要求显示物料组名的话,就需要CatalogEnityRetrieveg一个物料组对象aCatalog,使用此aCatalogName属性才能显示出来。

      我们也发现了,在ORM模式下,由于采用了OO的编程模式,对开发者来说带来了很多的方便,但在过程中,进行了两次数据库访问,我们试想一下,要是此Items物料表有十个类型的外键关联,那么一个Items的显示,将进行十一次数据访问,对于极其宝贵的数据库访问资料来说,性能大打折扣了。

 

NHibernate中的解决方案:

NHibernate中,可以采用“Relation”的方式来解决,需要在XML中配置十个one-to-many的“Relation”,然后采用非Lazy的方式进行一次性读取,这种方式是把十一次的数据访问放在一起执行了,这看起来可以很方便的实现;但如果有N个人都要进行这样的操作,那么数据访问数还是非常大(N*11)。而且我非常不喜欢NH里的“Relation”,因为这“Relation”还存在一个问题,返回的是对象数组,在没有Grid控件绑定的Java中,这可能是不错的方式,可是在.NET中,我们更会选择Grid进行DataTable的数据绑定,因此这种“Relation”无法很方便的返回DataTable,所以从目前来看,这种“Relation”在.NET中非常不适用的。这也是我的SPL是没有添加“Relation”的原因之一。

 

缓存内存表的概念:

   对于上面的这种情况,我们可以思考一下,采用另一种更好的方式解决。我们可以把这个物料组表的内容放到内存里,作为一个“内存镜像表”,此“内存镜像表”是数据库中实际表的一个镜像,实时保持与实际表的同步,我们在读取时:

if(内存镜像存在)

       从内存直接读取;

else

       {

              从数据库读取;

              建立内存镜像表,把数据库中的数据镜像到内存表中;

}


   这样的方式可以减少很多的数据访问,就拿上面的例子中,在第一个人进行了十一次访问后,以后的N个人执行,只要执行N次数据访问了,速度将会有一个突破性的提高。

 

内存镜像表的可行性分析:

   对于这种“内存镜像表”的可行性我们先从数据库数据来分析。

   一个系统将包括很多的数据,但我们可以对这些数据进行分一下类:

   “维护性数据”:

        这是指那些在系统中的基本维护性数据,比如前面的物料组、客户类型、付款方式、结算方式、销售类型、货币种类等等,大家都可以举出很多很多这种类似的数据,这些数据存在的共同点是:

       数据量小:这种数据一般在50条记录以内,都是一些类型,分类之类的数据

       字段少:这种数据的字段都是很少的,主要是ID、名称、状态等

       使用频繁高:这种数据的使用率非常频繁,因为在其他表中作为外键,经常会被使用

       维护少:这种数据一般在系统启用前期进行维护,大部分情况下不维护

      因此,具有这些特点的数据,我们就非常有理由放到内存中进行缓存起来。

“操作型数据”:

    这种数据的特点是数据量比较大,字段也比较多,比如物料、客户等信息,不适合用到内存中。

 “日志型数据”:

    这种数据数据量就更多的,比如订单、订单明细、操作记录等,这就更不合适了。

因此我们主要是考虑“维护性数据”,大家想想在你的系统中,会有多少是这种“维护性数据”,对于结构相似的“维护性数据”都会采用“数据字典”的方式来对待。

  

内存镜像表的可操作性分析:

在可行性分析后,我们要考虑,在实际操作起来要注意哪些。

大家也很容易发现,唯一注意点就是“如何保持实时的镜像关系”,也就是要实时保持内存中镜像与实际的数据库表数据一致。

那么我们就要保证在进行更新(包括新增,更新,删除)时能同时更新内存表数据就可以了。

对于非ORM的开发模式来说,使用Sql语句进行数据访问的,要实现这种“实时同步”是很难的,幸好的是我们现在有了ORM的开发模式,在ORM上,我们的数据访问不是直接使用SQL的,而是通过中间的持久层(PersistenceLayer)来实现的,因此,这个持久层(PersistenceLayer)为实现这种同步提供了必不可少的条件。

OR Mapping时,我们标识一个实体为“需保存到内存镜像”,那么持久层(PersistenceLayer)在进行此对象的Save()Update()Delete()时,自动进行“内存同步处理”即可,而且这是完全可实现的。

好了,我们回到前面的例子中,那样的一个物料Retrieve()操作,其实只要两次访问数据库,而且当有N个用户进行类似的操作时,由于“内存镜像表”在第一次就载入到内存了,因此访问数据库次数为N次了。

 

内存镜像表的弊端性:

   从上面的操作性我们就可以看出了,效率的提高是建立在内存的牺牲上的,如果有过多的“维护型数据”使用“内存镜像”,那么,整个内存消耗将非常大,所以,在考虑使用多少“内存镜像表”时,是要考虑服务器的承受能力的。

   但我想,增加内存,提高性能这是很多客户所在追求的。因此,我们只要能仔细分析,是可以得出一种好的“内存镜像表”量的。

 

内存镜像表的实现性:

   这种“内存镜像表”在我的SPL(SmartPersistenceLayer)已经实现了,不知道NH等其他的持久层中是否也有类似的功能。

   SPL中的使用:

ClassMap中定义实体类的IsSaveToMemorytrue即可。例:

   

 <class name="StudentEntity" table="Student" database="MyDB" IsSaveToMemory="true">

    
<attribute name="Id" column="Id" type="Integer" increment="true" key="primary" />

    
<attribute name="No" column="No" type="String" key="primary" />

    
<attribute name="Name" column="Name" type="String" />

    
<attribute name="Birthday" column="Birthday" type="Date" />

    
<attribute name="Grade" column="Grade" type="Integer" />

    
<attribute name="Score" column="Score" type="Decimal" />

</class>


SPL的实现细节:

SPL中有个静态类,它的ArrayList里存放各种“内存镜像表”的“镜像DataTable”,在进行Retrieve()时,先判断此ArrarList中是否存在此实体的“镜像”,如果有的话,则直接从此“镜像DataTable”中Retrieve()出来,还有RetrieveCriteira(获取标准)时,会从此“镜像DataTable”中使用Select符合条件的,生成新的“镜像DataTable”返回出来。

SPL的实体在进行Save()Delete()UpdateCriteria(更新标准)以及DeleteCriteria(删除标准)时都是会进行“镜像DataTable”的同步更新。

  但这种也不能完全意义上的保证“同步”,因为SPL支持Sql语句执行,所以不要使用Sql来更新“内存镜像”的表。要不然,就无法保证“内存同步”了。

 

 SPL的更多资料,请参考SPL专栏:http://www.cnblogs.com/tintown/category/12787.html 

                                                          听棠

                                                       2005-3-23

posted on 2005-03-23 21:51 听棠.NET 阅读(675) 评论(19)  编辑 收藏

评论

# re: 系统性能提升之道--内存镜像表 2005-03-23 22:03 木野狐
好文章,受教了!
  

# re: 系统性能提升之道--内存镜像表 2005-03-23 22:53 dudu
对象数组可以直接绑定到DataGrid控件中。
  

# re: 系统性能提升之道--内存镜像表 2005-03-23 22:58 laserman
good,不过分布式处理将无法保证同步。
  

# re: 系统性能提升之道--内存镜像表 2005-03-23 23:00 dudu
ASP.NET 1.1中的缓存机制也类似于“内存镜像表”, 只不过在数据库更新时, 缓存中的数据不能及时更新。ASP.NET 2.0已经解决这个问题。
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 08:26 听棠.NET
关于ASP.NET 2.0对缓存的介绍可以看:
ASP.NET 2.0 中改进的缓存功能
但这也不是我们想要的效果,他的这种机制还是无法保持内存的同步的。而且我想我也不会去使用Sql编程。

  

# re: 系统性能提升之道--内存镜像表 2005-03-24 09:13 努力學習
大哥寫的太好﹑希望能繼續看到大哥的新作。
也要注意休息啊﹗
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 09:17 none
去年夏天,我搞过一个东西,可以自动的管理缓存,包括读取,更新等等,或许能达到你想要的效果的90%

有兴趣的话,可以MSN我发给你
cnlamar(at)hotmail.com
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 09:19 none
同时,数据不要一次性全部放到内存里,这样并不好,可以考虑一条一条,分别放内存里,需要的时候连续起来使用,再建立一个索引来维持连贯性,把缓存的粒度尽量的细化后,效果更佳
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 09:23 听棠.NET
一条一条是不现实的,我这个不是缓存,是一个数据表的镜像,因为随时都可能要求返回DataTable,因此在内存镜像中必须有一个完全的镜像,要不然分不清哪些是去数据库,哪些不去,反正不方便,增加压力。
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 09:37 Richer
好!
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 10:00 hawk6224

  

# re: 系统性能提升之道--内存镜像表 2005-03-24 10:13 milula
貌似访问多个异种数据库,因此内存还是数据层通过DAL访问,是吗
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 10:55 小强
内存镜像表的使用,跟缓存的使用,有相似之处。
以前我也是使用楼主提的类似内存镜像表的结构,
在服务器内存中维护一个DataSet,将稳定型数据(多读少写)
置于DataSet中,然后用特定策略维护数据一致性。
但这种方式对内存的要求比较高,而且对服务器的稳定性有
不少影响。

之后,就采用了类型微软Application Block架构的缓存处理
(也是内存中维护数据,hashtable结构),
而缓存数据以记录为单元,第一次使用时才置入缓存中,然后用特定策略(完全避免脏数据)维护数据一致性。这种方式减少内存中的数据量。但也有缺点,对于返回DataTable记录集,该种方式有明显缺陷。

这两种方式各有其应用范围吧。

  

# re: 系统性能提升之道--内存镜像表 2005-03-24 12:16 Shrek
企业应用中经常使用Farm的形式
如何处理Scale out?
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 16:04 Yok
然后采用非Lazy的方式进行一次性读取,这种方式是把十一次的数据访问放在一起执行了
====================
lazy="true"

而且我非常不喜欢NH里的“Relation”,因为这“Relation”还存在一个问题,返回的是对象数组,在没有Grid控件绑定的Java中,这可能是不错的方式,可是在.NET中,我们更会选择Grid进行DataTable的数据绑定,因此这种“Relation”无法很方便的返回DataTable,所以从目前来看,这种“Relation”在.NET中非常不适用的。
====================
没Relation就不叫object-RELATIONAL mapping了.
Winform的Grid控件能绑定的条件是对象实现了IList, 可以用<bag>; Webform的要求更低, 实现IEnumerable就可以了(说白了, 是集合就能绑定), 可以用<bag>或<set>

  

# re: 系统性能提升之道--内存镜像表 2005-03-24 17:27 听棠.NET
@ Yok :
哦,原来可以直接绑定啊。不过,竟然我对象的基类的IsPersistent属性也给带出来了。
象要是有十个关联的,要配置这么多关系,而且效率上也没有提高,我觉得这种利小于弊,增加这么多复杂度,就只为了那几个值。
我知道没有Relation就不是完整的ORM了,但我想理论归理论,有很多理论没有实际价值,那是美好的憧憬而已,ORM中还有些理论都没有实现的,就像共产主义就是理论,那是永远也到不了的理想社会,没有实际意义。我还是喜欢实践才是检验真理的唯一标准。
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 17:40 progame
就算是typed dataset,也支持relation的,我无法理解为什么relationship会成为orm的一个解决难题,内存镜像表还是一个缓存的实现,这在xpo、nhibernate中都已经实现,然而这一点恰恰是我不欣赏的地方,为什么?因为cache这一层完全可以在整个系统构架中独立于orm,我会在系统中来处理entity object的缓存和同步问题,而不是交给orm这一层来完成。
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 17:45 progame
在我的persistore中,entitylist对数据库是在需要时一次读取,延迟加载所有entity object的,也就是说如果我不访问这个entitylist,那么永远都不会去查询数据,如果访问了,只需要查询一次,而具体的对象是在需要时才创建的,而且支持可更新的数据绑定,可是我觉得无论是性能、方便性还是其它,我宁可选择dataset,用entitylist的唯一好处就是我内部是数据库平台无关的sql语句,但我现在已经倾向于通过xml定义data view,从而完成绑定、更新等操作,这样的data view做为一个object,其功能、灵活性、性能都远好于entitylist。
  

# re: 系统性能提升之道--内存镜像表 2005-03-24 18:43 Yok
估计你没有认真使用过任何一个真正的orm框架
orm的精髓不在于自动处理curd, 而是能用面向对象的方式去操作数据.
Relation并不单指能customer.Orders这样通过关联访问"几个值", 而是在查询中能使用面向对象的关联, 例如
"From Order order = where order.Customer.Name = 'Tom'" 查询属于Tom的订单
"From Customer customer where count(customer.Orders) > 100" 查订单数量大于100的客户
另外多态性也是很常用, 假如在权限系统中, 用户User和用户组Group都可以作为一个被授权的实体, 可以用IActor接口来作为User和Group的抽象. "From IActor actor, Resorces res where res.Name = 'xx' and actor in elements(res.GrantedActors)"可以查出拥有名为"xx"的资源的访问权限的User或Group
要节省写sql的时间的话, 倒不如用DAAB或者用ide生成data adapter + typed dataset
 re: 系统性能提升之道--内存镜像表
posted on 2005-03-25 08:54  把我的欢乐带给你  阅读(573)  评论(0)    收藏  举报