最近,我們在我們的RoR應用程式中添加了一個功能,允許用戶打開一個特定的記錄,比方說在他們自己的單獨標簽中。在這樣做的時候,我們開始經常看到ActiveRecord::StaleObject錯誤。在調查這個問題時,我發現每當在標簽頁中打開一個資源時,rails確實在嘗試首先更新會話存盤,并引發例外。
我們在活動記錄會話存盤中使用了lock_version,所以Rails默認將其視為樂觀的鎖定。我們是否有辦法在不引入太多復雜性的情況下解決這個問題,因為應用程式已經在客戶的機器上運行,并且不影響我們存盤在會話存盤 DB 中的任何會話資料。
如果有任何建議,我們將非常感激。謝謝
。uj5u.com熱心網友回復:
聽起來你是在資料庫會話記錄上使用樂觀鎖定,并在處理其他記錄的更新時更新會話記錄。不確定你需要在會話中更新什么,但是如果你擔心會話物件的更新可能會發生沖突(并且需要鎖定),那么這些錯誤可能是你所需要的。
如果你不這樣做--你可以在保存會話之前重繪 會話物件(或者禁用它的樂觀鎖定),以避免這些會話更新出現這種錯誤。
你也可以研究一下會話中的哪些內容正在被更新,以及它是否有嚴格的必要性。如果你正在更新像 "last_active_on "這樣的東西,那么你可能最好發送一個后臺作業來完成這個任務,并且/或者使用update_column方法來繞過相當重的activerecord保存回呼鏈。
--- UPDATE ---
模式。將副作用放在后臺作業中
有幾種常見的 Rails 模式會隨著應用程式使用量的增加而開始出現故障。我遇到的最常見的情況之一是,用于特定記錄的控制器端點也會更新公共/共享記錄(例如,如果創建 "訊息 "也會更新使用計數器快取的用戶的 隨著時間的推移,這些模式往往會悄悄進入您的應用程式,并在以后變得難以重構。我建議始終在異步作業中處理請求的副作用(使用類似Sidekiq的東西)。類似于: 雖然這起初可能看起來有些矯枉過正,但它創建了一個可擴展性明顯增強的架構。如果計算訊息的速度變慢......這將使作業變慢,但不會影響產品的可用性。此外,如果某些活動創建了大量具有相同副作用的物件(假設你有一個 "注冊 "控制器,它為一個用戶創建了一堆物件,這些物件都觸發了 模式。跳過 activerecord 回呼鏈 在 ActiveRecord 物件上呼叫 ----更新 #2 ---- 關于死鎖 避免從并發請求中更新(或通常鎖定)一個公共/共享物件的一個原因是,它可以引入死鎖錯誤。
通常來說,資料庫中的 "死鎖 "是指有兩個行程都需要另一個行程擁有的鎖。任何一個執行緒都無法繼續,所以它必須出錯。在實踐中,檢測這種情況是很難的,所以一些資料庫(如postgres)只是在一個執行緒等待排他性/寫入鎖的時間達到一定程度后拋出一個 "死鎖 "錯誤。雖然對鎖的爭奪很常見(例如,兩個更新都在更新一個 "會話 "物件),但真正的死鎖往往是罕見的(執行緒A有一個執行緒B需要的會話鎖,但執行緒B有一個執行緒A需要的不同物件的鎖),所以你可能能夠通過查看/延長死鎖超時來部分地解決這個問題。雖然這可能會減少錯誤,但它并沒有解決執行緒可能等待到死鎖超時的問題。另一種方法是有一個較短的死鎖超時,并救援/重試幾次。
標籤: 上一篇:如何正確處理與jooq的連接?
messages_count,或者更新會話的last_active_at)。這些模式會在您的應用程式中產生瓶頸,因為整個應用程式中的多個不同型別的請求將不必要地爭奪同一資料庫行的寫鎖。
class Message < ActiveRecord::Base
after_commit:enqueue_update_messages_count_job
def enqueue_update_messages_count_job>>
Jobs::UpdateUserMessageCountJob.enqueue(self.id)
結束。
結束。
user.uped_at的更新),那么就很容易拋出重復的作業,并防止同一欄位更新20次。
save 會運行驗證以及所有 before 和 after 回呼。這些可能很慢,而且(有時)是不必要的。例如,更新一個message_count快取值不一定關心用戶的電子郵件地址是否有效(或任何其他驗證),你可能不關心其他回呼的運行。如果你只是更新一個用戶的updated_at值來清除一個快取,也是類似的。你可以通過呼叫user.update_attribute(:message_count, ...)來繞過activerecord回呼鏈,將該欄位直接寫入資料庫中。理論上,對于一個設計良好的應用程式來說,這應該是沒有必要的,但在實踐中,一些較大的/傳統的代碼庫可能會大量使用activerecord回呼鏈來處理你可能不想呼叫的業務邏輯。
