簡體   English   中英

Doctrine Paginator 選擇整個表格(很慢)?

[英]Doctrine Paginator selects entire table (very slow)?

這與這里的前一個問題有關: Doctrine/Symfony query builder add select on left join

我想使用 Doctrine ORM 執行復雜的連接查詢。 我想選擇 10 篇分頁博客文章,留下加入單個作者,例如當前用戶的價值,以及文章上的主題標簽。 我的查詢構建器如下所示:

$query = $em->createQueryBuilder()
            ->select('p')              
            ->from('Post', 'p')
            ->leftJoin('p.author', 'a')
            ->leftJoin('p.hashtags', 'h')
            ->leftJoin('p.likes', 'l', 'WITH', 'l.post_id = p.id AND l.user_id = 10')
            ->where("p.foo = bar")
            ->addSelect('a AS post_author')
            ->addSelect('l AS post_liked')
            ->addSelect('h AS post_hashtags')
            ->orderBy('p.time', 'DESC')
            ->setFirstResult(0)
            ->setMaxResults(10);

// FAILS - because left joined hashtag collection breaks LIMITS
$result = $query->getQuery()->getResult(); 

// WORKS - but is extremely slow (count($result) shows over 80,000 rows)
$result = new \Doctrine\ORM\Tools\Pagination\Paginator($query, true);

奇怪的是,分頁器上的 count($result) 顯示了我的表中的總行數(超過 80,000),但按預期遍歷 $result 與 foreach 輸出 10 個 Post 實體。 我需要做一些額外的配置來正確限制我的分頁器嗎?

如果這是分頁器類的限制,我還有哪些其他選擇? 編寫自定義分頁器代碼或其他分頁器庫?

(獎金):我怎樣才能水合一個數組,比如 $query->getQuery()->getArrayResult();?

編輯:我在我的函數中遺漏了一個流浪的 orderBy。 看起來同時包含 groupBy 和 orderBy 會導致速度變慢(使用 groupBy 而不是分頁器)。 如果我省略其中一個,則查詢速度很快。 我嘗試在表中的“時間”列上添加索引,但沒有看到任何改進。

我嘗試過的事情

// works, but makes the query about 50x slower
$query->groupBy('p.id');
$result = $query->getQuery()->getArrayResult();

// adding an index on the time column (no improvement)
indexes:
    time_idx:
        columns: [ time ]

// the above two solutions don't work because MySQL ORDER BY
// ignores indexes if GROUP BY is used on a different column
// e.g. "ORDER BY p.time GROUP BY p.id is" slow

您應該簡化您的查詢。 這將減少一些執行時間。 我無法測試您的查詢,但這里有一些提示:

  • 在執行 count() 時不要進行排序
  • 您可以按orderBy('p.id', 'DESC')排序,將使用索引
  • 如果連接表中始終存在至少一條記錄,則可以使用join()而不是leftJoin() 否則該記錄將被跳過。
  • KNP/Paginator 使用 DISTINCT() 僅讀取不同的記錄,但這可能導致使用磁盤 tmp 表
  • $query->getArrayResult() 使用數組隱藏模式,它返回多維數組,對於大結果集,它比對象隱藏更快
  • 你可以使用 partial select('partial p.{id, other used fields}') ,這樣你就可以只加載需要的字段,在使用對象水合時可能會跳過不需要的關系
  • 在學說部分下檢查給定查詢的 SF profiler EXPLAIN,可能未使用索引
  • p.hashtags 和 p.likes 只返回一行還是 oneToMany,乘以結果
  • 也許一些帖子設計更改,這會刪除一些連接:
    • 將 p.hashtags 字段定義為@ORM\\Column(type="array")並存儲標簽的字符串值。 稍后可能會在序列化數組上使用全文搜索。
    • 將 p.likesCount 字段定義為@ORM\\Column(type="integer")這將有喜歡的計數

我使用KnpLabs/KnpPaginatorBundle並且也可能遇到復雜查詢的速度問題。

通常使用 LIMIT x,z 對 DB 來說很慢,因為它在整個數據集上運行 COUNT。 如果不使用索引,它會非常緩慢。

您可以使用不同的方法並通過 ID 推進進行一些自定義分頁,但這會使您的方法復雜化。 我已經將它用於像 SYSLOG 表這樣的大型數據集。 但是你失去了排序和總記錄計數功能。

歸根結底,我的應用程序中使用的許多查詢都過於復雜,無法正確使用分頁器,而且我無法對分頁器使用數組水化模式。

根據MySQL 文檔,如果 GROUP BY 用於不同的列,則索引無法解析 ORDER BY。 因此,我最終使用了幾個后處理查詢來填充具有一對多關系(如主題標簽)的基本結果(ORDERed 和 LIMITed)。

對於從連接表加載單行的連接,我能夠在基本有序查詢中連接所需的值。 例如,加載當前用戶的“點贊狀態”時,只需要加載點贊集合中的一個點贊,即可表明當前帖子是否被點贊。 類似地,給定帖子只有一位作者的存在會產生一個加入的作者行。 例如

$query = $em->createQueryBuilder()
        ->select('p')              
        ->from('Post', 'p')
        ->leftJoin('p.author', 'a')
        ->leftJoin('p.likes', 'l', 'WITH', 'l.post_id = p.id AND l.user_id = 10')
        ->where("p.foo = bar")
        ->addSelect('a AS post_author')
        ->addSelect('l AS post_liked')
        ->orderBy('p.time', 'DESC')
        ->setFirstResult(0)
        ->setMaxResults(10);

// SUCCEEDS - because joins only join a single author and single like
// no collections are joined, so LIMIT applies only the the posts, as intended
$result = $query->getQuery()->getArrayResult(); 

這會產生以下形式的結果:

[
  [0] => [
    ['id'] => 1
    ['text'] => 'foo',
    ['author'] => [
       ['id'] => 10,
       ['username'] => 'username',
    ],
    ['likes'] => [
       [0] => [
         ['post_id'] => 1,
         ['user_id'] => 10,
       ]
    ],
  ], 
  [1] => [...],
  ...
  [9] => [...]
]

然后在第二個查詢中,我加載前一個查詢中加載的帖子的主題標簽。 例如

// we don't care about orders or limits here, we just want all the hashtags
$query = $em->createQueryBuilder()
        ->select('p, h')              
        ->from('Post', 'p')
        ->leftJoin('p.hashtags', 'h')
        ->where("p.id IN :post_ids")
        ->setParameter('post_ids', $pids);

產生以下結果:

[
  [0] => [
    ['id'] => 1
    ['text'] => 'foo',
    ['hashtags'] => [
       [0] => [
         ['id'] => 1,
         ['name'] => '#foo',
       ],
       [2] => [
         ['id'] => 2,
         ['name'] => '#bar',
       ],
       ...
    ],
  ], 
  ...
]

然后我只是遍歷包含主題標簽的結果並將它們附加到原始(有序和有限)結果中。 這種方法最終要快得多(即使它使用更多查詢),因為它避免了 GROUP BY 和 COUNT,充分利用 MySQL 索引,並允許更復雜的查詢,例如我在此處發布的查詢。

您可以通過執行以下一項或多項優化,將paginator器配置為使用更簡單的'count' sql 策略。

$paginator = new Paginator($query, false);
$paginator->setUseOutputWalkers(false);

如果結果出乎意料,您可能需要執行DISTINCT select (select('DISTINCT p'))

對我們來說,它進行了大量改進,我們無需編寫或使用自定義paginator

可以在 此站點上找到更多詳細信息。 請注意,我是該網站的所有者。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM