使用 Laravel 4.2 通过分页提高多对多查询性能
Improve many-to-many Query Performance with Pagination using Laravel 4.2
我有一个看起来很简单的带分页的多对多关系查询。它工作正常,但缺点是它需要时间。在生产服务器上,超过 20 秒。在我的开发环境中,13 秒。
代码如下:
$query = $this->excerpt->orderBy($sort, $order);
$excerpts = $query->with('source.authors')
->with('excerptType')
->with('tags')
->whereHas('tags', function($q) use ($tagId){
$q->where('tag_id', $tagId);
})
->paginate($this->paginateCount);
这两个查询耗时最长
select count(*) as aggregate
from `excerpt`
where (select count(*)
from `tags`
inner join `excerpt_tag`
on `tags`.`id` = `excerpt_tag`.`tag_id`
where `excerpt_tag`.`excerpt_id` = `excerpt`.`id`
and `tag_id` = '655') >= 1
2.02 秒
select *
from `excerpt`
where (select count(*) from `tags`
inner join `excerpt_tag`
on `tags`.`id` = `excerpt_tag`.`tag_id`
where `excerpt_tag`.`excerpt_id` = `excerpt`.`id`
and `tag_id` = '655') >= 1
order by `created_at` desc limit 15 offset 0
2.02 秒
我正在考虑将其更改为带有内部联接的简单查询,例如:
select *
from `excerpt`
inner join excerpt_tag on excerpt.id = excerpt_tag.excerpt_id
inner join tags on excerpt_tag.tag_id = tags.id
where tags.id = 655
limit 10 offset 0
但后来我失去了预先加载等优势。
有没有人知道加快速度的最佳方法是什么?
改变
( SELECT COUNT(*) ... ) > 0
至
EXISTS ( SELECT 1 ... )
按照 here 中的说明获取 many:many table 中的索引提示。
如果 tag
只是一个短字符串,请不要为它们设置 table (tags
)。相反,只需在 excerpt_tag
中添加 tag
并删除 tag_id
.
没有 ORDER BY
的 LIMIT
有点没有意义——你得到的 10 行将是不可预测的table.
好吧,我有一个解决方案,它带来了显着的改进,只添加了几行代码,并且只添加了 1 或 2 个额外的 sql 查询。
我决定先查询标签,找出连接了哪些摘录,然后使用whereIn
查询摘录中的所有信息,因此希望仍然利用with
功能和急切加载。至少将查询数量保持在绝对最低限度。
这是带有解决方案的代码:
// workaround to make excerpt query faster
$excerptsWithTag = $this->tag->with(['excerpts' => function($query) {
$query->select('excerpt.id');
}])->find($tagId,['tags.id']);
// actual excrpt query
$excerptIds = array_column($excerptsWithTag->excerpts()->get()->toArray(), 'id');
$query = $this->excerpt->orderBy($sort, $order);
$excerpts = $query->with([
'source.authors',
'excerptType',
'tags'
])
->whereIn('excerpt.id', $excerptIds)
->paginate($this->paginateCount);
很可能有更多 eloquent 方法来解决这个问题,但这个有效,我很高兴。
我有一个看起来很简单的带分页的多对多关系查询。它工作正常,但缺点是它需要时间。在生产服务器上,超过 20 秒。在我的开发环境中,13 秒。
代码如下:
$query = $this->excerpt->orderBy($sort, $order);
$excerpts = $query->with('source.authors')
->with('excerptType')
->with('tags')
->whereHas('tags', function($q) use ($tagId){
$q->where('tag_id', $tagId);
})
->paginate($this->paginateCount);
这两个查询耗时最长
select count(*) as aggregate
from `excerpt`
where (select count(*)
from `tags`
inner join `excerpt_tag`
on `tags`.`id` = `excerpt_tag`.`tag_id`
where `excerpt_tag`.`excerpt_id` = `excerpt`.`id`
and `tag_id` = '655') >= 1
2.02 秒
select *
from `excerpt`
where (select count(*) from `tags`
inner join `excerpt_tag`
on `tags`.`id` = `excerpt_tag`.`tag_id`
where `excerpt_tag`.`excerpt_id` = `excerpt`.`id`
and `tag_id` = '655') >= 1
order by `created_at` desc limit 15 offset 0
2.02 秒
我正在考虑将其更改为带有内部联接的简单查询,例如:
select *
from `excerpt`
inner join excerpt_tag on excerpt.id = excerpt_tag.excerpt_id
inner join tags on excerpt_tag.tag_id = tags.id
where tags.id = 655
limit 10 offset 0
但后来我失去了预先加载等优势。
有没有人知道加快速度的最佳方法是什么?
改变
( SELECT COUNT(*) ... ) > 0
至
EXISTS ( SELECT 1 ... )
按照 here 中的说明获取 many:many table 中的索引提示。
如果 tag
只是一个短字符串,请不要为它们设置 table (tags
)。相反,只需在 excerpt_tag
中添加 tag
并删除 tag_id
.
没有 ORDER BY
的 LIMIT
有点没有意义——你得到的 10 行将是不可预测的table.
好吧,我有一个解决方案,它带来了显着的改进,只添加了几行代码,并且只添加了 1 或 2 个额外的 sql 查询。
我决定先查询标签,找出连接了哪些摘录,然后使用whereIn
查询摘录中的所有信息,因此希望仍然利用with
功能和急切加载。至少将查询数量保持在绝对最低限度。
这是带有解决方案的代码:
// workaround to make excerpt query faster
$excerptsWithTag = $this->tag->with(['excerpts' => function($query) {
$query->select('excerpt.id');
}])->find($tagId,['tags.id']);
// actual excrpt query
$excerptIds = array_column($excerptsWithTag->excerpts()->get()->toArray(), 'id');
$query = $this->excerpt->orderBy($sort, $order);
$excerpts = $query->with([
'source.authors',
'excerptType',
'tags'
])
->whereIn('excerpt.id', $excerptIds)
->paginate($this->paginateCount);
很可能有更多 eloquent 方法来解决这个问题,但这个有效,我很高兴。