【问题标题】:First use of array is slow第一次使用数组很慢
【发布时间】:2016-06-03 15:43:23
【问题描述】:

我在 PHP 中遇到了一个奇怪的“错误”,由于我是新手,所以我的知识已经走到了尽头。

我正在开发一个 TYPO3 扩展程序,该扩展程序在数据方面存在一些重大性能问题,至少我是这么认为的。 事实证明,存储我从数据库查询中获得的所有对象的数组的第一次使用需要很长时间。 之后的每次使用或循环都会再次快速。

代码如下所示:

        $productsArr = $this->productRepository->findByDetail($category, $properties);

        $newSortArr = array();
        $familyProductList = array();

        $counter = count($productsArr);
        /** @var Product $product */
        for($i = 0; $i < $counter; $i++) {

            //it takes to long to do this
            $product = $productsArr[$i];

            if(!empty($productsArr[$i])) {
                $newSortArr[$product->getInFamily()->getUid()][] = $product;
            }
        }

我第一次在哪里使用对象数组并不重要。数组的第一次使用总是需要大约 30 秒。

有没有人遇到过类似的情况? 如果您需要更多信息,我很乐意提供。

提前致谢!

【问题讨论】:

  • 可能是查询“重”,第一次使用后仍然在您的数据库服务器的查询缓存中,因此您下次得到更快的响应。
  • 我不熟悉 TYPO3,但翻译成 Symfony/Doctrine 术语,您似乎可以从数据库中获取产品列表,然后访问循环内的某些关系 (getInFamily)。这可能意味着您延迟加载此数据 - 意味着您正在为循环中的每个产品启动另一个数据库查询。计算运行的查询,或跳过$newSortArr 行,看看循环是否运行得更快而不这样做。两者都表明可能是这种情况。通常通过加入关联关系来解决,因此它被添加到结果集中而不是使用延迟加载来获取。
  • @MarcB 但查询执行得非常快。只有第一次使用数组,比如当我将它分配给另一个变量时需要 long
  • @JimL getInFamily 请求并没有破坏它。 if ( $product = $productsArr[$i]; ) 上面的部分需要很长时间。在该行之前,第一次使用 $productsArr 是在 if 中,这也花费了很长时间。

标签: php sql arrays typo3 extbase


【解决方案1】:

您的 $productsArr 不是数组,而是 Exbase 类 QueryResult 的对象,您可以使用 foreach 对其进行迭代或进行索引访问。这个对象只在需要时执行查询并构建它的对象,所以在你做$product = $productsArr[$i];的那一刻,$productsArr的所有Product对象都被构建了。主要的问题是用 PHP 构建对象的性能很差,而且会消耗大量的内存。

因此,为避免性能问题,请考虑使用自定义查询

$this->productRepository->createQuery()->statement('select * from ...')->execute();

准确地得到你想要的,而不是加载大量的对象,然后在 PHP 中对其进行优化。

【讨论】:

  • 这是一个正确的答案,但不推荐您的解决方案。你真的应该避免使用自定义语句,因为它们本质上不太安全,与不同 SQL 数据库的兼容性问题的可能性更大,并且在大多数情况下,你可以使用 extbase 查询函数很好地构建它们:docs.typo3.org/typo3cms/ExtbaseFluidBook/6-Persistence/…
  • @JozefSpisiak 我同意!只要可以使用默认的 extbase 查询函数构建查询,我强烈建议大家使用它们而不是使用自定义查询。但遗憾的是,无法使用 extbase 查询函数连接表。这就是我在这种情况下使用自定义查询的原因。
【解决方案2】:

正如 Jay 已经提到的,您的结果不是一个数组,而是一个 QueryResult。仅供参考,可以通过在查询末尾添加 -&gt;toArray() 将其转换为数组:

$productsArr = $this->productRepository->findByDetail($category, $properties)->toArray();

但这不会改善情况。有两个可能的问题:

迭代所有对象

QueryResult 的优点是它只反映查询的结果,而不是已经解析所有对象。可以传递一个 QueryResult,例如到分页小部件,然后将仅加载请求的结果(例如 1-10、11-20 等)。

由于您正在应用手动排序,所有对象(取决于您的项目,这可能会很多......)都已加载。

显然您想按产品的家庭 UID 对产品进行分类?为什么不使用 ProductRepository 中的 Extbase 功能来做到这一点:

protected $defaultOrderings = array(
    'inFamily.uid' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
);

渴望加载子对象

您的模型 Product 可能与其他模型有关系(例如产品到类别、产品到选项等)。默认情况下,Extbase 在访问对象时会解析所有这些关系。

为了防止这种情况,您可以对关系使用延迟加载。这对于未在所有视图中使用的子对象是有意义的。例如。在您的列表视图中,您只需要产品的标题、图片和价格,而不需要产品的所有选项。

要为这些子对象配置延迟加载,只需要在模型中注解@lazy即可:

/**
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\My\Extension\Domain\Model\ObjectStorageModel>
 * @lazy
 */
protected $categories;

/**
 * @var \My\Extension\Domain\Model\OtherModel
 * @lazy
 */
protected $author;

延迟加载可能有一些缺点,例如在某些情况下,当检查一个对象是OtherModel 的实例时,您会得到一个LazyLoadingProxy 类型的对象。您可以解决大多数这些问题,或者在正常情况下甚至不会偶然发现它们。如果您确实依赖于不是 LazyLoadingProxy 的对象,一个常见的解决方法是这样的检查:

if ($product->getAuthor() instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
    $product->getAuthor()->_loadRealInstance();
}

这确保在任何情况下您都拥有对象的“真实”实例。

当您对任一问题进行更改时,请不要忘记刷新系统缓存。

【讨论】:

    【解决方案3】:

    我假设数组填充在第一行。您是否考虑过使用foreach($productArr as $product) 填充$product 而不是使用for

    【讨论】:

    • 是的,这是我以前做过的事情,但是对于第一个循环,它仍然存在相同的问题。
    • productArr 应该包含什么?是否有 - 例如 - base64 编码图像或其他大列?如果是,那么也许您应该通过从结果中消除这些来进行测试,以便将其从嫌疑人列表中排除。
    • $productArr 包含产品对象。产品对象也有与之关联的子对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-27
    相关资源
    最近更新 更多