2010年11月30日星期二

Interview 問題 - 如何防止多使用者同時修改同一Record

昨天見了兩份System Analyst的工作,其中一份,又再被問起老生常談的問題。其實這個問題,我已經被人問了幾次,所以我決定寫出來,和大家分享一下。

問題如下:

不論是WinForms還是WebForms,假如有兩個使用者Tom 與 May,同時已經打開Edit Form,在修改同一個紀錄。
Tom正在埋頭苦幹更新紀錄,May卻慢條斯理工作。

Tom首先完成更新工作後,在12:10pm按下[Submit]按鈕,更新工序完成。
但與此同時,May還在更新,最後在12:20pm才完成工作,那當May按下[Submit]按鈕時,便會Overwrite Tom所更改的資料。
如何防止這個情況?


大家花幾分鐘,先想一想....



這個問題有幾個方向,我們要先決定 :
(1.) 是否容許讓多個使用者開啟同一個紀錄,並可以寫入資料庫?
(2.) 較遲的開啟者只能唯讀?
(3.) 完全防止其他人開啟同一個紀錄?
因為如果決定是(2)和(3)作解決方案的話,換句話說,在Tom進入時,我們已經要有一個Flag去讓May知道有其他人在更改資料中,再由Application 層面去決定如何提示和防止載入。

但很明顯沒有太多人會認為(2)和(3)的做法是最好,因為問題來自那一個Flag會產生DeadLock問題,先解釋一下為什麼 :
假設要做到(2)和(3)的話,可能大家會想到以下做法 :
1. 在SQL Table中加入一個Column - 叫[Locked], DataType Bit, Default 0
2. 當Tom 進入時執行: Update MyContent Set Locked=1 Where id=@id
3. 當May 開啟紀錄前,會檢查[Locked]是否等於1,是的話,就不能更改或進入。
4. 當Tom更新完成後,把Locked設回0


但問題就來了,假如Tom的電腦Hang機或Reboot,那一個紀錄就永遠是Locked=1,其他人亦不能修改,需要由Admin把Locked設回0。


那好了,說了那麼多,那應該怎麼做?

不知道大家有沒有接觸一些Version Control 或者Source System , 例如SVN,每次Commit一個檔案,都會有一個Revision流水號。

參考SVN的原理,我會把那一個Flag Column刪除,加入以下Column
ADD VersionNumber INT;
ADD Modifier Int; /*(記錄StaffID)*/
ADD ModifyDate DATETIME;

在軟件層面上加入一個Version Checking方法,
不論你是WinForms或者WebForms使用Timer或者SetTimeOut + Ajax都是一樣。

程序大約如下:
1. Tom和May都可以同時進入Edit Form修改紀錄,兩個人得到的VersionNumber都是1。
2. 當Tom按下Submit時,會Update VersionNumber/Modifier/ModifyDate,假設是VersionNumber=VersionNumber + 1。
3. 當May之後按下Submit時,會進行一個Version Checking,因為Tom已經把VersionNumber轉變為 2,所以May會被提醒Version不同,這時候可以在讓May選擇覆寫? 檢視更新版本? 還是取消修改?
4. 在軟件層面中用Timer加上剛剛說的Version Checking,即使在編輯途中,也可以知道文件已變更。

以上就是我個人推薦的解決方面,沒有複雜的技術,但這可以解決到多人執行的問題,即使是三個,四個,五個人都可以應付到。
因為他們都可以有選擇權去繼續還是取消。

但當然我不得不承認,這還有很多解決方法。

再假設一個比較不真實的例子:
假如在同時同分同秒同微秒去執行讀寫Query的話,那會怎樣呢?

例如:
一部出單系統的過程是: (當然這是簡單虛構的Flow以容易理解)
1. 輸入Product ID查詢是否有貨
2. 有貨的話,提出貨品,並扣減數量
(1)和(2)之間沒有選擇,是連續性進行。

貨倉只剩下最後一部iPhone,有兩個Sales (Tom 和 May),同時想賣iPhone給客人,假設我虛構成是同一刻進行的話,兩者同時輸入ProductID,按下確定,會怎麼樣?
雖然大家都覺得這種在同一微秒的交互執行是幾乎沒有可能,但既然Programming是對錯誤零容忍的話,就深入看一看究竟。

而上述問題,已經變成SQL Server的Transaction Isolation Level 和 Lock的問題,之後我會再寫編文和大家分享。

5 則留言:

  1. 我曾經天真的以為,識得用db的transaction, read committed, uncommited, commit, rollback係好簡單 係programmer 都識的基本 。 後來才發現原來真的會的人少之又少

    回覆刪除
  2. Transaction commit同rollback係一樣基本o野,
    莫講讀CS的人,連IVE都有教 :clown:

    但Set SQL Server transaction isolation level 又係另一樣o野。

    但因為SQL Server default level已經係READ COMMITTED, 而呢個case前後相差10分鐘, 就算set更嚴格的SERIALIZABLE 同REPEATABLE READ都無補於事。

    所以我不認為修改Session level的isolation就可以解決問題。

    所以如果答interviewer用transaction死硬,因為單單用Transaction係解決唔到呢個問題,因為就算拉近到1,2秒之間的事,後者最多只係在Quene狀態。

    問題在於第二個人有可能唔知情情況下over write,要commit定rollback其實已經同呢個case冇關。

    如果第二個人不知情,資料正確,什麼去決定佢不能Commit而應該RollBack?

    回覆刪除
  3. 自從 NoSQL, BigTable, Denormalize 出現之後, db的transaction唔一定係最好的方法.
    想想 SVN 同 Git 的分別可以知了.

    回覆刪除
  4. 有無教係一回事, 但係識唔識"應用" 又係另一回事

    甚至我見過有人以為 atomic action 唔用transacion唔會出事

    ---

    其實無 deadlock 方法真係有好多.

    WinForm 可以用 dual connection, addition lock table, update with commit 加上 read uncommited 去做 locking

    而 WebForm, 可以用 lock table + expiry time 去做 locking (可配合 read uncommitted 去 update lock table)

    而 record version, 好多DB都有 timestamp/row_version 功能


    1分鐘定10分鐘其實無分別

    回覆刪除
  5. 你個問題是關於Concurrent update,同deadlock是兩回事,亂答用transaction的人其實概念未清

    回覆刪除