[Day 19] 談談 n+1 問題和 eager loading

提到資料之間的關係,我們就不得不提到 n+1 問題,以及怎麼解決。

什麼是 n+1 問題

以昨天的 UsersCities 舉例。

如果我們需要所有 Cities 相關 Usersname,我們可能會這樣寫:

City.all().joinToString{ city -> city.users.joinToString { it.name }}

如果我們這樣寫,那麼這段的 SQL 會是:

SELECT CITIES.ID FROM CITIES

SELECT USERS.ID, USERS."NAME", USERS.CITY FROM USERS WHERE USERS.CITY = 1
SELECT USERS.ID, USERS."NAME", USERS.CITY FROM USERS WHERE USERS.CITY = 2
SELECT USERS.ID, USERS."NAME", USERS.CITY FROM USERS WHERE USERS.CITY = 3

我們可以看到,第一個語法是撈出所有 city 資料的 SQL query。如果 CITY 有幾筆資料,我們就會產生幾筆 SQL query。

如果 city 的個數很少那還沒有關係,如果很多的話,那麼這段程式的效能就會很不好。

這個就是所謂的 n+1 問題。

eager loading

要解決這個問題,我們希望寫出來的程式可以不用這麼多的 SQL query 就找到我們需要的資料。

這裡,我們可以用 eager loading 的方式改寫:

City.all().with(City::users)

這樣改寫之後,這段 SQL 會變成是:

SELECT CITIES.ID FROM CITIES

SELECT USERS.ID, USERS."NAME", USERS.CITY FROM USERS WHERE USERS.CITY = 1
SELECT USERS.ID, USERS."NAME", USERS.CITY FROM USERS WHERE USERS.CITY IN (1, 2, 3)

無論 city 的個數是幾個,SQL query 的次數都會是 2 個。這樣我們就成功囉!