轉(zhuǎn)載自 Laravel 論壇:https://learnku.com/laravel/t/46881
隨著應(yīng)用程序的擴展棒搜,使用Laravel Eloquent處理大量數(shù)據(jù)庫記錄可能變得越來越困難指煎。 導(dǎo)致內(nèi)存不足異常并總體上降低應(yīng)用程序速度。 這是為什么白粉?
從數(shù)據(jù)庫中獲取結(jié)果時掩缓,您又將數(shù)據(jù)拉到內(nèi)存中雪情。 以這個代碼片段為例
Post::all()->each(function ($post) {
// ...
});
這將導(dǎo)致以下查詢遵岩,將posts表中的所有記錄加載到內(nèi)存中
select * from posts;
通常對于具有少量記錄的表你辣,這是絕對可以接受的。 但是尘执,隨著成千上萬的帖子的積累舍哄,您最終將開始遇到Web服務(wù)器的內(nèi)存資源限制。
分塊
Laravel中常見的方法是使用Eloquent的 (通過 BuildsQuery) chunk()
一種方法誊锭,該方法獲取固定數(shù)量的記錄表悬,將較大的集合分解為更多的可消耗塊。
Post::chunk(1000, function ($post) {
// ...
});
盡管這看起來不錯丧靡,但有很多改進和要注意的地方蟆沫。
首先,設(shè)想以下情形:您正在從數(shù)據(jù)庫中獲取Post記錄以更新也在where子句中使用的屬性
Post::where('published_at', '<', now())->chunk(1000, function ($post) {
$post->update('published_at', now());
});
盡管是人為的温治,但它例證了一個非撤古樱現(xiàn)實的問題,其中這樣的查詢將導(dǎo)致無限循環(huán)熬荆,因為在下一次執(zhí)行查詢時舟山,published_at
屬性將始終小于now()
(假設(shè)精度為秒 使用MySQL的timestamp
列類型或類似名稱)。
其次,存在查詢性能及其對數(shù)據(jù)庫服務(wù)器的影響的問題累盗。 上面的代碼將導(dǎo)致類似于以下內(nèi)容的查詢
select * from posts order by posts.id asc limit 1000 offset 9000
由于刪除的記錄和附加的查詢約束寒矿,MySQL無法直接轉(zhuǎn)到偏移量,因此若债,此查詢必須有效地選擇前10,000條記錄符相,以僅返回最后選擇的1,000條記錄。 可以想象拆座,這無法很好地擴展到成千上萬行主巍。 這將導(dǎo)致數(shù)據(jù)庫服務(wù)器使用不必要的資源,從而降低了應(yīng)用程序中所有其他查詢的速度挪凑。
大塊...但是更好孕索!
為了防止無法預(yù)料的陷阱并提高數(shù)據(jù)庫服務(wù)器性能,我們可以使用Eloquent chunkById 的方法
Post::where('published_at', '<', now())->chunkById(1000, function ($post) {
$post->update('published_at', now());
});
上面的代碼段將導(dǎo)致類似于以下內(nèi)容的查詢
select * from posts where published_at < '2019-09-11 12:00:00' and id > 9000 order by id asc limit 1000
為什么將此方法視為“更好”躏碳?
a)它允許MySQL完全跳過前9000條記錄(假設(shè)是順序記錄)
b)由于where子句中的id約束搞旭,我們將不再重新選擇已經(jīng)更新的記錄
獎金 - 怎么樣?! ??
深入探討B(tài)uildsQueries特性的chunkyById方法,我們[看到](https://github.com/laravel/framework/blob/6.x/src/Illuminate/Database/Concerns/BuildsQueries.php#L92-L107) 存儲的最后一條記錄的ID(請記住菇绵,我們按ID升序排列)被存儲并在下一個要運行的查詢中用作參數(shù)肄渗。
討論請前往專業(yè)的 Laravel 論壇:https://learnku.com/laravel/t/46881