オプションは次のとおりです。
-
SERIALIZABLE
で実行 隔離。相互依存トランザクションは、シリアル化に失敗したため、コミット時に中止されます。多くのエラーログスパムが発生し、多くの再試行が行われますが、確実に機能します。 -
UNIQUE
を定義する あなたが指摘したように、制約と失敗時に再試行します。上記と同じ問題。 -
親オブジェクトがある場合は、
SELECT ... FOR UPDATE
を実行できます。max
を実行する前の親オブジェクト クエリ。この場合、SELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE
。bar
を使用しています すべてのfoo
のロックとして sそのbar_id
。そうすれば、カウンターインクリメントを実行しているすべてのクエリがこれを確実に実行する限り、続行しても安全であることがわかります。これは非常にうまく機能します。これでも、呼び出しごとに集計クエリが実行されます。これは(次のオプションごとに)不要ですが、少なくとも上記のオプションのようにエラーログをスパムすることはありません。
-
カウンターテーブルを使用します。これが私がすることです。
を使用して行IDを取得しますbar
のいずれか 、またはbar_foo_counter
のようなサイドテーブル 、UPDATE bar_foo_counter SET counter = counter + 1 WHERE bar_id = $1 RETURNING counter
または、フレームワークが
RETURNING
を処理できない場合は、効率の低いオプションです。 :SELECT counter FROM bar_foo_counter WHERE bar_id = $1 FOR UPDATE; UPDATE bar_foo_counter SET counter = $1;
次に、同じトランザクションで 、生成されたカウンター行を
number
に使用します 。コミットすると、そのbar_id
のカウンターテーブルの行 次のクエリで使用できるようにロックが解除されます。ロールバックすると、変更は破棄されます。
bar
に列を追加する代わりに、カウンター専用のサイドテーブルを使用するカウンターアプローチをお勧めします。 。これはモデル化がよりクリーンであり、bar
で作成される更新の膨張が少ないことを意味します 、bar
へのクエリを遅くする可能性があります 。