2012-09-27 300 views
0

我目前正在處理交易和變得困惑。這些事務是在數據訪問層創建的,而不是在數據庫的存儲過程中創建的(SQL Server 2008)。 我瞭解爲交易設置的隔離級別的正常工作。 我無法理解在以下情況下會發生什麼情況。交易和鎖

  1. 發起交易
  2. 與ID = 1選擇的僱員。
  3. 更新ID = 1的員工。
  4. 提交

有多個線程在做同樣的事情,但不同的ID。但可能會出現兩個線程查找相同ID的情況。讓我們稱它們爲線程A和B.上述步驟按照以下兩種線程的方式進行。隔離級別設置爲可重複讀取。

A1。開始交易 A2。選擇ID = 1的員工。 B1。開始交易 B2。選擇ID = 1的員工。 A3。更新ID = 1的員工。 A4。提交 B3。更新ID = 1的員工。 B4。提交

我真正想從事務中實現的是,當線程A選擇特定記錄時,線程B甚至不應該能夠選擇該記錄。我不知道我是否通過使用事務和鎖來解決這個問題。

等待答覆:)

+0

一般來說,線程B在線程A之後立即更新記錄是否可以?即線程A所做的更改將會丟失。如果這不適合你,那麼你應該研究樂觀的併發控制。 – Alexander

+0

線程B更新記錄並不正確。我甚至想阻止A在交易中被A選中時被選中。 – Sharkz

回答

1

您應該使用UPDLOCK表提示,以防止死鎖,例如,

select * from employee with (updlock) where id = @id 
update employee set name = @name where id = @id 

如果沒有這個,你可以得到死鎖,因爲默認選擇需要共享讀鎖:

  1. 事務A做select(共享讀鎖)。
  2. 事務B執行select(共享讀鎖,可能在與事務A相同的記錄(例如,如果執行頁面鎖定)的某些 中)。
  3. 事務A現在執行更新,這需要鎖定(鎖定升級)的獨佔寫入 ,但必須等待事務B釋放其共享讀鎖定 。
  4. 事務B現在也想更新它,但必須等待事務A釋放其共享讀鎖。

因此,事務A和B現在正在彼此等待 - 經典鎖升級死鎖。 UPDLOCK表提示避免了這種情況,因爲它強制選擇採取排他鎖:

  1. 事務A執行select(獨佔更新鎖定)。
  2. 事務B想要做它的選擇,但必須等待事務A首先釋放它的鎖。
  3. 事務A現在執行更新並提交,釋放select所採用的更新鎖定。
  4. 交易B現在可以進行選擇。

編輯:您可以將UPDLOCK和ROWLOCK組合起來,以請求行級鎖定,例如「with(updlock,rowlock)」。你可以問,但你可能並不總是得到它 - 見http://msdn.microsoft.com/en-us/library/ms187373(v=sql.100).aspx。同樣,行鎖可能比頁鎖更昂貴,因爲如果使用行鎖,SQL Server可能會有更多的鎖來跟蹤。所以我會讓SQL Server爲自己選擇鎖的範圍,它通常是確定的工作;在這種情況下,它不應該採取表鎖。只有明確地使用一個行鎖,如果你有沒有它的問題。

另請注意,自己的rowlock不會阻止死鎖,其中兩個事務選擇相同的記錄(行),然後嘗試更新它 - 所以你總是需要一個updlock。

+0

updlock是否鎖定已選擇的特定行或將鎖定整個表或頁鎖。 ID是主鍵,因此它總是隻選擇一條記錄。如果它只鎖定那一行,那麼這就是我一直在尋找的解決方案。 – Sharkz

+0

請參閱我的編輯。 – Polyfun

1

你應該看看樂觀鎖定,它的工作原理是你在哪裏檢查記錄並不在讀取和寫入之間改變的更新增加了額外的檢查。您還可以在事務處理範圍之外閱讀您的記錄,從而爲您提供更好的整體表現。

Optimistic_concurrency_control wikipedia

+0

我不確定這是否有助於這種特殊情況。樂觀鎖定將使線程B能夠執行Select ID = 1的僱員,而無需等待來自線程A的事務。最後,線程B也會更新僱員。 – OttO

+0

樂觀併發控制允許多個事務訪問一條記錄,並在未修改的情況下對其進行更改。但在我的情況下,我不想讓多次交易甚至選擇相同的記錄。 – Sharkz

0

我看來,你應該看看線程你使用機制。您應該能夠知道最前面的內容(不是在交易過程中),也不能使用已經處理的ID啓動線程。或者,線程應該有權訪問一些帶有應該處理的ID的共享同步列表。這樣兩個線程不能在相同的ID上工作。

1

嘗試這樣:

using System; 
using System.Transactions; 
using System.Data; 
using Microsoft.Practices.EnterpriseLibrary.Data; 

namespace StackOverflow.Demos 
{ 
    class Program 
    { 

     static Database db = DatabaseFactory.CreateDatabase("demo"); 

     public static void Main(string[] args) 
     { 
      TransactionOptions options = new TransactionOptions(); 
      options.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead; //see http://www.gavindraper.co.uk/2012/02/18/sql-server-isolation-levels-by-example/ for a helpful guide to choose as per your requirements 
      using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options)) 
      { 
       using (IDbConnection connection = db.CreateConnection()) 
       { 
        connection.Open(); //nb: connection must be openned within transactionscope in order to take part in the transaction 
        IDbCommand command = connection.CreateCommand(); 

        command.CommandType = CommandType.Text; 
        command.CommandText = "select top 1 status from someTable with(UPDLOCK, ROWLOCK) where id = 1"; //see http://msdn.microsoft.com/en-us/library/aa213026(v=sql.80).aspx 
        string statusResult = command.ExecuteScalar().ToString(); 

        if (!statusResult.Equals("closed",StringComparison.OrdinalIgnoreCase)) 
        { 
         command.CommandType = CommandType.Text; 
         command.CommandText = "update someTable set status='closed' where id = 1"; 
        } 

        scope.Complete(); 
       } 
      } 
     } 
    } 
} 

PS。一般建議您像上面所做的那樣使用硬編碼SQL的存儲過程 - 如果您可以將所有邏輯推送到存儲過程中,以便您只需調用一次proc,並在數據庫中處理所有邏輯即可。

在上面的例子中,你會發現命名空間:

Microsoft.Practices.EnterpriseLibrary.Data; 

這是那裏,因爲我傾向於從自己的企業庫,它給你的功能,負載上的OOTB庫的頂部使用MS的數據塊。如果你有興趣,你可以在這裏閱讀更多關於:http://msdn.microsoft.com/library/cc467894.aspx

+0

我自己使用所有數據庫相關功能的數據塊。在if條件中我不能直接設置狀態的情況下,我調用另一個提供響應的Web服務,並基於該響應我必須更新狀態。而且由於它的網絡請求可能需要時間,並且在此期間,另一個請求可能會進入我的服務器並選擇相同的記錄並嘗試使用它。我想停止這個其他請求,甚至在相同的記錄上執行選擇。 – Sharkz