对象缓存和n+1问题分析

我们常见的web应用,性能瓶颈往往是数据库查询,因为应用服务器层面可以水平扩展,但是数据库是单点的,很难水平扩展,当数据库服务器发生磁盘IO,往往无法有效提高性能,因此如何有效降低数据库查询频率,减轻数据库磁盘IO压力,是web应用性能问题的根源。

对象缓存是所有缓存技术当中适用场景最广泛的,任何web应用,即使实时性要求很高,你也可以使用对象缓存,而且好的ORM实现,对象缓存是完全透明的,完全不需要你的程序代码进行硬编码。

用不用对象缓存,怎么用对象缓存,不是一个简单的代码调优技巧,而是整个应用的架构问题。在你开发一个应用之前,你就要想清楚,这个应用最终的场景是什么?会有多大的用户量和数据量。你将采用什么方式来架构这个应用:

也许你偏好对SQL语句级别的优化,数据库设计当大表有很多冗余字段,会尽量消除大表之间的关联关系,当数据量很大以后,选择分库分表的优化方式,这是目前业界常规做法。但是也可以选择使用ORM的对象缓存优化方式:数据库设计避免出现大表,比较多的表关联关系,通过ORM以对象化方式操作,利用对象缓存提升性能。举个例子:

论坛的列表页面,需要显示topic的分页列表,topic作者的名字,topic最后回复帖子的作者,常规做法:

select ... from topic left join user left join post .....

你需要通过join user表来取得topic作者的名字,然后你还需要join post表取得最后回复的帖子,post再join user表取得最后回贴作者名字。也许你说,我可以设计表冗余,在topic里面增加username,在post里面增加username,所以通过大表冗余字段,消除了复杂的表关联:

select ... from topic left join post...

OK,且不说冗余字段的维护问题,现在仍然是两张大表的关联查询。然后让我们看看ORM怎么做?

select * from topic where ... --分页条件
就这么一条SQL搞定,比上面的关联查询对数据库的压力小多了。 也许你说,不对阿,作者信息呢?回贴作者信息呢?这些难道不会发送SQL吗?如果发送SQL,这不就是臭名昭著的n+1条问题吗? 你说的对,最坏情况下,会有很多条SQL:

select * from user where id = topic_id...;
....
select * from user where id = topic_id...;

select * from post where id = last_topic_id...;
....
select * from post where id = last_topic_id...;

select * from user where id = post_id...;
....
select * from user where id = post_id...;

事实上何止n+1,根本就是3n+1条SQL了。那你怎么还说ORM性能高呢? 因为对象缓存在起作用,你可以观察到后面的3n条SQL语句全部都是基于主键的单表查询,这3n条语句在理想状况下(比较繁忙的web网站的热点数据),全部都可以命中缓存。所以事实上只有一条SQL,就是:

select * from topic where ...--分页条件 

这条单表的条件查询和直接使用join查询SQL通过字段冗余简化过后的大表关联查询相比,当数据量大到一定程度以后对数据库磁盘IO的压力很小,这就是对象缓存的真正威力!

更进一步分析,使用ORM,我们不考虑缓存的情况,那么就是3n+1条SQL。但是这3n+1条SQL的执行速度一定比SQL的大表关联查询慢吗?不一定!因为使用ORM的情况下,第一条SQL是单表的条件查询,在有索引的情况下,速度很快,后面的3n条SQL都是单表的主键查询,在繁忙的数据库系统当中,3n条SQL几乎可以全部命中数据库的data buffer。但是使用SQL的大表关联查询,很可能会造成大范围的表扫描,造成频繁的数据库服务器磁盘IO,性能有可能是非常差的。

因此,即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过SQL的大表关联查询,而且对数据库磁盘IO造成的压力要小很多。这个结论貌似令人难以置信,但经过我的实践证明,就是事实。前提是数据量和访问量都要比较大,否则看不出来这种效果。

对象缓存的命中率

应用场景

即使是web应用,也要看访问的频度,一个极少被访问到的缓存等于没有什么效果。一般来说,互联网网站是非常适合缓存应用的场景。

缓存的粒度

毫无疑问,缓存的粒度越小,命中率就越高,对象缓存是目前缓存粒度最小的,因此被命中的几率更高。举个例子来说吧:你访问当前这个页面,浏览帖子,那么对于ORM来说,需要发送n条SQL,取各自帖子user的对象。很显然,如果这个user在其他帖子里面也跟贴了,那么在访问那个帖子的时候,就可以直接从缓存里面取这个user对象了。

架构的设计

架构的设计对于缓存命中率也有至关重要的影响。例如你应该如何去尽量避免缓存失效的问题,如何尽量提供频繁访问数据的缓存问题,这些都是考验架构师水平的地方。再举个例子来说,对于论坛,需要记录每个topic的浏览次数,所以每次有人访问这个topic,那么topic表就要update一次,这意味着什么呢?对于topic的对象缓存是无效的,每次访问都要更新缓存。那么可以想一些办法,例如增加一个中间变量记录点击次数,每累计一定的点击,才更新一次数据库,从而减低缓存失效的频率。

缓存的容量和缓存的有效期

缓存太小,造成频繁的LRU,也会降低命中率,缓存的有效期太短也会造成缓存命中率下降。

所以缓存命中率问题不能一概而论,一定说命中率很低或者命中率很高。但是如果你对于缓存的掌握很精通,有意识的去调整应用的架构,去分解缓存的粒度,总是会带来很高的命中率的。

时间: 2025-01-20 14:32:38

对象缓存和n+1问题分析的相关文章

Memcached分布式内存对象缓存系统简单的使用过程分析

风信网(ithov.com)原创文章:Memcached是一套分布式内存对象缓存系统,用于在动态系统中减少数据库负载,进而提升系统性能.Memcached在很多时候都作为数据库的前端cache使用,因为它比数据库少了很多SQL解析.磁盘操作等开销,而且使用内存来管理数据,所以它可以提供比直接读取数据库更好的性能.另外,Memcached也经常作为服务器之间数据共享的存储媒介.学习完本章,相信读者能够对Memcached有一个全面的了解,使构建一套属于自己的分布式内存对象缓存系统变成很简单的事情.

Ehcache 整合Spring 使用页面、对象缓存(转)

Ehcache在很多项目中都出现过,用法也比较简单.一般的加些配置就可以了,而且Ehcache可以对页面.对象.数据进行缓存,同时支持集群/分布式缓存.如果整合Spring.Hibernate也非常的简单,Spring对Ehcache的支持也非常好.EHCache支持内存和磁盘的缓存,支持LRU.LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件.同时它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度.

java中request对象各种方法的使用实例分析_java

本文实例讲述了java中request对象各种方法的使用.分享给大家供大家参考,具体如下: request对象是从客户端向服务器端发出请求,包括用户提交的信息以及客户端的一些信息.request对象是javax.servlet.http.HttpServletRequest类的实现实例. request对象封装了浏览器的请求信息,通过request对象的各种方法可以获取客户端以及用户提交的各项请求信息. 使用request对象获取客户端提交的请求参数的常用方法如下: 1.String getPa

MySQL 5.7: Innodb事务对象缓存

在5.7中,Innodb引入了一个pool结构来专门做对象缓存重用.这可能会提升短连接场景的性能.本文的目的主要是理清其代码结构.当然主要是作为一个C++小白,学习下C++的一些代码STYLE. 代码版本:MySQL 5.7.5 我们这里以事务对象池为例 1.初始化的过程如下: trx_pools = UT_NEW_NOKEY(trx_pools_t(MAX_TRX_BLOCK_SIZE)); trx_pools 全局变量,也是操作trx pool的接口,类型为trx_pools_t 其定义如下

Memcached分布式内存对象缓存系统的性能监控

风信网(ithov.com)原创文章:本文将从三个方面向你介绍Memcached分布式内存对象缓存系统的管理与http://www.aliyun.com/zixun/aggregation/14216.html">性能监控,主要的内容涉如何管理Memcached,如何监控Memcached及随着Memcached应用不断衍生出来的各变种产品的介绍. 如何管理Memcached 1.通过Memcached的监听端口进行管理 Memcached的管理相对比较容易,通过命令行登录到Memcach

WordPress的对象缓存介绍和使用方法

WordPress 对象缓存的函数 使用 WordPress 对象缓存技术其实是非常简单的: 使用 wp_cache_set() 把数据写到缓存中. 使用 wp_cache_get() 到缓存中读取数据. 使用 wp_cache_delete() 删除缓存. 这些函数会涉及到的四个参数: $key: 对象的 key. $data: 要存的值. $group: 分组,可选参数,用来把缓存对象就行分组. $expire: 过期时间,可选参数,如果是默认缓存,这个函数没用,如果是内存缓存,是设置缓存的

Ehcache 整合Spring 使用页面、对象缓存

Ehcache在很多项目中都出现过,用法也比较简单.一般的加些配置就可以了,而且Ehcache可以对页面.对象. 数据进行缓存,同时支持集群/分布式缓存.如果整合Spring.Hibernate也非常的简单,Spring对Ehcache的支持也非常好. EHCache支持内存和磁盘的缓存,支持LRU.LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件.同时 它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速

使用ConcurrentDictionary替代Hashtable对多线程的对象缓存处理

在之前一段时间里面,我的基类多数使用lock和Hashtable组合实现多线程内缓存的冲突处理,不过有时候使用这两个搭配并不尽如人意,偶尔还是出现了集合已经加入的异常,对代码做多方的处理后依然如故,最后采用了.NET 4.0后才引入的ConcurrentDictionary多线程同步字典集合,问题顺利解决. 1.使用lock和Hashtable组合实现 在我的基类里面,构建业务对象,一般用BLLFactory<T>.Instance就可以获得对应业务对象的应用了. var result = B

Android开发笔记之图片缓存、手势及OOM分析_Android

把图片缓存.手势及OOM三个主题放在一起,是因为在Android应用开发过程中,这三个问题经常是联系在一起的.首先,预览大图需要支持手势缩放,旋转,平移等操作:其次,图片在本地需要进行缓存,避免频繁访问网络:最后,图片(Bitmap)是Android中占用内存的大户,涉及高清大图等处理时,内存占用非常大,稍不谨慎,系统就会报OOM错误. 庆幸的是,这三个主题在Android开发中属于比较普遍的问题,有很多针对于此的通用的开源解决方案.因此,本文主要说明笔者在开发过程中用到的一些第三方开源库.主要