之前三篇讲了MoSonic整体设计上的思路参考。这篇讲一下其中的一些细节优化方面遇到的问题。

Cache Money对于查询类型的要求限制的非常死,整个系统变成只有两种查询:

  1. select id from table where simple condition
  2. select * from table where id = XXX

假设有user表,有password跟is_banned两列。已知user_name,要如何获得is_banned?

Cache Money的限制使得必须得有两个查询:

  1. select id from user where user_name=XXX
  2. select * from user where id=XXX

然后再从第二个查询里面的*去获得is_banned。

有人会认为这很傻,完全可以用一个: select is_banned from user where user_name=XXX的查询来替代,实现减少sql查询次数,以及sql返回的数量两方面的优化。

这是“优化”是一个很有意思的问题。如果我们要认为 select is_banned from user where user_name=XXX 是一个优化;首先必须清楚了解这条sql中数据库究竟做了什么。

首先,要支持where user_name=XXX的查询,user_name这列是必须加索引的。

那么,user_name的索引中包含了什么?这实质上是user_name对user表主键(id)的索引!

数据库要返回is_banned这个数据,实际上它是需要:

  1. 首先查询user_name的索引,获得id的对应的row指针
  2. 根据指针定位至真实的row,并在row找到is_banned的列的数据,才能返回。

上述的数据库内部操作,跟cache money的两个查询实质上是一一对应的。

唯一不同的是,数据库查询是硬盘寻址,而cache money是memcache访问。

memcache与传统硬盘谁快?memcache与SSD硬盘谁快?一个memcache集群与单个SSD物理硬盘谁快?高并发情景下又如何?

我们的初衷是要实现一个分布式的系统以解决性能问题,但为什么突然又要把单个硬盘上的操作视为一个优化?

有人还会认为一开始user_name的索引其实建错了,应该是建user_name + is_banned的联合索引,这样数据库内部在执行select is_banned from user where user_name=XXX 这个查询的时候,就可以直接在索引数据上获得is_banned的值,而无需多一次寻址。

这种又是不是优化?

我们先把user表的分布式放一边,仅考虑单机的情况;我们假设它是优化。

我们原来的设计中,cache money在典型情况下是缓存了100%的查询,对于这种新冒出来的查询,要怎么缓存?

仔细研究的话,这种缓存其实是可以做的;但查询类型增加了,缓存类型增加了,相应的缓存更新策略复杂度也会增加。

这里的实际问题是:在框架中增加这些复杂度/编码量,缓存占用,数据库的索引空间;而换来的查询优化是否值得?

这其实不可一概而论,是要视业务场景而定。

如果,以用户名获得密码的查询在产品业务中的调用异常频繁,甚至就是业务本身100%的调用,那么神马编码复杂度,缓存占用,索引空间都是浮云,这是必须付出的代价。

OK,我们决定做不惜一切代价来实现这个优化;这其实又引入了另一个问题:

MoSonic支持的查询类型是否是优化此业务的唯一选择?

可不可以在不改变MoSonic现有结构的情况下来实现这样的优化?

这个碰巧是有现成的案例,人人网,为了高效的检查一个用户帐号是否有效,专门搞了一整台服务器做中间层缓存所有人人网用户是否被ban的数据,并提供高效的网络接口供系统其他模块调用。

具体可查阅人人网中间层:实践篇

所以,答案是可以。

MoSonic本身是作为一个通用的底层ORM框架,它的分布式支持,缓存支持,都是透明的;但也不强制开发者必须使用;对Cache Money不符合的业务场景,那么是可以把缓存禁用掉;开发者根据自己业务特性去寻求更贴切的缓存方案。

设计一个通用的数据库访问方案,实质上并不应该对特定业务做特定场景下的优化,而应该留出来空间,让开发者在遇到特殊场景时有实现特定优化的可能。

如果说,MoSonic把cache money这层透明缓存做成强制的,开发者连禁用某个表的缓存控制都没有,那才是设计的问题。

上述,实际上仅讨论了一个小细节在单机场景下的情况,同一个问题如果延伸至分布式的场景,也会引发很多很有意思的话题,这篇就先不累述了。