Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/67.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 使用事务避免竞争_Sql_Ado.net_Transactions - Fatal编程技术网

Sql 使用事务避免竞争

Sql 使用事务避免竞争,sql,ado.net,transactions,Sql,Ado.net,Transactions,我正在编写一个守护进程来监视新对象的创建,当它检测到新事物时,会将行添加到数据库表中。我们将调用对象小部件。代码流程大致如下所示: 1: every so often: 2: find newest N widgets (from external source) 3: foreach widget 4: if( widget not yet in database ) 5: add rows for widget 最后两行表示竞态条件,因为如果此守护进程的两个实例

我正在编写一个守护进程来监视新对象的创建,当它检测到新事物时,会将行添加到数据库表中。我们将调用对象小部件。代码流程大致如下所示:

1: every so often:
2:   find newest N widgets (from external source)
3:   foreach widget
4:     if( widget not yet in database )
5:       add rows for widget
最后两行表示竞态条件,因为如果此守护进程的两个实例同时运行,那么如果时间一致,它们可能都会为小部件X创建一行

最明显的解决方案是在widget identifier列上使用
unique
约束,但由于数据库布局的原因,这是不可能的(实际上允许一个widget有多行,但守护进程不应该自动这样做)

我的下一个想法是使用事务,因为这是它们的目的。在ADO.NET世界中,我相信我希望隔离级别为,但我不是积极的。有人能给我指出正确的方向吗

更新:我做了一些实验,序列化事务似乎不能解决问题,或者至少不能很好地解决问题。下面描述了一个有趣的案例,并假设只涉及一个表。请注意,我对锁的细节不是很肯定,但我认为我是对的:

Thread A: Executes line 4, acquiring a read lock on the table
Thread B: Executes line 4, acquiring a read lock on the table
Thread A: Tries to execute line 5, which requires upgrading to a write lock
   (this requires waiting until Thread B unlocks the table)
Thread B: Tries to execute line 5, again requiring a lock upgrade
   (this requires waiting until Thread A unlocks)
这使我们处于典型的死锁状态。其他代码路径也是可能的,但如果线程A和线程B不交错,就不会出现同步问题。最终结果是,在SQL检测到死锁并终止其中一条语句后,在其中一个线程上引发SqlException。我可以捕获这个异常并检测特定的错误代码,但这感觉不是很清楚

我可能采取的另一种方法是创建第二个表来跟踪守护进程看到的小部件,在这里我可以使用
唯一的
约束。这仍然需要捕获和检测某些错误代码(在本例中,是完整性约束冲突),因此,如果有人能想到一个更好的解决方案,我仍然对它感兴趣。

如何检查“if(widget not in database)”。如果这是以“Select”的形式在sql中编写的,则可以使用“Select for update”一次只允许一个守护进程实例执行此操作。通过对最后两行使用事务,并使用“选择更新”来锁定,您将避免竞争


我确信ado.net中有一个等价物。

通常,如果有多个进程或线程同时使用数据库,则应始终使用事务

隔离级别“可序列化”实际上应该可以工作。它不允许从一个事务读取的数据被另一个事务更改。但是它锁很多,一般不应该使用,因为它会减慢应用程序的速度,并且存在更高的死锁风险

备选方案:

  • 您只能锁定整个表,以确保在检查表中是否有内容时没有人写入。问题是,只有一个人可以同时向表中写入数据,这意味着这会大大降低速度。(您可以搜索数据,如果数据不存在,请锁定表并在插入前再次搜索。这是一种常见模式。)
  • 老实说,您应该考虑两个事务试图同时插入同一行的事实。这可能是你应该开始解决它的地方。只有一个守护进程负责相同的数据。
    • 让每个守护进程处理自己的数据
    • 使每个守护进程调用一个服务而不是数据库。在那里,你可以在插入之前把东西放在一起

顺便说一下,您需要一个数据的唯一标识符来清楚地标识它。如果你没有一个唯一的标识符,你如何在数据库中搜索它?

如果你有SQL Server 2008(或者支持它的另一个DBMS),考虑使用一个合并语句。仅当该行不存在时,才可以写入以执行插入

(实际上,它被允许拥有更多 一个小部件有多行,但 守护进程不应该这样做 (自动)

那么问题不在于数据库。事实上,您有多个守护进程不知道您检测到的是同一个对象。在我看来,这就是你需要集中注意力的地方。从数据库的角度来看,它们都提交了完全合法的行

如果您更改,会发生什么情况:

1: every so often:
2:   find newest N widgets (from external source)
3:   foreach widget
4:     if( widget not yet in database )
5:       add rows for widget


“选择更新”在此处不起作用。因为您无法锁定不存在的行。如果他找不到行,他需要确保没有人在他执行此操作的同时插入。谢谢您的建议。不幸的是,我认为这仍然不能解决问题(我将在原始问题中解释)。从一致性的角度来看,它是有效的:-),但如果它导致死锁,当然是不好的。恐怕我帮不上什么忙,但我在我的答案中添加了一些替代方案。再次感谢您对这一点给予了如此多的考虑。我也考虑过服务的想法,我认为这是我的特殊情况下的理想解决方案(因为我可以使用更多的“标准”锁定原语,如互斥锁或监视器)。我同意我所描述的拥有两个守护进程不是一个好主意。@Charlie:这项服务会给你更多的控制权。但锁定应用程序并不总是一种更好的方法。它更容易出错。DB总是关心并发性,wile应用程序锁需要由您实现,在某些情况下,它往往会被忘记。这是真的。然而,我对线程问题和写作服务非常满意,所以我认为这可以不用太多的痛苦就完成。对,我同意你的看法。我并不想说数据库在任何意义上都是罪魁祸首,只是在寻找一种实现既定目标的方法。我确实认为我可以使用数据库来提供帮助,通过创建一个专门供守护进程使用的表,在该表中我可以强制执行唯一性。我仍然不确定如何检测合法的
1: every so often:
2:   while there are unhandled widgets
2:       find first unhandled widget
4:       if( widget not yet in database )
5:           add one row for widget