【问题标题】:PHP: One giant database class or several smaller classes?PHP:一个巨大的数据库类还是几个较小的类?
【发布时间】:2010-06-23 08:01:23
【问题描述】:

对于我正在编写的当前应用程序,我选择将所有数据库功能放在一个类中,因为它允许我将数据库代码远离业务逻辑,并在需要切换时轻松替换数据库代码到另一个 DBMS。但是,最近我的数据库类变得相当大(信息编辑:大约 53k),由于它的体积,我担心解析这个文件的速度,因为它通常必须针对每个请求进行解析。

通常在任何给定时间只进行一种或两种不同“类型”的数据库调用(例如,用户系统调用、资产系统调用、地图系统调用、会话系统调用等),所以一个选项 I正在考虑将任务分解为一系列数据库对象“切片”,然后根据函数请求在运行时动态加载这些对象。

另一方面,我担心这样做会(a)导致内存中的大量并行执行(即每个切片现在都有一个查询方法、一个独立的查询日志等)以及强迫我修改所有现有代码以指向新的、更小的对象或(b)导致相对性能损失,因为我在此功能中反向破解以使用已编写的代码(例如,让每个切片指向父的查询功能以及突然在整个地方使用 __call 而不是直接方法访问所招致的性能损失)。

在这种情况下,更正确的做法是什么?

编辑了解更多信息:该文件大约 53kb,目前大约有 2,350 行(尚未完成),尽管这可能被认为是有偏差的,因为我使用扩展的 SQL 模型来提高可读性,例如

SELECT
    foo,
    bar,
    baz
FROM
    someTable st
    LEFT JOIN someOtherTable sot
        ON st.id = sot.stId
WHERE
    cond > otherCond

有 70 个查询函数,每个执行一些独特的任务,几乎没有重叠(如果我需要两个惊人相似的结果集,我可以简单地忽略我每次不需要的并重复使用相同的查询)。

编辑:示例函数:

public function alarm_getActiveAlarmsByAsset($tier, $id) {
    if (    !Redacted::checkNumber($tier, $id) 
        ||  $id < 0 
        ||  $tier > Redacted::ASSET_LOWEST_TIER 
        ||  $tier < Redacted::ASSET_TIER_CUSTOMER
    ) {
        return false;
    }

    $sql = "
        SELECT
            alarmId,
            alarmTime,
            server,
            source,
            reason,
            train,
            server_sites.siteId AS `siteId`
        FROM
            alarm_alarms
    ";

    $join = '';

    switch ($tier) {
        case Redacted::ASSET_TIER_CUSTOMER:
            $join = '
                LEFT JOIN red_campus
                    ON red_campus.campId = red_site.campId
            ';
        case Redacted::ASSET_TIER_CAMPUS:
            $join = '
                LEFT JOIN red_site
                    ON red_site.siteId = server_sites.siteId
            ' . $join;
        case Redacted::ASSET_TIER_SITE:
            $join = '
                LEFT JOIN server_sites
                    ON server_sites.servId = alarm_alarms.server
            ' . $join;
    }
    $table = isset(self::$dbTierMap[$tier + 1]) ? self::$dbTierMap[$tier + 1]['table'] : 'server_sites';
    $field = isset(self::$dbTierMap[$tier + 1]) ? self::$dbTierMap[$tier + 1]['parent'] : 'site';
    $sql .= $join . "
        WHERE
                ackId IS NULL
            AND {$table}.{$field}Id = {$id}
    ";

    $r = $this->query($sql);

    if (!$r) {
        return false;
    }

    $alarms = array();
    while ($alarm = mysql_fetch_assoc($r)) {
        $alarms[] = $alarm;
    }
    return $alarms;
}

【问题讨论】:

  • “相当大”到底有多大,难道没有可能通过更好地重用代码在内部优化类吗?
  • ~53kb,~2350 行,虽然我使用扩展 SQL 时可能会认为行数有偏差 - 将更新帖子。
  • 我是否理解正确,您的应用程序对每个唯一查询都有单独的函数?
  • 是的,有一个单独的函数来读取每个查询,将其结果解析为一致且有意义的格式,并将其返回给调用模块;我真的想把 SQL 排除在这个主类之外,所以它完全存在于一个地方。
  • 那么我必须问,您是否考虑过尝试自动构建这些查询?例如,通过使用 Model-View-Controller 架构,特别是结合 ORM 库,如Doctrine

标签: php performance oop optimization


【解决方案1】:

据我了解,您的数据库类基本上包含在整个应用程序中进行的所有可能的查询(硬编码)?

实现数据库层抽象的更好方法是将查询抽象为内部一致的格式,并使用模型和数据库适配器将这些转换为实际的 SQL。例如:

$customer = $Customer->find(array(
    'fields'    => array('name', 'id'),
    'condition' => array('ssn' => $ssn)
));

Customer 类映射到特定的表,如果需要,可以将架构映射到不同的列:

class Customer extends BaseModel {
    public $table = 'xe_a3_cstmr';
    public $schema = array(
        'name' => 'ze_42_nm',
        …
    );
}

BaseModel 将这些抽象查询转换为真正的 SQL,具体取决于它需要与哪个数据库通信:

SELECT `xe_a3_cstmr`.`ze_42_nm`, `xe_a3_cstmr`.`…` FROM `xe_a3_cstmr`
WHERE `xe_a3_cstmr`.`ssn` = 123235;

这样,您的数据库层不会随着您需要在应用中进行的每个新查询而呈指数级增长,同时仍保持将查询映射到不同数据库的灵活性。这被称为ORM。更不用说这实际上提供了更容易的抽象。您无需重写每个硬编码查询,只需编写不同的数据库适配器。

正如 Mewp 在 cmets 中建议的那样,Doctrine 是一个不错的起点。

【讨论】:

  • 更准确地说,它包含可能需要构建的每种类型的查询;有时这是一个简单的近乎静态的查询,有时查询是基于相关信息构建的:例如,在资产的层级意义上,可以存在于 0 到 3 层,相同的函数将用于生成资产查询基于手头表的语义知识的任何级别;我将在我的帖子中编辑一个示例。
  • 我想我现在会使用字节码缓存机制;我对将查询交给查询生成器有点谨慎,因为为了提高效率,我花了很多时间调整查询,甚至是我半生成的查询。我将研究这种系统,并可能希望在未来的某个日期和时间将其包含在内,但由于我现在最大的恐惧是原始性能,我不认为添加新的抽象层是正确的方法解决这个问题。
  • @Dereleased 很公平,一如既往,在性能和灵活性(以及可维护性)之间进行权衡。 ORM 为您提供了令人难以置信的灵活性,但当然可能性能更差。如果这是您主要关心的问题,那么构建自己的查询可能确实是更好的选择。无论如何,您可能都想研究一下,“日常查询”可能不会对性能造成太大影响,您仍然可以为困难的情况保留专门的手工查询。如果担心的话,可以通过适当的缓存来加快查询生成过程本身。
【解决方案2】:

通常维护许多小类更容易。对于 MySQL(主要与 PHP 一起使用),有几个可用的类生成器:

sourceforge: PHP Class Generator

sourceforge: php Class Generator - PCG

也许你可以在那里找到新的想法。

【讨论】:

  • 在我看来,不要使用代码生成器。使用好的库确实比生成代码更好。机器生成的代码,即使是可读的,也会很快变得无法维护。此外,它严重破坏了DRY
【解决方案3】:

如果您担心解析时间而不是代码质量,您可以使用一些编译器缓存,例如APC

但是,如果您有一个大类,您可能应该将其重构为更小的类,以提高可读性。如果您无法在类中轻松找到所需的内容(例如修改它),那么它可能太大而无法维护。
如果您决定重构,请记住可维护性和代码质量比执行速度重要(无论如何,数据库 I/O 比执行代码慢)。

【讨论】:

  • 53k 字节不是行,但我明白你的意思。在这一点上,一切仍然很容易找到和处理;我的 IDE 为我提供了所有函数入口点的快速链接,名称的前缀基于它们所接触的系统等。我对您关于 APC 的“而不是代码质量”cmets 很好奇,因为我们可能会使用字节码缓存;我假设您的意思是使用 APC 作为创可贴而不尝试重构或修复其他任何东西?
  • 是的,你的理解是正确的。我的意思是,如果您对自己的代码感到满意并且只想要速度,APC 可能是解决方案。
【解决方案4】:

如果不知道数据库方案就很难分辨,但维护多个类总是比维护一个大类更容易(例如,每个逻辑实体/数据库表一个类)

您不必担心解析时间,因为对数据库的请求(以及任何网络请求)总是需要更长的时间

【讨论】:

  • 我几乎可以肯定我什么都不担心,但这是我从中等规模应用程序迈向高可用性大型应用程序的第一步。
  • 如果这是您的第一步,请记住:过早优化是万恶之源。
猜你喜欢
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 2012-05-06
  • 2011-04-24
  • 2010-11-18
相关资源
最近更新 更多