我有以下情况:
如果有一个带有InnoDB表的MySQL数据库,我用它来存储唯一的数字。 我启动一个事务,读取值(例如1000471),将该值存储在另一个表中并更新递增的值(100472)。现在我想避免在我的事务运行时有人甚至读取值。
如果我使用普通的MySQL,我会做这样的事情:
Exceute(“LOCK tbl1 READ”);
执行(“SELECT ... from tbl1”);
执行(“INSERT into tbl2”);
执行(“UNLOCK TABLES”);
但由于我使用SubSonic作为DAL并且代码应该独立于mysql,我必须使用TransactionScope。
我的代码:
TransactionOptions TransOpt = new TransactionOptions();
TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
TransOpt.Timeout = new TimeSpan(0, 2, 0);
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, TransOpt))
{
// Select Row from tbl1
// Do something
ts.Complete();
}
根据TransactionOptions的帮助
system.transactions.isolationlevel
我想要达到的效果可以使用IsolationLevel.ReadCommitted实现,但我仍然可以从事务外部读取行(如果我尝试更改它,我会获得锁定,因此事务正在运行)
有人有建议吗?使用TransactionScope
即可实现读锁定答案 0 :(得分:2)
如果有人有兴趣,这就是TransactionOptions影响MySql的方式:
假设我有两种方法。
Method1启动一个事务,从我的表中选择一行,递增该值并更新表。
Method2是相同的,但在select和update之间我添加了1000ms的睡眠。
现在假设我有以下代码:
Private Sub Button1_Click(sender as Object, e as System.EventArgs) Handles Button1.Click
Dim thread1 As New Threading.Thread(AddressOf Method1)
Dim thread2 As New Threading.Thread(AddressOf Method2)
thread2.Start() // I start thread 2 first, because this one sleeps
thread1.Start()
End Sub
如果没有交易,就会发生这种情况:
thread2启动,读取值5,然后睡觉,
thread1启动,读取值5,将值更新为6,
thread2也将值更新为6。
效果:我有两次唯一的号码。
我想要的是什么:
thread2启动,读取值5,然后睡觉,
thread1启动,trys读取值,但获取锁定并休眠,
thread2将值更新为6,
thread1继续,读取值6,将值更新为7
这是如何使用TransactionScope开始交易:
TransactionOptions Opts = new TransactionOptions();
Opts.IsolationLevel = IsolationLevel.ReadUncommitted;
// start Transaction
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, Opts))
{
// Do your work and call complete
ts.Complete();
}
甚至可以管理分布式事务。如果抛出异常,则永远不会调用完成,并且Scope的Dispose()部分回滚事务。
以下是不同IsolationLevels如何影响交易的概述:
答案 1 :(得分:1)
我的第一个猜测是使用SELECT FOR UPDATE
并在快速搜索后在MySQL 5参考中找到了一个关于locking reads的页面。
如果我理解正确,这与所使用的隔离级别无关。请注意 - 隔离级别只是告诉当前事务如何受到其他事务更改的影响。它没有说明其他交易可以做什么。但是,更高的隔离级别需要更多限制锁。
答案 2 :(得分:1)
由于我没有找到一种方法来锁定行以便使用InnoDB和TransactionScope进行阅读(我可能错了)这应该可行:
如果我同时运行两个事务(没有TransactionOptions)并且一个完成,则另一个因为“死锁”异常而无法完成。
根据MySQL documentation,似乎最好的做法是避免出现这种异常,而是期望出现死锁并重启事务。
如果你设置:
TransactionOptions TransOpt = new TransactionOptions();
TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
对于你的事务你没有得到死锁异常,但是,在我的情况下,这会导致一个dublicate唯一的数字,这更糟。
答案 3 :(得分:0)
由于SubSonic似乎不支持SELECT ... FOR UPDATE
,并且在我看来使用隔离级别会被滥用 - 如果有user defined function,它会返回一个新ID?此函数将使用SELECT ... FOR UPDATE
从tbl1读取当前值,并对该行进行udpate并返回该值。
在您插入新值的应用程序代码中,您只需使用:
insert into tbl2 (id, ....) values (next_id(), ...)
答案 4 :(得分:0)
好的,我决定使用“后门”,现在使用内联查询:
// start transaction
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (SharedDbConnectionScope scope = new SharedDbConnectionScope(DB._provider))
{
try
{
Record r = new InlineQuery().ExecuteAsCollection<RecordCollection>(
String.Format("SELECT * FROM {0} WHERE {1} = ?param FOR UPDATE",
Record.Schema.TableName,
Record.Columns.Column1), "VALUE")[0];
// do something
r.Save();
}
}
}
我同时开始了10个线程,它按预期工作。请向rudolfson索取“SELECT FOR UPDATE”提示。