清理OO结构与SQL性能

时间:2009-07-22 13:28:37

标签: php sql design-patterns oop domain-driven-design

在PHP编程时,我总是尝试创建与数据库中的表对应的有意义的“模型”(类)。我经常遇到以下问题:

假设我创建了一个包含两个表的数据库:authorsblogs,它们在我的应用程序中都有相应的模型。

假设我想打印所有博客以及有关作者的信息,我必须做这样的事情:

<?php
foreach ($app->getBlogs() as $blog) {
  echo "<h1>" . $blog->title . "</h1>";
  echo "Written by" . $blog->getAuthor()->name . "</p>";
  // ... et cetera
}
?>

问题是应用程序现在将触发1个SQL查询以获取所有博客项目和[博客项目数]查询以获取每个作者的信息。使用简单的SQL后,我可以使用简单的查询检索此信息:

SELECT * FROM blogs
JOIN authors ON authors.id = blogs.author

处理此类问题的最佳方法是:开发面向对象的应用程序而不执行太多无用的SQL查询。

13 个答案:

答案 0 :(得分:3)

IMO,我认为你应该写另一个封装你所拥有的类。拥有作者的博客是否总是有意义的?每个作者都有博客吗?作者可以有多个博客吗?想想这些问题,然后设计一个封装它的类。请记住,典型的数据库模式不是OO ......它们是关系型的。是的,他们很接近,但有微妙的差异。

因此,如果作者可以拥有多个博客,则可以拥有某种多值类的密钥(使用基于作者ID的密钥),并且可以使用一个SQL调用初始化或加载此类。只是要考虑一些事情。

答案 1 :(得分:3)

除非您知道效率低下的sql操作没有实际影响,例如冗余迭代次数或受影响的行总是很小(例如,对一个系列中的子项数量进行迭代操作, ,如Duggars之类的非常罕见的情况,可以依赖于小于10),我一直赞成关系查询的效率优于OO代码的美。

虽然丑陋的OO代码可能会使维护成为一种痛苦,但低效的数据访问可能会使系统陷入困境,通常是在您度假或试图睡觉时。大多数情况下,您可以找到一个很好的折衷方案,使最有效的SQL操作具有合理的“对象”接口。如果您的对象模型不漂亮,那么在重构或添加功能方面可能会花费您更多的时间,但是每次按下该按钮都会花费您的客户时间(或者更大的硬件用于运行应用程序 - 永远不是一个好的优化方法),使用应用程序花费的工时应该远远超过开发它的工时(人们希望)。

至于你是否需要一个接口的问题(迫使你弄清楚所有可能的消费模式),我已经通过存储过程完成所有数据修改来处理这个问题,但允许数据访问直接针对桌子和桌子通过仅为所有用户提供选择权限的视图。这是一个半争议的立场,因为许多人希望锁定下游消费者的所有数据访问操作,以确保所有正在运行的SQL符合他们的标准。但是新的查看数据的方法总是会出现,如果你必须添加一个新的存储过程,更新你的核心类库并在每次有人想要实现新功能时更新你的客户端代码,部署和资格可以增长到是一个真正的负担 - 远远超过必须处理不符合宗教理想的对象模型。并且实现代码检查过程要容易得多,该过程验证下游消费者编写的新选择语句是犹太教的。

答案 2 :(得分:3)

我是一个巨大的ORM倡导者,这是我的权衡:

可以为大量的开发人员性能交换不易察觉的应用程序性能。如今服务器功能非常强大,额外的铁可以为我们提供一些新的灵活性。

也就是说,如果你做了一些愚蠢的事情,通过让服务器瘫痪来消除用户体验,那就不再适合了。如果你的例子中有一百万作者,将它们全部拉下来以及所有的字段并迭代它们将是不明智的。如果你只有20位作者,那就没什么大不了的了。

对于庞大的数据集和昂贵的批处理操作,即使作为ORM人员,我也必须针对该情况优化和编写特殊的sprocs或SQL语句。我必须小心,不要以这样的方式编写我的代码,以至于我使用缓存模式来更好地使用缓存模式,然后我就可以下载大数据集,然后再进行操作。

这是一场持续不断的辩论,但对我来说,只需了解你无法用一个工具解决所有问题。

答案 3 :(得分:2)

这样的事情正是创建自己的数据层应该为您解决的问题。在您的博客模型中,应该有一个像getBlogList()这样的函数,它将在一个查询中返回博客标题和作者姓名。

答案 4 :(得分:1)

无论您使用什么解决方案,ORM与否,都应该能够在这种情况下发出一个选择,并且它应该只能选择必要的列。然后,从该连接开始,它应该能够使用相应的每个作者的博客列表填充作者对象。必须发出多个SQL是浪费的。

答案 5 :(得分:1)

Propel是一个可以解决这个问题的PHP ORM的例子。我确信Doctrine必须能够这样做,尽管我从来没有看过它。

为什么重新发明轮子?

答案 6 :(得分:1)

我使用了一些其他绑定..示例:

<?php
$blogs = $app->getBlogs();
$blogs->getAuthor();
foreach ($blogs as $blog) {
  echo "<h1>" . $blog->title . "</h1>";
  echo "Written by" . $blog->getAuthor()->name . "</p>";
  // ... et cetera
}
?>

- &GT; $ blog上的getAuthor()调用只查询数据库一次,并使用数组的特殊对象,在每个上调用getAuthor()调用(但是,以某种方式优化为仅作为一个查询运行)。

答案 7 :(得分:1)

你已经回答了这个问题:

  

使用简单的SQL我可以使用简单的查询检索此信息

您可以选择仅提取 博客帖子的SQL和提取博客帖子作者的SQL。同样,您可以选择一些获取博客帖子的PHP代码或获取博客帖子作者的PHP代码。你必须选择你的PHP代码,就像你必须选择你的SQL一样。

上面有很多例子可以说明这在实践中是如何运作的。使用Doctrine或Propel的建议也很好。

答案 8 :(得分:1)

考虑Greg Young和Martin Fowler所描述的命令/查询分离。您的查询模型可以将博客和作者非规范化为单个表格,以便为您的表示层检索DTO。

Greg Young在InfoQ上发表了关于CQS的精彩演讲。

答案 9 :(得分:0)

这是典型的ORM问题。许多思想流派。不确定php的细节,但有几种策略可以解决这种“阻碍不匹配”。 Google orm。

答案 10 :(得分:0)

这样做的一种方法是创建一个包含连接的视图,并将视图结果映射到另一个包含博客和作者数据的类。

答案 11 :(得分:0)

老实说,只需在Blog类上创建一个名为getBlogsWithAuthors()的方法,然后运行

SELECT  *
FROM    blogs
JOIN    authors 
        ON authors.id = blogs.author

我知道为每个模型类写这样的东西似乎很痛苦,但实际上没有别的办法。但是,你可以让它变得更有活力:

//this is a method of a model class. 
//Assume $this->table is the table name of the model (ie, Blog)
public function getWith($joinTable, $pivot1, $pivot2)
{
    $sql="SELECT    *
            FROM    {$this->table}
            JOIN    $joinTable 
                    ON $pivot1 = $pivot2";

    return executeQuery($sql);    
}

$blog=new Blog();
$result=$blog->getWith('authors', 'authors.id', 'blogs.author');
[play with results here]

答案 12 :(得分:0)

您始终可以将memcached用作中间层。每个查询都是纯粹基于RAM的,这意味着您可以根据需要运行任意数量。