2

我有一个数据库表,其中的字段必须是唯一的。假设该表称为“Table1”,唯一字段称为“Field1”。

我计划通过执行 SELECT 来查看 Field1 = @valueForField1 是否存在任何 Table1 记录,并且仅在不存在此类记录时才更新或插入。

问题是,我怎么知道这里没有竞争条件?如果两个用户都在写入 Table1 的表单上(几乎同时)单击保存,并且他们的 Field1 具有相同的值,是否可能会发生以下情况?

User1 进行 SQL 调用,该调用执行选择操作并确定 Field1 = @valueForField1 没有现有记录。User1 的进程被 User2 的进程抢占,该进程也没有找到 Field1 = @valueForField1 的记录,并执行插入。允许 User1 的进程再次运行,并在 Field1 = @valueForField1 的位置插入第二条记录,违反了 Field1 唯一的要求。

我怎样才能防止这种情况?有人告诉我事务是原子的,但是为什么我们也需要表锁呢?我以前从未使用过锁,我不知道在这种情况下我是否需要一个。如果一个进程试图写入一个锁定的表会发生什么?它会阻止并重试吗?

我正在使用 MS SQL 2008R2。

4

3 回答 3

5

在字段上添加唯一约束。这样你就不必选择了。您只需插入即可。第一个用户将成功,第二个用户将失败。

最重要的是,您可以使该字段自动递增,因此您不必关心填充它,或者您可以添加一个默认值,同样不关心填充它。

一些选项将是自动递增的 INT 字段或唯一标识符。

于 2013-08-12T20:52:53.420 回答
0

您可以添加一个添加唯一约束。来自http://www.w3schools.com/sql/sql_unique.asp的示例:

CREATE TABLE Persons
(
P_Id int NOT NULL UNIQUE
)
于 2013-08-12T20:54:01.230 回答
0

编辑:还请阅读下面马丁史密斯的评论。

jyparask 对如何解决这个特定问题有一个很好的答案。但是,我想详细说明您对锁定、事务、阻塞和重试的困惑。为了简单起见,我将假设事务隔离级别可序列化。

事务是原子的。数据库保证,如果你有两个事务,那么一个事务中的所有操作都会在下一个事务开始之前完全发生,无论存在什么样的竞争条件。即使两个用户同时访问同一行(多核),也不可能出现竞争条件,因为数据库会确保其中一个用户失败。

数据库是如何做到这一点的?带锁。当您选择一行时,SQL Server 将锁定该行,以便所有其他客户端在请求该行时都会阻塞。阻止意味着他们的查询被暂停,直到该行被解锁。

数据库实际上有几件事可以锁定。它可以锁定行、表或介于两者之间的某个位置。数据库决定它认为什么是最好的,而且它通常很擅长。

从来没有任何重试。数据库永远不会为您重试查询。您需要明确告诉它重试查询。原因是正确的行为很难定义。是否应该使用完全相同的参数重试查询?或者应该修改什么?重试查询是否仍然安全?数据库简单地抛出异常并让您处理它更安全。

让我们谈谈你的例子。假设您正确使用事务并执行正确的查询(Martin Smith 链接到一些好的解决方案),那么数据库将创建正确的锁,以便竞争条件消失。一位用户成功,另一位用户失败。在这种情况下,没有阻塞,也没有重试。

然而,在事务的一般情况下,会有阻塞,你可以实现重试。

于 2013-08-13T05:51:18.413 回答