標籤:

用戶和管理員同時操作同一記錄的不同欄位,如果做並發控制?

業務場景:

(1)用戶提交申請內容,管理員對內容進行審核,在完成審核前,用戶可隨時對申請內容進行編輯;完成審核後,用戶不能再編輯該內容;

(2)管理員審核時,在管理里員提交審核結果前,如果用戶編輯了內容,管理員再提交結果會導致審核失敗,需重新刷新頁面,審核新內容。

業務流程可參見下圖。

在不存在並發時,管理員和用戶都是交替進行操作,資料庫的最終結果符合預期,見圖中的時序1,2

但在並發情況下,見時序3,用戶和管理員同時判斷是否可以寫入結果,得到的都是肯定的判斷,然後都寫資料庫,此時,資料庫雖然寫入成功,但已不具有意義,該記錄的狀態雖然是已審核,但內容已不是管理員查看的內容。此種情況,如果解決。


1、用transaction。

2、每次修改的時候版本++。

3、這樣就變成了一個跟source control一樣的三路合併的問題,消息送到的時候你就知道這個更改是基於哪個版本做出來的,寫在store procedure+transaction裡面不知道多簡單,還不會出事。


很簡單,出現這種問題是因為你沒有版本的概念,屬於需求分析不徹底,業務概念不清晰。

管理員是針對內容的版本進行審核的,而不是針對什麼記錄

理清了業務需求,解決方案是明擺著的……


有一個語法叫select for update


這個…

都不是同一欄位…

有些同志啊,對概念不熟,一聽到有多個用戶,就開始想到什麼並發之類好像很牛逼的東西了。

這個問題和並發都毫無關係。

我也不多批評了。我就直接跳到結論給一個標準解決方案。這本質是一個版本問題,而解決問題連版本記錄其實都不需要。

用戶每次更新的時候記錄一個時間戳。然後管理員開始審核的時候記錄一個時間戳。最後記錄一個管理員審核結論。只要用戶更新時間戳早於管理員開始審核時間戳,且結論通過,才能通過。其他任何情況都不通過,具體分情況的不通過理由留作課後習題。

下課。


關於並發編輯的處理思路及歷史版本概念的介紹

A.並發編輯處理

在設計程序架構時應該考慮這樣的問題,比如當一條會診記錄的狀態是剛提交時,那麼會診申請醫院和會診接收醫院都可以對這條記錄執行操作,會診申請醫院可以取消修改會診信息、會診接收醫院可以拒絕或安排專家。如果兩個用戶在同一時間段操作的話,很可能出現衝突。比如會診申請醫院有可能正在取消頁面中填寫取消會診的原因,而會診接收醫院恰好也進入了會診處理頁面正給同一會診申請記錄安排專家。類似的情況還很多,比如當系統用戶位於修改頁面中正在編輯某條數據時,恰巧有另外的用戶對此條數據執行了刪除動作,這時修改完後的提交動作就會出現異常,如何避免這一類的問題?

有個想法是,可以給資料庫表加個布爾型的鎖欄位,當有用戶可能對此表中的某一記錄執行寫動作時,比如進入修改頁面,標識鎖欄位(加鎖),任何用戶在想對此條記錄執行其它的修改動作前,要先查看鎖欄位是否處於加鎖定狀態。可問題也有,BS的系統中程序如何判斷用戶已經停止可能的更改動作,而恢復此標誌為解鎖狀態呢?比如用戶可能進入修改頁面後,並未執行修改動作,而是直接在瀏覽器中關閉了此頁面,但此時記錄卻還處於被鎖狀態。

此種類似悲觀鎖的處理方式之所以不可行,因為以上設計思路中妄想用資料庫中的表欄位標識用戶進出頁面的動作,可在BS的系統中,程序是無法監聽到客戶端頁面釋放的。再有,此設計中鎖的資源其實質是資料庫中的表記錄而非頁面,而在實現時卻想用其去鎖頁面,也不甚合理。由此,對於鎖,首先應該明確鎖的內容,其次應該明確添加、釋放動作是否可被捕獲。

那換種思路,不修改資料庫表設計,不加額外的鎖欄位。後台程序在執行修改動作時先做判斷,注意此處說的是後台程序而不是用戶,是在後台程序執行針對資料庫的寫操作時而非用戶進入修改頁面時。先根據主鍵對相應記錄執行一次查詢,如果有早前的操作導致本次操作無效,則給出提示,比如該記錄已被刪除、該會診申請已被取消不能安排專家等等。但此種解決方案只能解決記錄被刪除、會診單狀態被修改這種特定性的情況,無法處理該記錄和提交修改前的初始記錄不同的廣泛型問題,因為程序不好在執行寫操作前,逐個判斷整條記錄的每個具體欄位。有可能此條記錄已被刪除、有可能是會診單狀態有更改、也有可能是病情描述有修改。如果申請醫院修改的是病情描述,那接受會診醫院再做寫操作提交,就會把之前的病情描述覆蓋掉。

所以對於並發編輯的問題,目前唯一可行的方法,還是在資料庫中加入一個標識欄位。不過此欄位不能是布爾型,而是數值型或日期時間型。程序在執行修改前先執行一次查詢,檢查此欄位當前的值(時間)和本修改提交前獲取的原始值(時間)是否一致。如果一致,則此欄位的數值發生變化,同時正式執行針對記錄的修改動作;如果不一致,說明此條記錄在當前的修改期間被其他人修改過,此修改動作作廢,給出前端錯誤提示。但這樣的不好之處是,用戶有可能在操作完之後才發現,自己的之前的操作是無效的。比如在修改頁面都已經填寫完要修改的信息了,點擊提交才給提示此條記錄已被刪除。

理想的悲觀鎖解決方案應該是:既然有人正在編輯此條記錄,那在這段時間,其他人則不能進入針對這條記錄寫動作的頁面、不能執行針對這條記錄的寫動作。但前面也說過了,在BS的程序中,前端頁面的釋放動作無法捕獲,註定此方案不好實現。

嚴格來講,所有數據可修改的表中都應該有這樣的鎖欄位來處理並發,可以在程序的業務邏輯層對修改方法進行統一控制,校對鎖欄位。以往的資料庫設計、程序設計中,對並發編輯的處理考慮欠缺,很多處理也不甚得當,要引起注意。

B.歷史版本設計

在資料庫設計過程中,經常會遇到一個需求,就是希望把操作之前的數據保留下來,能夠看到操作之前是什麼數據,操作之後是什麼數據。對於這種需求,我們可以使用保留歷史數據或者使用版本來實現。

版本號是一種常見的版本設計方案,就是在要進行歷史數據保留的表上面增加一個版本號欄位,該欄位可以是datetime類型,也可以是int類型,每進行數據操作時,都是創建一個新的版本,版本是只增不減的,所以只需要拿到最大一個版本號,就能得到最新的業務數據。

版本號和上面針對並發編輯加鎖的解決方案類似,兩者表現在在資料庫中的設計完全一樣,都是加一個標識欄位,可以是數值類型或時間類型。不過程序的處理不一樣:版本號的設計中程序會在同一張表中存下記錄修改的歷史;標識鎖的設計,只是為了給某一記錄加鎖,處理並發編輯問題。

個人不建議將修改的歷史記錄和業務數據混淆在一起存放於業務表中,因為程序讀取數據時要先對版本號欄位進行判斷、程序的修改動作在程序中實質也變成了插入動作,多了很多邏輯處理,況且一起存放容易使業務數據表的數據量因歷史版本記錄而膨脹。

歷史版本記錄可以單獨建歷史表存放,其實就是建立相同Schema的表(當然也可以添加更多的欄位用於記錄額外的歷史版本信息),該表只保留歷史版本的數據。這有點像一個歸檔邏輯,所有歷史版本我們認為都應該是不經常訪問的,可以扔到單獨的表,對於現有生效的版本,仍然保留在原表中,如果需要查詢歷史版本,那麼就從歷史表中查詢。使用歷史表記錄歷史版本,就是在程序對數據進行增刪改操作時,先在歷史表中留痕。

對於會診單這種核心的業務表,保存歷史修改記錄是很有必要的,但並非所有的表都一定要保存歷史記錄,對於其它一些非核心表,可根據實際情況斟酌是否建立對應歷史表。不過合理的設計應該是一套完整的體系,沒有重要不重要之分,只要存在的都是重要的,都要用嚴格統一的設計方案。為此或可以創建額外的歷史版資料庫,裡面單獨保存所有的歷史表。

這裡有必要再次提下前面系統日誌表設計部分的介紹,裡面說可以把每次的寫操作序列化成某種格式存儲在操作日誌表中,如果操作日誌的設計可以記錄的足夠詳細靈活——增加的話增加了什麼、刪除的話刪除了什麼、修改的話修改了哪些屬性、修改前後屬性值各是什麼,具體到某張錶的歷史記錄或可不用再單獨處理。

以往的資料庫設計中,多沒有考慮詳細的歷史記錄功能,後面應該注意,再仔細考慮下操作日誌(OperationLog)表的設計和存儲(優先考慮Hbase或OB),看看可否將其設計的足夠詳細靈活。


版本號或者時間戳審核,不過時間戳也可以看做版本號,反正記住是唯一就可以了,審核的是唯一標識,用戶更新後是另外一個標識,標識不對審核不通過。


為文章添加版本號,用戶更新文章時將版本號 + 1,管理員添加審核信息時判斷版本號是否發生改變就可以了,其實就是 CAS。


簡單啊,時間戳對應樂觀鎖(或者用版本號,是一個道理)。或者悲觀鎖+Transaction。

這個東西當然是資料庫要管的(當然你的業務程序也要做好基本的檢查),建議題主了解下基本的Timestamp, Locking, transaction, ACID這些概念

真要深究還是挺麻煩,推薦幾本書

Database Systems: The Complete Book

Database Systems: A Practical Approach to Design, Implementation, and Management


如果從技術上解決,還不如從設計上解決。你的數據結構應該設計成,用戶有這個欄位,管理員也有同樣的欄位,每次管理員要審查,就拉取用戶欄位到管理員的欄位,同時給用戶置一個管理員操作中的標識。。。

通用來說,有underwriting的情況發生,無論什麼系統,都應該擁有working branch, underwriting branch和status三個概念,通常還需要有歷史記錄,最好還有生效歷史。


從業務上解決,可以設定文檔狀態,draft的時候可以編輯,submit for approve就不能編輯了,或者允許編輯,生個一個working copy,後面需要再submit for approve。

從技術上解決,事務+樂觀悲觀鎖都可以。樂觀鎖的話用版本,不要用時間戳,時間處理是個巨大的坑啊。


搜索到了另外一篇更新同一記錄同一欄位的文章,版本控制方案、select for update方案都有給出,感興趣的可以琢磨比對下業務場景和解決方式的差異。原文鏈接:MySQL中SELECT+UPDATE處理並發更新問題解決方案分享

複製內容保存出錯,原文就不搬運了。


加個時間戳就行了嘛...

提交數據先判斷時間戳

時間戳不對就忽略當前提交...

給用戶,管理員個數據已更改的提示就行了…


這種東西是並發?就是個業務問題。


事務不知道,都敢搞開發了,服。

即使你不知道事務,你只要把檢查和修改寫入一句SQL也是可以的


推薦閱讀:

MySQL 對於千萬級的大表要怎麼優化?
如何解決主從資料庫同步延遲問題?
mysql DBA技術難度低為什麼工資比oracle高?
將開源軟體(比如mysql)的源碼進行修改後必須也開源嗎?
高並發的情況下(100W),數據先存在Redis保證快速響應,然後怎麼往MySql裡面寫?

TAG:資料庫 | MySQL |