SQL Server中的并发执行

Est*_*sty 10 sql sql-server rowlocking

表模式(SQL Server 2012)

Create Table InterestBuffer
(
    AccountNo CHAR(17) PRIMARY KEY,
    CalculatedInterest MONEY,
    ProvisionedInterest MONEY,
    AccomodatedInterest MONEY,
)

Create Table #tempInterestCalc
(
    AccountNo CHAR(17) PRIMARY KEY,
    CalculatedInterest MONEY
)
Run Code Online (Sandbox Code Playgroud)

我正在做一个upsert.更新存在的行并插入其他行.

UPDATE A
SET A.CalculatedInterest = A.CalculatedInterest + B.CalculatedInterest
FROM InterestBuffer A
INNER JOIN #tempInterestCalc B ON A.AccountNo = B.AccountNo

INSERT INTO InterestBuffer
SELECT A.AccountNo, A.CalculatedInterest, 0, 0
FROM #tempInterestCalc A
LEFT JOIN InterestBuffer B ON A.AccountNo = B.AccountNo
WHERE B.AccountNo IS NULL
Run Code Online (Sandbox Code Playgroud)

一切都很好.并发执行期间出现问题.我#tempInterestCalc通过加入其他各种表来插入数据,包括与表的左连接,InterestBuffer#tempInterestCalc为每个并发执行插入不同的数据集.

我的问题是,有时执行会被另一个执行锁定,直到我将它们串行提交.

我的问题是,因为我提供的是不同的数据集,所以它不会对行锁定产生任何其他并发操作的影响.任何建议将不胜感激.

更新1:我用于SP_LOCKInterestBuffer表.它说IndId = 1, Type = KEY, Mode = X, Status = GRANT.

我认为更新和插入阻止其他事务进行幻像读取.

更新2:对不起!以前我告诉过更新很好.但现在我意识到第一个事务写入阻止了第二个事务写入.在第一个事务中,我运行更新并插入.在第二个事务中,在我在#tempInterestCalc表中插入数据后,我只需执行以下操作即可正常工作.

--INSERT DATA INTO #tempInterestCalc 

SELECT * FROM #tempInterestCalc 
RETURN

--UPDATE InterestBuffer

--INSERT InterestBuffer
Run Code Online (Sandbox Code Playgroud)

更新3:我认为我的问题是在更新期间从InterestBuffer读取数据并插入到InterestBuffer中.

更新4:如果我REBUILD INDEX在InterestBuffer表中使用BranchCode,我的回答有时会有效.是否有任何原因导致批量插入/更新使索引出现问题???

更新5:我已经读过如果需要锁定页面的最大行以进行批量更新,那么SQL服务器可能会锁定该页面.是否有任何方法可以查看哪个行包含哪个页面或哪个页面将在执行期间锁定和释放?

更新6:我正在提供我的方案.

CREATE TABLE [dbo].[Account](
        [AccountNo] [char](17) NOT NULL,
        [BranchCode] [char](4) NOT NULL,
     CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED 
    (
        [AccountNo] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]

CREATE TABLE [dbo].[InterestBuffer](
    [AccountNo] [char](17) NOT NULL,
    [BranchCode] [char](4) NOT NULL,
    [CalculatedInterest] [money] NOT NULL,
 CONSTRAINT [PK_Buffer] PRIMARY KEY CLUSTERED 
(
    [AccountNo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

查询分支0001:

BEGIN TRAN

Declare @BranchCode AS Char(4) = '0001'
Declare @CalculatedInterestNew MONEY = 10

CREATE TABLE #tempInterestCalc
(
    AccountNo Char(17),
    BranchCode Char(4),
    CalculatedInterestNew MONEY,
    CalculatedInterestOld MONEY
)

INSERT INTO #tempInterestCalc
SELECT A.AccountNo, A.BranchCode, ISNULL(B.CalculatedInterest, 0), B.CalculatedInterest
FROM Account A 
LEFT JOIN InterestBuffer B ON A.AccountNo = B.AccountNo AND A.BranchCode = B.BranchCode
WHERE A.BranchCode = @BranchCode

UPDATE A
SET A.CalculatedInterest = B.CalculatedInterestNew + @CalculatedInterestNew
FROM InterestBuffer A
INNER JOIN #tempInterestCalc B ON A.AccountNo = B.AccountNo AND A.BranchCode = B.BranchCode
WHERE A.BranchCode = @BranchCode

INSERT INTO InterestBuffer
SELECT A.AccountNo, A.BranchCode, A.CalculatedInterestNew + @CalculatedInterestNew
FROM #tempInterestCalc A
WHERE A.CalculatedInterestOld IS NULL

DROP TABLE #tempInterestCalc
--ROLLBACK
--COMMIT TRAN
Run Code Online (Sandbox Code Playgroud)

对于Branch 0002,0003只需将@BranchCode变量值更改为0002&0003并同时运行它们. 第一分公司

第二分支

分公司三

Bri*_*ler 4

您可能会遇到潜在的死锁问题,因为您InterestBuffer在写入后正在对表进行另一次读取。如果另一个事务阻塞了部分事务,则该事务可能会死锁InterestBuffer更新,并且您的事务尝试再次从中读取数据以进行插入所需的选择,则该事务可能会死锁。

InterestBuffer您说您在计算表时已经离开了#tempInterestCalc...为什么不使用它来缓存所需的一些数据InterestBuffer,这样您就不必再次读取它?

将临时表更改为:

Create Table #tempInterestCalc
(
    AccountNo CHAR(17) PRIMARY KEY,
    CalculatedInterestNew MONEY,
    CalculatedInterestOld MONEY
)
Run Code Online (Sandbox Code Playgroud)

您可能希望在开始事务之前设置可重复读取隔离级别:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Run Code Online (Sandbox Code Playgroud)

它是更严格的锁定,但会阻止其他事务尝试同时处理相同的记录,您可能需要这样做,因为您正在组合旧值和新值。考虑这种情况:

  • 事务1读取数据并想在现有的基础上添加0.03 CalculatedInterest
  • 事务2读取数据,想要在5.0的基础上加0.02。
  • 交易1更新CalculatedInterest至 5.03。
  • 事务 2 的更新将事务 1 的值覆盖为 5.03(而不是添加到其中并得出 5.05)。

如果您确定的话,也许您不需要这个,但如果是这样,则读取提交不会让事务 2 读取值,直到事务 1 完成为止。

然后首先将事务分为不同的读取阶段,然后是写入阶段:

--insert data into #tempInterestCalc and include the previous interest value
insert into #tempInterestCalc
select AccountNo, 
    Query.CalculatedInterest CalculatedInterestNew, 
    InterestBuffer.CalculatedInterest CalculatedInterestOLD
from 
    (
    ...
    ) Query
left join InterestBuffer
on Query.AccountNo = InterestBuffer.AccountNo

UPDATE A
SET A.CalculatedInterest = B.CalculatedInterestNew + B.CalculatedInterestOld
FROM InterestBuffer A
INNER JOIN #tempInterestCalc B ON A.AccountNo = B.AccountNo

INSERT INTO InterestBuffer
SELECT A.AccountNo, A.CalculatedInterestNew, 0, 0
FROM #tempInterestCalc A
--no join here needed now to read from InterestBuffer
WHERE CalculatedInterestOld is null
Run Code Online (Sandbox Code Playgroud)

这不应该死锁...但您可能会看到由于Lock Escalation导致的“不必要的”阻塞,特别是在您更新大量行时。一旦表上的锁超过 5000 个,它将升级为表。在该交易完成之前,其他交易将无法继续。这不一定是坏事......您只是想确保您的事务尽可能短,以免锁定其他事务太久。如果锁升级给您带来了问题,您可以采取一些措施来缓解这种情况,例如:

  • 将事务分解为更小的工作块,从而创建更少的锁。
  • 确保您有一个高效的查询计划。
  • 明智地使用锁定提示。

InterestBuffer检查您的查询计划并查看任何语句中是否有任何表扫描...特别是对于您的初始人口,#tempInterestCalc因为您没有展示如何构建它。

如果您绝对不会同时更新一个分支中的帐户,那么您可能会考虑保持主键相同,但将聚集索引更改为Branch, Account number(顺序很重要)。这将使同一分支的所有记录物理上彼此相邻,并减少您的计划执行表扫描或锁定其他事务可能需要的页面的机会。然后,您还可以使用PAGLOCK提示,这将鼓励 SQL Server 按页而不是按行锁定,并防止达到触发锁定升级的阈值。为此,请修改问题中UPDATE 6中的代码,如下所示:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN TRAN

Declare @BranchCode AS Char(4) = '0001'
Declare @CalculatedInterestNew MONEY = 10

CREATE TABLE #tempInterestCalc
(
    AccountNo Char(17),
    BranchCode Char(4),
    CalculatedInterestNew MONEY,
    CalculatedInterestOld MONEY
)

INSERT INTO #tempInterestCalc
SELECT A.AccountNo, A.BranchCode, ISNULL(B.CalculatedInterest, 0), B.CalculatedInterest
FROM Account A
LEFT JOIN InterestBuffer B
ON A.AccountNo = B.AccountNo AND A.BranchCode = B.BranchCode
WHERE A.BranchCode = @BranchCode

UPDATE A WITH (PAGLOCK)
SET A.CalculatedInterest = B.CalculatedInterestNew + @CalculatedInterestNew
FROM InterestBuffer A
INNER JOIN #tempInterestCalc B ON A.AccountNo = B.AccountNo AND A.BranchCode = B.BranchCode
WHERE A.BranchCode = @BranchCode

INSERT INTO InterestBuffer WITH (PAGLOCK)
SELECT A.AccountNo, A.BranchCode, A.CalculatedInterestNew + @CalculatedInterestNew
FROM #tempInterestCalc A
WHERE A.CalculatedInterestOld IS NULL

DROP TABLE #tempInterestCalc
--ROLLBACK
--COMMIT TRAN
Run Code Online (Sandbox Code Playgroud)

由于记录在物理上排序在一起,因此即使更新数千条记录,也只会锁定几页。然后,您可以与分支 0001 同时运行分支 0003 的事务,而不会出现任何阻塞问题。但是,如果您尝试同时执行相邻分支(例如 0002),则可能会遇到阻塞问题。这是因为分支 0001 和 0002 中的一些记录可能会共享同一页。

如果您确实需要分离分支,您可以考虑使用分区表或索引。我对它们了解不多,但听起来它可能对你想做的事情有用,但它也可能伴随着它自己的一系列复杂性。