オプションは次のとおりです。
-
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へのクエリを遅くする可能性があります 。