🐢

Rails + PostgreSQL でトランザクションを書くときに、トランザクションの外で行ったデータ操作の影響

2025/02/11に公開

これは何?

  • Rails + PostgreSQLでトランザクションを書くとき、トランザクションの内外でデータ操作の影響がどう変わるかの備忘録
  • 環境
    • Rails 8
    • PostgreSQL 17

トランザクションの中でのデータ変更について

トランザクションの中で発生したupdate, create, destroyなどのデータ変更は、分離レベルに関係なく、コミットされるまでトランザクションの外には反映されません。

トランザクションの外でのデータ変更について

オプションなしのトランザクションの場合

ActiveRecord::Base.transactionisolationオプション指定なしで実行すると、分離レベルは PostgreSQLではデフォルトのRead Commitedとなります。この状態だと、トランザクションの外でcommitされたデータがトランザクション内に反映されます

たとえば以下のトランザクションでbinding.bした箇所で、別のモデルをcreate,update,destroyしてコミットすると、コミット結果をトランザクション内で見ることができます。

  def test_transaction
    Supplier.transaction do
      binding.b
      update!(name: "updated_name")
    end
  end

PostgreSQLのデフォルトより高レベルの分離オプションを指定したトランザクションの場合

ActiveRecord::Base.transactionを、PostgreSQLのデフォルトより高レベルのisolationオプション指定あり(:repeatable_read or :serializable)で実行すると、トランザクションの外でcommitされたデータはトランザクション内に反映されません。上記のトランザクションを以下のように変えると、別のモデルのデータはトランザクション開始時点のデータを参照します。

  def test_transaction
-    Supplier.transaction do
+    Supplier.transaction(isolation: :repeatable_read) do
      binding.b
      update!(name: "updated_name")
    end
  end

使い分けのユースケース

意図しないロールバックが発生したときに適切にrescueせず、500エラーがユーザーに出てしまう場合は対策したほうがいいかも

参考

https://blog.toshimaru.net/rails-4-transaction-isolation/

Discussion