質問: データベースでトランザクションを実行してテーブルにselectを行い、データが存在しないことを確認した上でinsert処理を行う場合に並列でほぼ同時にトランザクションが走ると、お互いにコミットされていないためselectでの存在チェックをすり抜け、insert処理が複数実行されてしまうケースを改善するためにはトランザクションレベルを上げるか、またはテーブルに対してデータが重複しないようにユニークキーを設定するしかないのでしょうか?アプリケーションレベルでの回避は困難なのでしょうか? 追記 RDBMSを使用し(出来ればMysqlかPostgres)、PKをAUTO_INCREMENTに設定している場合です。 ユニークキーを設定した場合や、テーブルロックを行った場合は重複が防げると理解しているのですが、それ以外の方法で回避する方法はないのか知りたいです。 updateの場合はselect for updateを使用すれば該当の行のみをロックすることができますが、insertの場合は対象の行が存在しないため行レベルでのロックが行えないため、ロックするにしてもテーブルロックしか方法がないのかと思っています。 アプリケーションレベルでのトランザクション内で更新とチェックはよくあることだと思うのですが、それに対して他のアプローチや回避策がないかと思い質問させて頂きました。決してRDBMSの機能を一切使わないわけではありません。
回答: 質問文で除外されいてるパターンも含めて整理し直します。 * データベースによる解決 + 「selectでの存在チェック」と同じ内容でユニークキーを設定することで`INSERT`時に自動的に存在チェックを行うことができます。「selectでの存在チェック」が不要になるため効率がいいです。 + 「selectでの存在チェック」が複雑でユニークキーを設定できない場合、`TRIGGER`を設定することで、`INSERT`時に自動的に存在チェックを行うことができます。アプリケーション側からの明示的な「selectでの存在チェック」が不要になるため効率がいいです。 + `MERGE`文)を使用することで存在しない場合にのみ`INSERT`を行うことができます。同時に存在する場合に`UPDATE`や`DELETE`を行うこともできます。 ただし事前にテンポラリテーブルなどに格納する必要があったり、対応しているデータベースエンジンが限られます。もっとも「selectでの存在チェック」の内容を`ON`句に記述するため、そうであればそもそもユニークキーが設定できそうです。 + トランザクション分離レベルを上げることでファントム読み取りを防止します。「selectでの存在チェック」を行うとデータベースによって「`INSERT`予定の行が存在しないこと」も保証され、複数実行は防止されます。 (ただし範囲ロックが使われるため偽の失敗が発生します...だっけ?) + テーブルロックを行うことで、根本的に複数実行を防止します。 * アプリケーションによる解決 + アプリケーションレベルで排他処理を行うことで、根本的に複数実行を防止します。