ORM能有效地提升程序员的开发效率,程序员更喜欢操做对象而不是数据库,他们不关心也不想手写一堆SQL语句,毕竟一个公司里普通程序员要占多数,他们并非很是熟悉数据库,写出来的SQL执行效率也确定会有这样那样的问题。php
若是让程序员去操做对象,这就是他们的强项了:定义关系、使用ORM的方法和属性、获取/遍历结果等等。同时ORM又能够在内部对SQL语句及对象之间的关系进行优化,尽可能保证SQL高效地执行,甚至能够透明地加个缓存。这样一个共赢的结果,何乐而不为呢。程序员
若是是一些比较复杂的查询语句,只能经过写SQL语句来实现,这样的话,能够在语句的执行段外面套一层缓存判断,如:web
<?php
$result = $memcache->get('isobamapresident'); // fetch
if ($result === false)
{
// do some database heavy stuff
$db = DB::getInstance();
$votes = $db->prepare( "SELECT COUNT(*) FROM VOTES WHERE vote = 'OBAMA'" )->execute();
$result = ($votes > (USA_CITIZEN_COUNT / 2)) ? 'Sure is!' : 'Nope.'; // well, ideally
$memcache->set('isobamapresident', $result, 0);
}
所谓透明缓存,就是用户正常使用ORM,获取ORM的查询结果。而事实上ORM的结果集极可能是来自缓存而不是数据库。算法
<?php
//获取1小时前发布的文章
$time = time() - 86400;
ORM::factory('article')->where('created', '>', $time)->findAll();
//正常的结果是经过执行如下SQL语句返回的
//SELECT * FROM article WHERE created > $time
//但实际上多是从Memcache中读取的结果
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$memcache->get('some_key');
这样一来,php代码不用改变,但由于是从缓存中读取,因此数据的获取速度有保障,同时也减轻了数据库的压力,又是一个共赢的局面。sql
固然愿望是美好的,现实是残酷的,若是要达到上面所说的效果,须要费很多周折。数据库
在设计ORM的缓存前,先了解如下数据库的大体架构。以netlog的数据库架构变迁为例:缓存
单数据库 服务器
主库+从库 架构
保持主库+从库的架构,把读写最频繁的几个表分到单独的数据库服务器 分布式
把那几个读写最频繁的表也分红主从
出现了1040 too many connections
Sharding(水平分区)
数据库服务器/数据库/分区
这样基本上就能够应付正常的访问了,若是哪一个表数据量过大或链接过多,就Sharding一下。但随之而来的问题也很明显,好比:
<?php
//没有分区以前,能够经过下面几段代码来获取数据
$db = DB::getInstance();
$db->prepare("SELECT title, message FROM BLOG_MESSAGES WHERE userid = {userID}");
$db->assignInt('userID', $userID);
$db->execute();
$results = $db->getResults();
//假设将BLOG_MESSAGES按照用户id分配到了不一样的分区上,上面的代码就须要作一些改动
//最简单的就是在getInstance时把用户的id传过去,让ORM内部去找分区,至关于路由
$db = DB::getInstance($userID);
当要对数据进行分片时,应该考虑这两个问题:使用表的哪一列(sharding key)做为分割的依据;使用怎样的分割算法(sharding scheme)。使用哪一个key要看具体的应用。以博客为例,若是想要现实每一个用户的博客,那么userID就能够做为sharding key。如何根据sharding key来找到对应的分区通常有三种方法:取模(求余)、数据量、映射表。假设采用映射表的方法,若是要获取用户的博客,先要到映射表里找到该userID对应的分区,再从分区中找到userID对应的博客列表。随之而来的问题是:
若是要从不一样的分区获取数据,就不能经过JOIN/GROUP BY/ORDER BY/LIMIT来实现了。好比:
//获取最新的10条博客
SELECT * FROM BLOG_MESSAGES ORDER BY created DESC LIMIT 0, 10;
//若是数据在多个分区中,上面这条查询就失效了
要解决这个问题,最好从设计上就避免这些查询语句。也能够经过冗余来实现。
由于会在多个数据库之间更新数据,若是要保证数据一致性,就要实现分布式事务。
也能够经过一个小技巧来模拟分布式事务,好比有两台数据库服务器,这时能够先开启一个事务,但只在保证两台服务器都正常的状况下才一一提交事务。固然两次事务的提交也会有延迟,但相对来讲更加靠谱。
若是基于用户ID进行分区,可能会出现分区之间的不平衡,好比一些活跃的用户都被分到了同一分区,而沉默用户被分到了另外一个分区,这时量贩额分区的压力明显不同。因此分区的算法很重要。
由于数据在不一样的分区中,备份策略就不想之前那么简单了。
先声明一下,ORM的缓存不能解决JOIN或者复杂的SQL查询,其实若是考虑到未来会有分区的可能,就应该在设计表时避免JOIN语句。由于复杂的SQL相对来讲占的少数,甚至能够对这些SQL单独制定缓存策略。
先不考虑分区,假设有一个用户表和博客表,要达到如下目标:
缓存行仍是比较简单的,用户查询某个id时,缓存该行内容,下次就能够直接读取缓存了。
若是内容被更新/删除了,缓存也同时更新/删除。
<?php
//若是在find/findAll里传入了参数,则该参数即为key
ORM::factory('article')->where('user_id', '=', '2')->and_where('created', '>', time() - 86400)->findAll(2);
//上面的代码会在Model内部生成一个结构化的字符串,该字符串及对应的值将被放入缓存中
{table_name}-{key}-{md5(sql)}
//相似这样
article-2-c81e728d9d4c2f636f067f89cc14862c
//若是没有传参数,{key}就不会被替代
article-{key}-c81e728d9d4c2f636f067f89cc14862c
//首次执行此代码时,ORM内部会先去缓存中找上面的结构化字符串,没有找到,就会执行SQL语句,而后把返回的结果的id放到缓存中
//这就是要放到缓存中的数据,下次若是再执行此SQL,直接从缓存中获取id(1,43,50),而后再从缓存中获取这些id对应的行内容
//注意到这里有个revision,这是未来要判断该缓存是否已过时的关键。
'article-2-c81e728d9d4c2f636f067f89cc14862c' => array(
'revision' => 1294476790,
'data' => [1, 43, 50],
);
//同时还会生成另外一组数据,就是revision
'article-2-revision' => 1294476777,
//若是做者又更新了一篇博客,则上面的查询语句结果就发生了变化。
ORM::factory('article')->values(array(...))->save(2);
//ORM会找到缓存中的一组revision数据,同时更新它
'article-2-revision' => 1294476888,
//若是没有提供key,那就是
'article-{key}-revision' => 1294476888,
//下次再执行上面的ORM查询代码时,会先去查找'article-2-revision'的版本,而后跟'article-2-c81e728d9d4c2f636f067f89cc14862c'的版本号比较,若是前一个版本号>后一个版本号,表示数据有改变,缓存已过时,这时就须要从新执行SQL语句,并更新'article-2-c81e728d9d4c2f636f067f89cc14862c'这个字符串的版本号。若是比较结果是前一个版本号<=后一个版本号,那就直接从缓存中读取。
上面说的是数据没有分区的状况,若是数据被分区了的话,还要在ORM内部实现路由功能。
<?php
ORM::factory('articles')->where('created', '>', time()-86400)->findAll();
假设文章经过某种算法,被分在了不一样的分区上,上面这个ORM编译出来的SQL是没法运行的。但又不能让程序员来关心分库分表的事,这时就能够在ORM内部实现路由机制,在具体的Model层实现路由算法。
<?php
class Model_Article extends ORM
{
protected function _route()
{
//这里能够实现具体算法,改变ORM的一些属性,从而影响SQL的编译
}
}
参考:
--EOF--