我目前正在处理交易并感到困惑。这些事务是在数据访问层创建的,而不是在数据库的存储过程中创建的(SQL Server 2008)。 我理解为事务设置的隔离级别的正常工作。 我无法理解在以下情况中会发生什么。
有多个线程执行相同但不同的ID。但是可能存在两个线程中查找相同ID的情况。让我们称它们为线程A和B.上述步骤以下列方式针对两个线程进行。隔离级别设置为可重复读取。
A1。发起交易 A2。选择ID = 1的员工。 B1。发起交易 B2。选择ID = 1的员工。 A3。更新ID = 1的员工。 A4。承诺 B3。更新ID = 1的员工。 B4。提交
我真正希望从事务中实现的是,当线程A选择特定记录时,线程B甚至不能选择该记录。我不知道在这种情况下使用事务和锁是否正在考虑正确的轨道。
等待回复:)
答案 0 :(得分:1)
你应该看看乐观锁定,它通过在更新中添加额外的检查来工作,在那里检查读取和写入之间的记录是否没有改变。您还可以在事务范围之外读取记录,从而为您提供更好的整体性能。
答案 1 :(得分:1)
您应该使用UPDLOCK表提示来防止死锁,例如,
select * from employee with (updlock) where id = @id
update employee set name = @name where id = @id
如果没有这个,你可能会遇到死锁,因为默认选择采用共享读锁:
所以事务A和B现在正在等待彼此 - 经典的锁升级死锁。 UPDLOCK表提示避免了这种情况,因为它强制select选择进行独占锁定:
编辑:您可以将UPDLOCK与ROWLOCK组合以请求行级别锁定,例如“with(updlock,rowlock)”。你可以问,但你可能并不总是这样 - 请参阅http://msdn.microsoft.com/en-us/library/ms187373(v=sql.100).aspx。行锁也可能比页锁更昂贵,因为如果使用行锁,SQL Server可能会有更多的锁跟踪。所以我会让SQL Server为自己选择锁定的程度,它通常做得很好;在这种情况下,它不应该采取表锁。如果没有它,只有明确使用行锁。
另请注意,行锁本身不会阻止死锁,其中两个事务选择相同的记录(行)然后尝试更新它 - 所以你总是需要一个更新锁。
答案 2 :(得分: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;
那就是因为我倾向于从他们的企业库中使用MS的数据块,它在OOTB库之上提供了大量功能。如果您有兴趣,可以在此处详细了解:http://msdn.microsoft.com/library/cc467894.aspx
答案 3 :(得分:0)
我觉得你应该看看你正在使用的线程机制。您应该能够预先知道(而不是在事务期间)并且不能启动具有已经处理的ID的线程。或者你的线程应该可以访问一些具有应该处理的ID的共享同步列表。这样两个线程就无法处理相同的ID。