[英]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
您應該簡化您的查詢。 這將減少一些執行時間。 我無法測試您的查詢,但這里有一些提示:
我使用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.