一、N+1問(wèn)題
介紹
在基本級(jí)別,ORM 是 “懶惰” 加載相關(guān)的模型數(shù)據(jù)。但是,ORM 應(yīng)該如何知道你的意圖?在查詢(xún)模型后,您可能永遠(yuǎn)不會(huì)真正使用相關(guān)模型的數(shù)據(jù)。不優(yōu)化查詢(xún)被稱(chēng)為 “N + 1” 問(wèn)題。當(dāng)您使用對(duì)象來(lái)表示查詢(xún)時(shí),您可能在不知情的情況下進(jìn)行查詢(xún)。
想象一下,您收到了100個(gè)來(lái)自數(shù)據(jù)庫(kù)的對(duì)象,并且每條記錄都有1個(gè)關(guān)聯(lián)的模型(即belongsTo)。使用ORM默認(rèn)會(huì)產(chǎn)生101條查詢(xún); 對(duì)原始100條記錄 進(jìn)行一次查詢(xún),如果訪(fǎng)問(wèn)了模型對(duì)象上的相關(guān)數(shù)據(jù),則對(duì)每條記錄進(jìn)行附加查詢(xún)。在偽代碼中,假設(shè)您要列出所有已發(fā)布帖子的發(fā)布作者。從一組帖子(每個(gè)帖子有一位作者),您可以得到一個(gè)作者姓名列表,如下所示:
$posts = Post::published()->get(); // 一次查詢(xún)
$authors = array_map(function($post) {
??? // 生成對(duì)作者模型的查詢(xún)
??? return $post->author->name;
}, $posts);
我們并沒(méi)有告訴模型我們需要所有作者,因此每次從各個(gè)Post?模型實(shí)例中獲取作者姓名時(shí)都會(huì)發(fā)生單獨(dú)的查詢(xún) 。
解決
預(yù)加載功能
使用with()方法指定想要預(yù)加載的關(guān)聯(lián):
$users = User::where(“age”, “>”, 18)
??????? ->with(“hasBalance”)
??????? ->select();
hasBalance?是什么呢?
它是在User模型中定義的一個(gè)方法:
class User extends Model
{
??? //? …
??? // User模型與Balance 模型進(jìn)行一對(duì)一關(guān)聯(lián)
??? public function hasBalance()
??? {
????????? return $this->hasOne(Balance::class, “user_id”, “user_id”);
??? }
}
通過(guò)這個(gè)方法讓User?模型與Balance?模型進(jìn)行一對(duì)一關(guān)聯(lián)。
延伸閱讀:
二、N+1 問(wèn)題由來(lái)
假設(shè)需要現(xiàn)在需要查找模型 A 的 n 條數(shù)據(jù),而模型 A 又關(guān)聯(lián)了另外多個(gè)的模型,這里假設(shè) A 關(guān)聯(lián)了模型 B 和 C. 現(xiàn)在需要查找 n 條 A 的記錄(這里需要 1 條 SQL 語(yǔ)句,例如 select * from A ),同時(shí)把這 n 條記錄關(guān)聯(lián)的 B, C 記錄也查出來(lái)(這里需要 n 條查詢(xún) B 的語(yǔ)句和 n 條查詢(xún) C 的語(yǔ)句),于是最后需要的 SQL 語(yǔ)句數(shù)目為 2n + 1。
將 2n 抽象出來(lái),假設(shè)需要查找的是 n 條 A 的記錄(count(SQL) = 1),同時(shí)需要把與 A 關(guān)聯(lián)的 k 個(gè)模型的記錄也查找處理出來(lái)( count(SQL) = n * k ), 設(shè) n * k = N, 最后查詢(xún)的 SQL 語(yǔ)句數(shù)目,即 N + 1。