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
# 事務回滾,數據庫恢復初始值
StandardError 的異常(如 ActiveRecord::RecordInvalid、RuntimeError、自定義業務異常)→ 觸發回滾,並向外拋出。Exception、SignalException、SystemExit、NoMemoryError)→ 不捕獲,不回滾,直接向上傳播(極少見)。Account.transaction do paul.deposit(100) raise ActiveRecord::Rollback if some_condition # 靜默回滾,不拋出異常 end
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) { ... }(會開啟新數據庫連接,慎用)。Account.transaction do paul.deposit(350) # paul.balance 內存變為 550 peter.withdraw(350) # 異常,事務回滾 end puts paul.balance # => 550 (內存對象未恢復!) puts peter.balance # => -250
ActiveRecord 不追蹤對象屬性變更,事務回滾僅影響數據庫。後續使用這些對象會導致邏輯錯誤。
paul.reload; peter.reload。object_transactions gem(社區方案,自動快照/恢復屬性)。通過 :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 保障,但開發者必須牢記:
save!/create! 觸發異常回滾,避免靜默失敗。reload 或重新查詢。「事務是數據庫的安全帶,不是業務邏輯的萬能膠水。」