SQL Server中的死锁和nolock问题

时间:2017-07-02 21:19:09

标签: sql-server stored-procedures isolation-level database-deadlocks locks

我们有一个插入和更新批量数据的存储过程;我们在选择上使用nolock提示。现在,当负载非常高时,我们面临601错误。我们可以使用行版本控制吗?

如果是,可以使用哪一个,因为我们同时运行多个事务。还有一件事我们必须承担的是由于始终打开而导致的延迟,我们同步多个数据库服务器以处理数据库故障转移。

我们正在使用它,因为它可以防止读取被其他操作陷入僵局。我们的应用程序具有较高的事务处理率,而且我们关注的另一件事是我们总是使用5个服务器的可用性组,因此我们在提交时会延迟,因为它只在提交所有服务器后提交。那么我们应该使用行版本控制吗?如果是这样如何选择哪一个?或者我们可以进行快照隔离吗?除了tempdb内存使用之外还要付出多少代价呢?

(UPDLOCK,ROWLOCK)用于小批量工作,但当批次高达2000时,它就陷入僵局。

这是执行SP的代码段;相同的代码托管在10台服务器上,因此预计会同时运行。 GetRSExecutionLogLatestID是内部调用SP的地方。

ExecutionLogData execLog = this.fileShareDeliveryWrapper.GetRSExecutionLogLatestID(rptPath, notification.Owner, sharePointSubscriptiondata.RASSubscriptionID, this.configRSConnectionString);
                        if (execLog != null)
                        {
                            sharePointSubscriptiondata.RSExecutionLogId = execLog.RSExecutionLogId;
                            sharePointSubscriptiondata.RSSubscriptionId = execLog.RSSubscriptionId;
                        }

这是SP:

    ALTER Procedure [dbo].[RAS_USP_GetLatestExecutionLogId]
@ReportURLPath NVARCHAR(MAX),
@UserID NVARCHAR(200),
@RASSubscriptionID NVARCHAR(100)
AS
BEGIN

SET NOCOUNT ON;
print @userId
DECLARE @SubscriptionId UNIQUEIDENTIFIER,@OwnerUserId UNIQUEIDENTIFIER, @LogEntryId BIGINT

DECLARE @Subscriptions AS TABLE(SubscriptionID uniqueIdentifier NOT NULL, 
                                NotificationID uniqueidentifier NULL, 
                                ReportID uniqueIdentifier NULL, 
                                ExtensionSettings XML NOT NULL
                                )
INSERT INTO @Subscriptions 
SELECT n.SubscriptionID,n.NotificationID,n.ReportID,n.ExtensionSettings 
FROM dbo.Notifications AS n WITH (UPDLOCK, ROWLOCK) INNER JOIN
        Subscriptions AS s WITH (UPDLOCK, ROWLOCK) ON n.SubscriptionID = s.SubscriptionID INNER JOIN
        Catalog AS c WITH (UPDLOCK, ROWLOCK) ON c.ItemID = n.ReportID INNER JOIN        
        Users AS um WITH (UPDLOCK, ROWLOCK) ON um.UserID = s.OwnerID
WHERE c.[Path] = @ReportURLPath --AND um.UserName=@UserID

SELECT @SubscriptionID = SubScriptionID FROM 

(SELECT SubscriptionID,
        NotificationID,
        ReportID,
        pValues.pValue.value('Name[1]', 'VARCHAR(50)') AS ParamName,
        pValues.pValue.value('Value[1]', 'VARCHAR(150)') AS ParamValue
    FROM 
       @Subscriptions CROSS APPLY
       ExtensionSettings.nodes('/ParameterValues/ParameterValue') pValues(pValue)
)  AS Result

where ParamName like '%RASSubscriptionID%' AND ParamValue = CAST(@RASSubscriptionID AS VARCHAR(100))

SELECT @OwnerUserId = UserID FROM Users WHERE UserName = @UserID

SELECT @LogEntryId = LogEntryId FROM (
SELECT top 1 LogEntryId
FROM ExecutionLogStorage a WITH (UPDLOCK, ROWLOCK) 
INNER JOIN [CATALOG] b WITH (UPDLOCK, ROWLOCK) ON a.reportid = b.itemid
INNER JOIN [Notifications] n WITH (UPDLOCK, ROWLOCK) ON n.ReportID = a.ReportID AND n.SubscriptionID = @SubscriptionId
INNER JOIN dbo.Subscriptions s WITH (UPDLOCK, ROWLOCK) ON s.SubscriptionID = n.SubscriptionID 
WHERE [Path] = @ReportURLPath AND n.SubscriptionOwnerID=@OwnerUserId 
ORDER BY TimeEnd desc) ss

SELECT @LogEntryId AS LogEntryId, @SubscriptionId AS SubscriptionID

END

1 个答案:

答案 0 :(得分:0)

SNAPSHOT ISOLATION几乎是在更新时防止阻塞的方法,当您控制查询并且需要语句之间的一致性时。 READ_COMMITTED_SNAPSHOT是另一种可能性,但影响所有查询,而且它不保证"一致性"在给定的交易中(就像READ COMMITTED vs SERIALIZABLE可以给出不同的结果)。 99%的情况下, NOLOCK几乎无从可取。

您可以将SNAPSHOT ISOLATION视为可行的"只读" serializable *。当然有几个成本 - 请注意,即使没有活动的快照交易,您也会支付很多费用,因为当然如果一个变为活动,则需要跟踪数据。

  • 正如您所提到的那样,增加了对tempdb的活动
  • 行将占用更多空间(每行14个字节)
  • 大多数操作的活动多一些,因此根据工作量可能会有性能损失

其他考虑因素:

  • 最好不要更新快照交易中的数据,因为您可能会收到意外结果
  • 某些操作仍然无法完成(例如重建索引 - 重组是可以的,但至少在SQL 2012中,如果查询是在快照隔离中的索引读取而正在其他地方重建时,则读取查询会失败)
  • 无论出于何种原因,如果使用连接池,在重用连接时隔离级别不会重置,这可能会导致意外结果(这适用于所有事务级别,而不仅仅是{{1 }})

本文对情况有一个很好的概述: https://technet.microsoft.com/en-us/library/ms188277(v=sql.105).aspx

但最终,如果你需要避免阻止,你唯一真正的选择是行版本控制和NOLOCK,这不是一个选择。否则,您可能需要以不同的方式设计。其他可能性是使用诸如UPDLOCK之类的东西来避免死锁,一次插入/更新小批量数据以便阻塞周期"很短/不明显等等。

(*)我这样说是因为:

  1. 如果您打开SNAPSHOT交易并且只读取它,您将获得与使用SNAPSHOT ISOLATION时相同的读取结果,除非您不会阻止作者并且你不会导致死锁
  2. 我说"可行"因为大多数时候,引入SERIALIZABLE会导致过多的阻塞和太多的死锁。
  3. 我说"只读"因为虽然可以在快照事务中写入,但这是一项更复杂的工作。如果您只是在快照交易中读取,那么您不太可能遇到数据正确性问题 - 实际上,因为它类似于SERIALIZABLE,您甚至可能提高结果的一致性。您可能遇到的唯一问题与性能有关。总的来说,就这一问题而言,它是一个重大变化,将其引入此类查询。如果你开始写作,但是:

    • 如果您尝试保存更改,并且在另一个事务中更改了相同的数据,则更新将失败,并显示错误3960:
  4.   

    由于更新冲突导致快照隔离事务中止。您   无法使用快照隔离来访问表格' XYZ'直   或间接在数据库' ABC'更新,删除或插入   已被另一个事务修改或删除的行。   重试事务或更改隔离级别   更新/删除声明。

    这是一个你必须处理的新情况,这比阻止更困难(尽管我认为处理死锁的难度相似)。

      

    想象一下,我们有一个包含白色和黑色大理石混合物的袋子。假设我们想要运行两个事务。一   交易将每个白色大理石变成黑色大理石。该   第二次交易将每个黑色大理石变成白色大理石。   如果我们在可序列化隔离下运行这些事务,我们必须运行   他们一次一个。第一笔交易将留下一个袋子   只有一种颜色的大理石。在那之后,第二次交易将会   将所有这些大理石改为另一种颜色。只有两个   可能的结果:只有白色大理石的袋子或只有袋子的袋子   黑色大理石。

         

    如果我们在快照隔离下运行这些事务,则有一个   在序列化隔离下不可能实现的第三个结果。每   交易可以同时拍摄大理石袋的快照   因为它在我们做出任何改变之前存在。现在一个交易发现   白色大理石,将它们变成黑色大理石。在同一个   时间,其他交易发现黑色大理石 - 但只有那些   当我们拍摄快照时,那些黑色的大理石 - 而不是那些大理石   第一个交易变为黑色 - 并将其变为黑色   白色大理石。最后,我们还有一个混合的弹珠袋   一些白色和一些黑色。事实上,我们已经精确地切换了每个   大理石。

    换句话说,如果你坚持只阅读SNAPSHOT交易,它就不那么复杂了,所以在这个意义上,我说你可以认为它是一个可行的"只读&#34 ; SERIALIZABLE - 如果你只是停在那里,它是一个非常有用的工具,几乎任何人都可以使用它来获得一致的非阻塞结果而不使用NOLOCK ,只要他们能负担得起性能打击。当然,它不止于此,但为了更进一步,你需要处理更多的复杂性。