我第一次玩交易我以为我会得到以下代码:
namespace database
{
class Program
{
static string connString = "Server=ServerName;Database=Demo;Trusted_Connection=True;";
SqlConnection connection = new SqlConnection(connString);
static Random r = new Random();
static void Add()
{
try
{
Thread.Sleep(r.Next(0, 10));
using (var trans = new TransactionScope())
{
using (var conn = new SqlConnection(connString))
{
conn.Open();
var count = (int)new SqlCommand("select balance from bank WITH (UPDLOCK) where owner like '%Jan%'", conn).ExecuteScalar();
Thread.Sleep(r.Next(0, 10));
SqlCommand cmd = new SqlCommand("update bank set balance = " + ++count + "where owner like '%Jan%'", conn);
cmd.ExecuteNonQuery();
}
trans.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static void Remove()
{
try
{
Thread.Sleep(r.Next(0, 10));
using (var trans = new TransactionScope())
{
using (var conn = new SqlConnection(connString))
{
conn.Open();
var count = (int)new SqlCommand("select balance from bank WITH (UPDLOCK) where owner like '%Jan%'", conn).ExecuteScalar();
Thread.Sleep(r.Next(0, 10));
SqlCommand cmd = new SqlCommand("update bank set balance = " + --count + "where owner like '%Jan%'", conn);
cmd.ExecuteNonQuery();
}
trans.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Thread t = new Thread(new ThreadStart(Add));
t.Start();
}
for (int i = 0; i < 5; i++)
{
Thread t = new Thread(new ThreadStart(Remove));
t.Start();
}
Console.ReadLine();
}
}
}
我认为在最后100次加法和100次减法后,我的balane将与我的起点相同 - 100,但每次运行脚本时它都会不断变化。即使使用isolationlevel可序列化。谁能告诉我为什么? O_O
编辑:将连接打开和关闭移动到事务范围内。 现在的问题是我得到“事务(进程ID XX)在锁资源上与另一个进程死锁,并被选为死锁牺牲品。重新运行事务”
像Marc Gravell说的那样: 将连接放在事务范围内并将UPDLOCK添加到select查询并结合将isolationlevel更改为repeatableRead就可以了:)
static void Add()
{
try
{
Thread.Sleep(r.Next(0, 10));
using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.RepeatableRead }))
{
using (var conn = new SqlConnection(connString))
{
conn.Open();
var count = (int)new SqlCommand("select balance from bank WITH (UPDLOCK) where owner like '%Jan%'", conn).ExecuteScalar();
Thread.Sleep(r.Next(0, 10));
SqlCommand cmd = new SqlCommand("update bank set balance = " + ++count + "where owner like '%Jan%'", conn);
cmd.ExecuteNonQuery();
}
trans.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
答案 0 :(得分:7)
1:目前TransactionScope
可能是冗余且未使用的;尝试更改事务以包装连接,而不是相反(哦,并使用using
):
using (var trans = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }))
using (var conn = new SqlConnection(connString))
{
conn.Open();
//...
trans.Complete();
}
这样,连接应该在事务中正确登记(并且如果发生错误,可以正确清理)
我认为以上是主要问题;即没有参与交易。这意味着可能会丢失更改,因为读/写操作实际上并未提升到更高的隔离级别。
2:但是,如果你自己这样做,我希望你会看到死锁。要避免死锁,如果您知道要更新,可能需要在(UPDLOCK)
上使用select
- 这将在开始时采用写锁定,因此如果存在竞争线程,则会遇到阻塞而不是死锁。
要明确,此死锁情况是由以下原因引起的:
添加UPDLOCK
,这将成为:
3:但查询做一个微不足道的更新是愚蠢的;更好的方法是在不选择的情况下发布就地更新,即update bank set balance = balance + 1 where ...
答案 1 :(得分:3)
您必须在<{1}}块中打开连接。
而不是
TransactionScope
像这样使用
var conn = new SqlConnection(connString);
conn.Open();
using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }))
{
// do stuff
}
这样打开连接会自动将其作为轻量级事务在using (var trans = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }))
using (var conn = new SqlConnection(connString))
{
conn.Open();
// do stuff
}
中登记。
您始终可以查看examples。