✨
Ent ORM: `SELECT ... FOR UPDATE SKIP LOCKED` を実装する方法
はじめに
Ent ORM で SELECT ... FOR UPDATE SKIP LOCKED を実装しようとしたところ、デフォルトでは同機能が有効になっていなかった(Ent が生成するクエリーに同機能が含まれていない)ので、やり方を備忘録として残します。
この機能は、並行処理においてデッドロックを回避しながら効率的にレコードを処理するために有用です。
SKIP LOCKED オプション自体の詳細についてはこちら
必要な機能フラグ
SELECT ... FOR UPDATE SKIP LOCKED を実装するために必要な機能フラグについて:
sql/lock 機能(必須)
この機能により、行レベルロックの機能が追加されます。
提供される機能:
-
ForUpdate() / ForShare() メソッド: クエリビルダーに
ForUpdate()とForShare()メソッドを追加 -
ロックオプション:
sql.LockOptionインターフェースを通じたSkipLockedなどのロックオプションのサポート - 適切なSQL生成: 行レベルロック用の適切なSQLを生成
この機能は SELECT ... FOR UPDATE SKIP LOCKED の実装に必須です。
sql/execquery 機能(オプション)
この機能は直接SQL実行機能を提供しますが、現在の実装では必須ではありません:
提供される機能:
-
QueryContext() メソッド: クライアント設定に直接SQLクエリ実行を可能にする
QueryContext()メソッドを追加 - Raw SQL実行: 高度なデータベース操作に必要な生のSQL実行機能
現在の実装ではEnt のクエリビルダーを使用しているため厳密には不要ですが、将来的な拡張性のために含めることができます。
機能の有効化
最小限の構成では sql/lock 機能のみを有効にします:
ent/generate.go
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --target ../entgen --feature sql/lock ./schema/...
将来的な拡張性を考慮する場合は、両方の機能を有効にできます:
ent/generate.go
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --target ../entgen --feature sql/lock --feature sql/execquery ./schema/...
これでコードを再生成すると必要な機能が含まれたコードが生成されます。
実装例
※処理の順序を問わない場合に利用可:
func processBatchJobs(ctx context.Context, client *ent.Client, limit int) error {
// SELECT ... FOR UPDATE SKIP LOCKED を使用してジョブを取得
pendingJobs, err := client.BatchJob.Query().
Where(batchjob.Status("pending")).
Order(
entgen.Desc(batchjob.FieldPriority), // 優先度の高い順
entgen.Asc(batchjob.FieldCreatedAt), // 同優先度では古い順
).
Limit(limit).
ForUpdate( // sql/lock 機能により提供
sql.WithLockAction(sql.SkipLocked), // SkipLocked オプション
).
All(ctx)
if err != nil {
return fmt.Errorf("failed to fetch pending jobs: %w", err)
}
// 取得したジョブを処理(各ジョブは独立)
for _, job := range pendingJobs {
// ...
}
}
return nil
}
Discussion