简体   繁体   English

避免SQL事务中的死锁

[英]Avoiding Deadlocks within SQL transaction

I have the following code (simplified) to access my database. 我有以下代码(简化)来访问我的数据库。 I know about using statements and parameters but I shortend this to focus my problem. 我知道使用语句和参数但我缩短了这一点以集中我的问题。

string ConnectionString = "ConnectionStringHere";

//1. insert Transaction
SqlConnection myConnection = new SqlConnection(ConnectionString);            
SqlTransaction myTrans = myConnection.BeginTransaction();                
string sUpdate = "INSERT INTO User (FirstName, LastName) VALUES ('jon','doe')";
SqlCommand myCommand = new SqlCommand(sUpdate, myConnection, myTrans);
myCommand.ExecuteNonQuery();

//2. select from the same table
SqlConnection myConnection2 = new SqlConnection(ConnectionString);
string sSelect = "SELECT FirstName, LastName FROM User WHERE ID = 123";
DataTable dtResult = new DataTable();
SqlDataAdapter myDataAdapter2 = new SqlDataAdapter(sSelect, myConnection2);
myDataAdapter2.Fill(dtResult); //TimeOut Exception here

//submit changes from my transaction here
myTrans.Commit();

In the second part I get a TimeOutExcetion because I can't access my User table until I commit my transaction myTrans.Commit(); 在第二部分中,我得到一个TimeOutExcetion,因为在我提交我的事务myTrans.Commit();之前我无法访问我的UsermyTrans.Commit(); which is after that - deadlock. 在那之后 - 僵局。

My question here is - what's the best practice to avoid deadlocks here? 我的问题是 - 在这里避免死锁的最佳做法是什么? I could avoid the Exception by making the SELECT part of the transaction or by setting the IsolationLevel 我可以通过制作事务的SELECT部分或设置IsolationLevel来避免异常

SqlTransaction myTrans = myConnection.BeginTransaction(IsolationLevel.ReadUncommitted);

But I'm not sure about the usage of those. 但我不确定那些用法。 I don't have to worry about invalid data because I always select by ID. 我不必担心无效数据,因为我总是按ID选择。

I don't see any reason why you would make the SELECT query as part of the transaction to solve the deadlock or time out issue. 我没有看到任何理由将SELECT查询作为事务的一部分来解决死锁或超时问题。 Setting the ReadUncommitted isolation level on first sql connection myConnection that you have thought is also not the right approach. 在您认为的第一个sql连接myConnection上设置ReadUncommitted隔离级别也不是正确的方法。 I see there are two possible solutions: 我看到有两种可能的解决方案:

  1. First Solution : Setting isolation level IsolationLevel.ReadUncommitted on the transaction myTrans you have started will not help. 第一个解决方案 :在已启动的事务myTrans上设置隔离级别IsolationLevel.ReadUncommitted将无济于事。 If you are comfortable with dirty reads then you should actually be setting this isolation level on the second SQL connection myConnection2 that you are establishing for firing select query on User table. 如果您对脏读取感到满意,那么您实际上应该在要为User表触发select查询而建立的第二个SQL连接myConnection2上设置此隔离级别。 To set the isolation level for the select query through myConnection2 you need to use with (nolock) table level hint. 要通过myConnection2select查询设置隔离级别,您需要使用with (nolock)表级提示。 So your query will start to look like: 所以你的查询将开始如下:

     string sSelect = "SELECT FirstName, LastName FROM User WITH (NOLOCK) WHERE ID = 123"; 

    You can get more details here . 你可以在这里获得更多细节。 Also, read about the consequences of dirty read here which is a side effect of using this particular isolation level. 另外,请阅读此处脏读的后果,这是使用此特定隔离级别的副作用。

  2. Second solution : Default isolation level of SQL Server is Read Committed . 第二种解决方案 :SQL Server的默认隔离级别为Read Committed So when you start firing query through a new SQL connection using a variable named myConnection2 it is working on ReadCommitted isolation level. 因此,当您使用名为myConnection2的变量开始通过新的SQL连接触发查询时,它正在处理ReadCommitted隔离级别。 The default behavior exhibited by ReadCommitted isolation level is Blocking Read ie if there are uncommitted changes on a table (which can be committed or rollbacked due to an active transaction) then your select statement on User table will get blocked. ReadCommitted隔离级别显示的默认行为是Blocking Read即如果表上存在未提交的更改(由于活动事务可以提交或回滚),则User表上的select语句将被阻止。 It will wait for the transaction to finish so that it can read the newly updated data or the original data in case of a rollback. 它将等待事务完成,以便在回滚时可以读取新更新的数据或原始数据。 If it doesn't do such a blocking read then it will end up doing dirty read which is a well known concurrency issue with databases. 如果它不执行这样的阻塞读取,那么它将最终执行dirty read ,这是一个众所周知的数据库并发问题。
    If you do not want your SELECT statements to get blocked and want to keep going with last committed value of a row then there is a new database level setting in SQL Server named READ_COMMITTED_SNAPSHOT . 如果您不希望SELECT语句被阻止并希望继续使用行的最后一个提交值,则SQL Server中有一个名为READ_COMMITTED_SNAPSHOT的新数据库级别设置。 Here is how you can change it using SQL script: 以下是使用SQL脚本更改它的方法:

     ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON 

    Quoting Pinal Dave from his article here : 从他的文章中引用皮纳尔戴夫在这里

If you are having problem with blocking between readers (SELECT) and writers (INSERT/UPDATE/DELETE), then you can enable this property without changing anything from the application. 如果您在读取器(SELECT)和编写器(INSERT / UPDATE / DELETE)之间遇到阻塞问题,则可以在不更改应用程序的任何内容的情况下启用此属性。 Which means application would still run under read committed isolation and will still read only committed data. 这意味着应用程序仍将在读提交隔离下运行,并且仍将只读取已提交的数据。

Note : This is a database level setting and will affect all the transactions on your database using READCOMMITTED isolation level. 注意 :这是数据库级别设置,将使用READCOMMITTED隔离级别影响数据库上的所有事务。

In my opinion you should go with first solution. 在我看来,你应该采取第一个解决方案。 Also there are few key points which you should keep in mind to avoid deadlocks in SQL Server queries. 此外,您应该记住几个关键点,以避免SQL Server查询中的死锁。 Quoting Pinal Dave from here : 这里引用Pinal Dave:

  • Minimize the size of transaction and transaction times. 最小化事务和事务时间的大小。
  • Always access server objects in the same order each time in application. 每次在应用程序中始终以相同的顺序访问服务器对象。
  • Avoid cursors, while loops, or process which requires user input while it is running. 避免使用游标,循环或在运行时需要用户输入的进程。
  • Reduce lock time in application. 减少应用程序中的锁定时间。
  • Use query hints to prevent locking if possible (NoLock, RowLock) 如果可能,使用查询提示来防止锁定(NoLock,RowLock)
  • Select deadlock victim by using SET DEADLOCK_PRIORITY. 使用SET DEADLOCK_PRIORITY选择死锁牺牲品。

It depends on what you need on that select. 这取决于你在那个选择上需要什么。

If you need to make sure the insert has been made prior to the select, you should have both commands executed within the same transaction, or commit the insertion before you run the SELECT query. 如果需要确保在select之前进行了插入,则应该在同一事务中执行这两个命令,或者在运行SELECT查询之前提交插入。

If not, then this being SQL Server, you could either do as you said and set the isolation level to READ UNCOMMITED, or you can use the NOLOCK table hint , ie: 如果没有,那么这就是SQL Server,你可以按照你的说法去做,并将隔离级别设置为READ UNCOMMITED,或者你可以使用NOLOCK表提示 ,即:

string sSelect = "SELECT FirstName, LastName FROM User WITH(NOLOCK) WHERE ID = 123";

But before you decide to do that, please read about its pros and cons in this other question: 但在您决定这样做之前,请阅读其他问题中的优缺点:

When should you use "with (nolock)" 什么时候应该使用“with(nolock)”

I'm not sure why you would get deadlocks as sql server will use rowlocks by default and it's not obvious that you would like to read the same rows you've just modified. 我不确定为什么你会遇到死锁,因为默认情况下sql server会使用rowlocks,而你想要读取你刚修改过的相同行并不明显。

But that said another option to go for would be using an optimistic concurrency level such as read commited snapshot isolation (RCSI) or snapshot isolation (snapshot). 但是,这说明另一个选择是使用乐观并发级别,如读提交快照隔离(RCSI)或快照隔离(快照)。 This uses tempdb to store row versions which readers can access if the data in the original table is currently locked. 这使用tempdb存储行版本,如果原始表中的数据当前被锁定,读者可以访问这些行。 With snapshot you'd have to set the transaction isolation level explicitly in each transaction in order to use it....with RCSI it is a database level setting. 使用快照,您必须在每个事务中显式设置事务隔离级别才能使用它....对于RCSI,它是一个数据库级别设置。 For more information on this topic look at this article . 有关此主题的更多信息,请查看本文

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM