Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/72.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_Sql Server_Concurrency_Sql Server 2008 R2 - Fatal编程技术网

SQL唯一字段:并发错误?

SQL唯一字段:并发错误?,sql,sql-server,concurrency,sql-server-2008-r2,Sql,Sql Server,Concurrency,Sql Server 2008 R2,我有一个DB表,其中的字段必须是唯一的。假设该表称为“Table1”,唯一字段称为“Field1” 我计划通过执行SELECT来实现这一点,以查看是否存在任何表1记录,其中Field1=@valueForField1,并且仅在不存在此类记录时更新或插入 问题是,我怎么知道这里没有比赛条件?如果两个用户都在写入表1的表单上单击Save(几乎同时),并且他们的Field1值相同,那么不可能发生以下情况吗 User1进行SQL调用,执行select操作并确定Field1=@valueForField1

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

我计划通过执行SELECT来实现这一点,以查看是否存在任何表1记录,其中Field1=@valueForField1,并且仅在不存在此类记录时更新或插入

问题是,我怎么知道这里没有比赛条件?如果两个用户都在写入表1的表单上单击Save(几乎同时),并且他们的Field1值相同,那么不可能发生以下情况吗

User1进行SQL调用,执行select操作并确定Field1=@valueForField1中不存在任何记录。User1的进程被User2的进程抢占,该进程也找不到Field1=@valueForField1的记录,并执行插入。允许User1的进程再次运行,并在Field1=@valueForField1处插入第二条记录,这违反了Field1必须唯一的要求

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


我使用的是MS SQL 2008R2。

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

最重要的是,您可以使字段自动递增,这样您就不必关心填充它,或者您可以添加一个默认值,再次不关心填充它


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

您可以添加唯一约束。示例来自:


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

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

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

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

数据库实际上有几个可以锁定的东西。它可以锁定行、表或中间的某个位置。数据库决定它认为什么是最好的,而且它通常非常擅长

永远不会有任何重试。数据库永远不会为您重试查询。您需要明确地告诉它重试查询。原因是正确的行为很难定义。是否应该使用完全相同的参数重试查询?还是应该修改一些东西?重试查询是否仍然安全?对于数据库来说,更安全的做法是简单地抛出一个异常并让您处理它

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


但是,在事务的一般情况下,会有阻塞,您可以实现重试

SERIALIZABLE
是唯一可以防止争用条件的隔离级别,但它并不像您所描述的那样。如果没有匹配行,则没有要锁定的行。两个事务中的
选择可以同时运行。由于serializable可以防止幻影,因此它需要至少在行所在的范围内保留
S
锁。然后,两者都可以进入插入
,在那里它们将死锁。在所有其他隔离级别
BEGIN TRAN SELECT*FROM T,其中C=2插入到T值(2)COMMIT
可能导致重复。
CREATE TABLE Persons
(
P_Id int NOT NULL UNIQUE
)