<p>您不需要<code>IF</code>来检查记录是否已经存在。UPDATE语句中的<code>WHERE</code>子句可以做到这一点。您只需确保在插入新记录之前记录不存在,例如:</p>
<pre><code>UPDATE MyTable
SET
Foo = @foo,
Bar = 1
WHERE BlahID = @id;
INSERT MyTable (Bar,Foo)
values (1,@foo)
where not exists (select BlahID
from MyTable
where BlahID=@id)
</code></pre>
<p>如果可能,请使用命名参数,这样您只需要传递2个参数而不是4个参数,并且有可能混淆顺序。在</p>
<p>您可以在事务中包装这两个语句,但要确保BlahID被索引</strong>。这将允许服务器只锁定一行进行更新。如果没有索引,服务器将不得不扫描并锁定更多的数据以确保一致性。在</p>
<p>这也避免了插入重复的条目。无论使用多少锁,如果使用<code>IF</code>子句,使用相同的不存在的ID进行两次并发尝试都将导致两次插入,因为<em>两个</em>查询都会发现缺少的行,都将尝试无条件地插入。在</p>
<p>另一个选择是使用MERGE,尽管在这种情况下它的性能不好。从<a href="https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-2017_" rel="nofollow noreferrer">MERGE documentation</a></p>
<blockquote>
<p>When simply updating one table based on the rows of another table, improved performance and scalability can be achieved with basic INSERT, UPDATE, and DELETE statements. For example:</p>
</blockquote>
^{pr2}$
<p>目前的情况更简单,只涉及一个表:</p>
<pre><code>INSERT MyTable (Bar,Foo)
VALUES (1,@foo)
WHERE NOT EXISTS (SELECT BlahID FROM MyTable WHERE BlahID=@id);
</code></pre>
<p>为什么会出现僵局?</strong></p>
<p>服务器必须锁定行以确保事务是可重复的。选择时,服务器对检索到的或扫描的行执行共享锁。这就是为什么拥有索引会导致<em>更少的</em>锁-服务器可以立即找到它需要的行。这些共享锁将在事务期间保留。如果没有显式事务,根据隔离模式,共享锁可能会在连接期间保留。可重复阅读就是这样。在</p>
<p>当您尝试更新一行时,服务器将尝试获取更新锁。如果一行具有共享锁,则更新操作将被阻止。如果一个事务已经持有一个行上的共享锁,它将尝试将其升级为升级锁。如果其他人在行上有S锁,则事务将被阻止。为了使读操作可重复,服务器必须锁定它所接触的行。在</p>
<p>如果服务器因为缺少索引而找不到一行,情况会更糟。在</p>
<p>NOLOCK并不意味着不取锁,它意味着别人的锁不受尊重。该操作仍将获取锁,但会导致脏结果、重影或丢失更新。在</p>
<p>在这种情况下,解除锁定的原因如下:</p>
<ol>
<li>两个连接执行<code>IF(SELECT)</code>,并在行S1和S2上获得共享锁。在</li>
<li>连接1尝试将锁升级到升级,但发现它上有S2锁,并阻止它等待释放。在</li>
<li>连接2尝试升级到U,但发现S1和块。没有连接可以继续导致死锁。在</li>
</ol>
<p>您可以在<a href="https://docs.microsoft.com/en-us/sql/2014-toc/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-2014" rel="nofollow noreferrer">SQL Server Transaction Locking and Row Versioning Guide</a>的<a href="https://docs.microsoft.com/en-us/sql/2014-toc/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-2014#Lock_Engine" rel="nofollow noreferrer">Locking in the Database Engine</a>部分找到有关锁定、锁类型、兼容性和范围的更多信息</p>
<p><strong>快照隔离</strong></p>
<p>可以使用<a href="https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/snapshot-isolation-in-sql-server" rel="nofollow noreferrer">snapshot isolation level</a>来避免读写器互相阻塞,类似于Oracle和PostgreSQL的做法。这对<em>这个</em>案例没有帮助,因为您有一个编写器阻塞另一个。在</p>