我有一个当前正在运行 700ms 的Eloquent查询,它只会随着我向用户帐户添加更多网站而增加。我试图了解优化它的最佳方法是什么,以便它可以更快地运行。
我真的不想保存"结果"我的计算然后只是稍后在较小的查询中获取它们,因为它们可以随时更新,这意味着它们在100%的时间内都不准确。虽然我很确定会加快查询速度,但我并不想牺牲准确性而不是性能。
这基本上是运行的原始查询:
select *
from
( SELECT `positions`.*,
@rank := IF(@group = keyword_id, @rank+1,
1) as rank_e0686ae02a55b8ad75aec0c7aaec0a21,
@group := keyword_id as group_e0686ae02a55b8ad75aec0c7aaec0a21
from
( SELECT @rank:=0, @group:=0 ) as vars,
positions
order by `keyword_id` asc, `created_at` desc
) as positions
where `rank_e0686ae02a55b8ad75aec0c7aaec0a21` <= '2'
and `positions`.`keyword_id` in ('hundreds of IDs listed here')
使用解决方案mentioned here生成查询,以获得每条记录的N个关系数。
我尝试运行一个更简单的查询,每条记录没有N个关系,实际上它甚至更慢,因为它获取了更多的数据。所以我认为问题是有太多ID
试图在查询的IN
方法中进行匹配。
在我的控制器中我有:
$user = auth()->user();
$websites = $user->websitesAndKeywords();
在我的User
模型中:
public function websitesAndKeywords() {
$user = auth()->user();
$websites = $user->websites()->orderBy('url')->get();
$websites->load('keywords', 'keywords.latestPositions');
return $websites;
}
我很感激任何人都可以帮助我提高速度。
编辑:所以我想我明白了。问题是Laravel每次使用预先加载来加载关系时使用的IN
子句。所以我需要找到一种方法来做JOIN
而不是急切加载。
基本上需要转换它:
$websites->load('keywords', 'keywords.latestPositions');
分为:
$websites->load(['keywords' => function($query)
{
$query->join('positions', 'keywords.id', '=', 'positions.keyword_id');
}]);
这不起作用,所以我不确定在当前集合上执行JOIN
的最佳方法是什么。理想情况下,我也只会获取最新的N个位置而不是所有数据。
以下是positions
表格中的索引:
以下是查询返回的内容:
答案 0 :(得分:0)
找到“每个分组中的前2名”的代码是我见过的最好的代码。它与my blog on such中的内容基本相同。
但是,我们可以能够改进其他两件事。
keyword_id in...
从外部查询移至内部。我假设您的索引以keyword_id
开头?SELECT positions.*, ...
,而只需SELECT id, ...
id
PRIMARY KEY
positions
JOIN
。然后,在外部查询中,positions
返回SHOW CREATE TABLE
以获取其余列。看不到IN
并知道 #dataframe
d<-data.frame(a=LETTERS[1:26],b=rnorm(26,50,10),c=rnorm(26,50,1))
#dotplot
dotplot(a~b,d,col="blue",pch=16,
#simple plot comparing two vectors
panel = function(x,col,y,...){
panel.dotplot(x,col="blue",y,...)
panel.xyplot(x=d$c,col="darkblue",y,...)
mins=NULL
maxs=NULL
#a line showing the difference between "measures"
for(i in 1:nrow(d)){
mins[i]<-min(d$c[i],d$b[i])
maxs[i]<-max(d$c[i],d$b[i])
}
panel.segments(x0=mins,y0=as.numeric(y),
x1=maxs,y1=as.numeric(y),col="red")
},
#the challenge of the conditional Y-axis
yscale.components = function(...){
temp <- yscale.components.default(...)
loc <- temp$left$labels$at
print( temp$left$labels$labels <-
sapply( temp$left$labels$labels,
function(x) if(a> x){
as.expression(bquote( bold(.(x))))
}else{
as.expression(bquote(.(x)))} ))
temp },
#a legend
key = list(columns=2,
points=list(pch=16,col=c("blue","darkblue")),
text=list(c("measure","fitted"))))
列表中表格的百分比,我无法确定这会有多大帮助。答案 1 :(得分:0)
如果您还没有,则需要索引positions(keyword_id)
或positions(keyword_id,created_at)
,具体取决于您是否要继续使用“懒惰评估”,以及取决于您是否要使用触发器解决方案。
正如Rick建议的那样,你必须将keyword_id in...
移到内部查询中,因为mysql无法将其优化到子查询中,因为优化器不理解IF(@group = keyword_id, @rank+1, 1)
将会不需要其他关键字才能正常工作。
这应该在不到700毫秒的时间内为具有数百万行的表(如果您不想在IN
中全部检索它们)提供结果,并且可以通过删除“懒惰评估”来改进Rick建议(因此,对索引中未包含的列执行较少的表查找),具体取决于您的数据。
如果您仍然遇到麻烦,则可以通过使用触发器实际预先计算数据而不会降低准确性。它会为你的插入/更新添加一个(很可能是很小的)开销,所以如果你插入/更新很多并且偶尔只查询一次,你可能不想这样做。
为此,您应该使用索引positions (keyword_id
,created_at
)。
添加另一个表格keywordrank
,其中包含keyword_id, rank, primarykeyofpositionstable
列,主键keyword_id
和rank
。您需要另一个表,因为在触发器中,mysql无法更新您正在更新的表中的其他行。
创建一个触发器,在每个插入positions
- 表格上更新这些等级:
delimiter $$
create trigger tr_positions_after_insert_updateranks after insert on positions
for each row
begin
delete from keywordrank where keyword_id = NEW.keyword_id;
insert into keywordrank (keyword_id, rank, primarykeyofpositionstable)
select NEW.keyword_id, ranks.rank, ranks.position_pk
from
(select NEW.keyword_id,
@rank := @rank+1 as rank,
`positions`.primarykeyofpositionstable as position_pk
from
(SELECT @rank:=0, @group:=0 ) as vars,
positions
where `positions`.keyword_id = NEW.keyword_id
order by `keyword_id` asc, `created_at` desc
) as ranks
where ranks.rank <= 2;
end$$
delimiter ;
如果您希望能够更新或删除条目(或者如果您一次这样做是安全的,那么无论如何这可能都是个好主意),请添加update
/ { {1}} - 触发器,只为delete
和old.keyword_id
执行此操作 - 您可能希望将代码放入过程中以便重复使用它。例如。创建一个过程new.keyword_id
,将整个触发器代码放入其中,但将所有fctname(kwid int)
替换为NEW.keyword_id
,然后只需为kwid
fctname(new.keyword_id)
调用insert
,{{1} } fctname(new.keyword_id)
用于fctname(old.keyword_id)
,update
用于fctname(old.keyword_id)
。
您需要初始化该表一次(如果您决定可能需要更多排名或其他订单),您可以使用任何版本的代码,例如
delete
您可以将触发器和init都放在迁移文件中(不带分隔符)。
然后,您可以使用联接来获取所需的行。
更新不使用触发器的代码,使用delete from keywordrank;
insert into keywordrank (keyword_id, rank, primarykeyofpositionstable)
select ranks.keyword_id, ranks.rank, ranks.position_pk
from
( SELECT `positions`.primarykeyofpositionstable as position_pk,
@rank := IF(@group = keyword_id, @rank+1,
1) as rank,
@group := keyword_id as keyword_id
from
( SELECT @rank:=0, @group:=0 ) as vars,
positions
order by `keyword_id` asc, `created_at` desc
) as ranks
where ranks.rank <= 2;
上的索引。您可以从索引中完全计算内部查询,然后只在tabledata中查找找到的ID。它取决于结果中的行数(与整个表相关),删除惰性求值的效果有多少。
(keyword_id, created_at)
检查select positions.*, poslist.rank, poslist.group
from positions
join
( SELECT `positions`.id,
@rank := IF(@group = keyword_id, @rank+1,
1) as rank,
@group := keyword_id as group
from
( SELECT @rank:=0, @group:=0 ) as vars,
positions
where `positions`.`keyword_id` in ('hundreds of IDs listed here')
order by `keyword_id` asc, `created_at` desc
) as poslist
on positions.id = poslist.id
where poslist.rank <= 2;
是否确实使用了正确的索引explain
。如果这还不够快,您应该尝试触发解决方案。 (或者添加新的(keyword_id, created_at)
- 输出和explain
- 输出,让我们深入了解一下。)