JKになりたい

何か書きたいことを書きます。主にWeb方面の技術系記事が多いかも。

gormでRelationを組む方法とn+1の回避

昨日から色々あってGo langで開発をはじめました。
超にわかですが、気づいたことやハマったことなどあれば備忘録を残しておこうと思います。

今回は、gormを使ってBelongToの関係にあるFishモデルとWaterAreeモデルのリレーションを組んでみます。

github.com

また、Fishは1匹ではなく、複数匹Selectする必要があります。

(1)Relationメソッドを使う方法

type Fish struct {
    ID             uint       `json:"id" gorm:"column:id"`
    Name           string     `json:"name" gorm:"column:name"`
    WaterAreaID    uint      `json:"-" gorm:"column:waterarea_id"`
    WaterArea      WaterArea `json:"water_area"`
}

type WaterArea struct {
    ID   uint   `json:"id" gorm:"primary_key"`
    Name string `json:"name" gorm:"column:name"`
}
var fishes []models.Fish
db.Find(&fishes)
for i := range fishes {
    db.Model(fishes[i]).Related(&fishes[i].WaterArea, "WaterArea")
}

多分ドキュメント読んでたらこの方法でリレーションを組むのが先に思いつくんじゃないでしょうか?(知らんけど)

2567レコード分のFIshを返すのにかかったレスポンスタイムは582.688351msでした。ふええ・・・遅すぎです・・><
(DBはlocalhostに立ってますので、外部ネットワーク通信によるレイテンシはありません)

これ、典型的なn+1問題です。
Go書いてると、Mapなんかもforでまわして結合したりするので、違和感なく上記のようなコードを書いてしまいがちな気がします?

あと、今回の問題とは関係ないですがRelatedの第二引数に”WaterArea”を渡さないと(invalid association )で怒られました。

ex) (invalid association )で怒られないために

WaterAreaID→WaterAreaIdにしたら大丈夫です。
ただ、Lintの警告が出でて味が悪いです。

type Fish struct {
    ID             uint       `json:"id" gorm:"column:id"`
    Name           string     `json:"name" gorm:"column:name"`
    WaterAreaId    uint      `json:"-" gorm:"column:waterarea_id"`
    WaterArea      WaterArea `json:"water_area"`
}

type WaterArea struct {
    ID   uint   `json:"id" gorm:"primary_key"`
    Name string `json:"name" gorm:"column:name"`
}

参考 gormでbelongs to にハマった話 · polidog lab++

(2)Preloadメソッドを使う方法

モデルの定義は先と同じです。

forで走査するのではなく、Preloadメソッドを使うようにしてみます。

db.Preload("WaterArea").Find(&fishes)

2567レコード分のFIshを返すのにかかったレスポンスタイムは30.843664msでした。 発行されるクエリが2クエリになり、かなりレスポンスが改善されました。

その他詳しい使い方は公式ドキュメントをご参照ください。

CRUD: Reading and Writing Data · GORM Guide