首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

ActiveRecord 事務機制詳解:ACID 保證、嵌套行為、異常處理與對象狀態陷阱

     編輯:Java 相關知識

事務基本用法

ActiveRecord::Base.transaction 開啟數據庫事務,塊內所有操作要么全部提交,要么全部回滾。

Account.transaction do
  account1.deposit(100)
  account2.withdraw(100)
end

完整示例:轉賬業務

# 遷移
create_table :accounts, force: true do |t|
  t.string :number
  t.decimal :balance, precision: 10, scale: 2, default: 0
end

# 模型
class Account < ActiveRecord::Base
  validate :balance_must_be_non_negative
  
  def withdraw(amount)
    adjust_balance_and_save(-amount)
  end
  
  def deposit(amount)
    adjust_balance_and_save(amount)
  end
  
  private
  def adjust_balance_and_save(amount)
    self.balance += amount
    save!  # 驗證失敗拋出異常,觸發事務回滾
  end
  
  def balance_must_be_non_negative
    errors.add(:balance, "is negative") if balance < 0
  end
end

# 業務代碼
peter = Account.create!(balance: 100, number: "12345")
paul  = Account.create!(balance: 200, number: "23456")

# 正常轉賬
Account.transaction do
  paul.deposit(10)
  peter.withdraw(10)
end
# 兩賬戶餘額正確變更

# 異常轉賬(餘額不足)
Account.transaction do
  paul.deposit(350)
  peter.withdraw(350)  # 觸發驗證失敗,拋出 ActiveRecord::RecordInvalid
end
# 事務回滾,數據庫恢復初始值

關鍵行為細節

1. 異常類型決定回滾與否

  • 繼承自 StandardError 的異常(如 ActiveRecord::RecordInvalid、RuntimeError、自定義業務異常)→ 觸發回滾,並向外拋出。
  • 非 StandardError 異常(如 Exception、SignalException、SystemExit、NoMemoryError)→ 不捕獲,不回滾,直接向上傳播(極少見)。

2. 手動觸發回滾

Account.transaction do
  paul.deposit(100)
  raise ActiveRecord::Rollback if some_condition  # 靜默回滾,不拋出異常
end

3. 嵌套事務:默認創建保存點

User.transaction do
  User.create!(name: "Outer")
  User.transaction do
    User.create!(name: "Inner")
    raise ActiveRecord::Rollback  # 僅回滾內層
  end
end
# 結果:Outer 被保存,Inner 被回滾
  • 內層 Rollback 僅回滾到保存點,外層繼續提交。
  • 需強制內層異常導致外層回滾:User.transaction(requires_new: true) { ... }(會開啟新數據庫連接,慎用)。

4. 對象狀態不回滾陷阱 ⚠️

Account.transaction do
  paul.deposit(350)   # paul.balance 內存變為 550
  peter.withdraw(350) # 異常,事務回滾
end

puts paul.balance  # => 550 (內存對象未恢復!)
puts peter.balance # => -250

ActiveRecord 不追蹤對象屬性變更,事務回滾僅影響數據庫。後續使用這些對象會導致邏輯錯誤。

解決方案

  1. 事務後重新加載:paul.reload; peter.reload。
  2. 使用 object_transactions gem(社區方案,自動快照/恢復屬性)。
  3. 設計無狀態服務層:事務內僅操作數據庫,不依賴內存對象狀態進行後續業務判斷。

事務隔離級別

通過 :isolation 選項指定(需數據庫支持):

Account.transaction(isolation: :serializable) do
  # 最高隔離,防止幻讀,性能最低
end

# 可用值::read_uncommitted, :read_committed, :repeatable_read, :serializable

跨模型事務

任何 ActiveRecord::Base 子類均可調用 transaction,效果相同:

Order.transaction do
  order.save!
  Inventory.decrement_stock!(order.items)
  Payment.charge!(order.user, order.total)
end

總結

ActiveRecord 事務提供了可靠的 ACID 保障,但開發者必須牢記:

  1. 用 save!/create! 觸發異常回滾,避免靜默失敗。
  2. 嵌套事務默認保存點,內層 Rollback 不影響外層。
  3. 事務回滾不恢復 Ruby 對象屬性,後續務必 reload 或重新查詢。
  4. 長事務殺手:避免在事務中發送郵件、調用外部 API、進行複雜計算,保持事務短小精悍。

「事務是數據庫的安全帶,不是業務邏輯的萬能膠水。」

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved