背景
DBからページネーション用のデータのリストを取ってきたい機会がありました。今までDBを全然扱ってこなかったので、色々調べていると、offset法とシーク法が主な実装方法で、offset法はクソ遅いということがわかりました。そこで、シーク法をGoで実装したときのコードを共有したいと思います。
offset法とシーク法の比較についての資料は、以下の3つの資料がわかりやすかったです。
方法
シーク法は最後に取得したデータをもとに、次のデータからリストを取得する方法です。順番を決めているカラムがユニークな場合(primary keyなど)は、それ自身で比較して次のデータを見つければいいですが、そうでない場合は、ユニークなデータと一緒に最後のデータの次のデータを見つける必要があります。
実装
実装にはgormを使っています。
順番を決めているカラムがユニークな場合(id)
idを比較して、次のデータからデータを取得します。
// orderはasc
// lastData.Idには前回取得時の最後のデータのidが格納されている
db.Limit(pageSize).Order("id asc").Where("id > ?", lastData.Id)
順番を決めているカラムがユニークではない場合(name)
まず、nameで比較します。ただ、nameのみでの比較では次のデータが一意に決められない可能性があるため(同名の場合など)、ユニークなidも合わせて比較します。
// orderはasc
// lastDataには前回取得時の最後のデータのnameとidが格納されている
db.Limit(pageSize).Order("name asc, id asc").
Where("CASE WHEN name > ? THEN 1 WHEN name = ? THEN id > ? ELSE 0 END",
lastData.Name, lastData.Name, lastData.Id)
最後に取得したデータをフロントに返す時などは、並び順を決めるデータ(上の例のnameやid)を格納した構造体をエンコードしてstringに変換して渡せばいいと思います。逆に、フロントから受け取る時はデコードして、構造体(上の例のlastDataのような感じ)に格納すれば、複数の並び順に対応することができます。
感想
offset法が遅い理由は最初のデータから検索するからですが、シーク法だと速い理由は何なんですかね?知っている方いたら教えてほしいです。
where句で検索する際は、すでにDB内部でindex化されているデータを検索するので、速いとか?