表模式(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
)
我正在做一个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
一切正常。并发执行期间出现问题。我通过加入其他各种表(包括左边连接和#tempInterestCalc
表,将数据插入InterestBuffer
,并为每个并发执行插入不同的数据集#tempInterestCalc
。
我的问题是,有时执行会被另一次执行锁定,直到我将它们串行提交。
我的问题是,因为我提供的是不同的数据集,所以它不会对行锁定产生任何其他并发操作的影响。任何建议将不胜感激。
更新1:我已将SP_LOCK
用于InterestBuffer表。它说IndId = 1, Type = KEY, Mode = X, Status = GRANT
。
我认为更新和插入阻止其他事务进行幻像读取。
更新2:抱歉!以前我告诉过更新很好。但现在我意识到第一个事务写入阻止了第二个事务写入。在第一个事务中,我运行更新并插入。在第二个事务中,在我在#tempInterestCalc表中插入数据后,我只需执行以下操作,它的工作正常。
--INSERT DATA INTO #tempInterestCalc
SELECT * FROM #tempInterestCalc
RETURN
--UPDATE InterestBuffer
--INSERT InterestBuffer
更新3:我认为我的问题是在更新期间从InterestBuffer读取数据并插入到InterestBuffer中。
更新4 :如果我在InterestBuffer表中使用了BranchCode REBUILD INDEX
,我的答案有时会有效。批量插入/更新是否有任何原因使索引???
更新5:我已经读过,如果需要锁定页面的最大行以进行批量更新,那么SQL Server可能会锁定该页面。是否有任何方法可以查看哪个行包含哪个页面或哪个页面将在执行期间锁定和释放?
更新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]
查询分支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
对于Branch 0002,0003只需将@BranchCode变量值更改为0002& 0003并同时运行它们。
答案 0 :(得分:4)
i)查看脏读中是否没有问题然后你可以使用Nolock,没有问题,或者你可以在proc的顶部设置TRANSACTION ISOLATION LEVEL READ UNANMITT。没有问题,两者都是一样的。< strong>你应该考虑&#34;脏读的问题&#34;使用nolock之前的数据。
ii)你没有很好地解释你的问题。使用#tempInterestCalc和#temp是什么。
iii)#tempInterestCalc从哪里填充?
iv)在插入过程中,#temp B的记录没有被使用,所以你可以删除左连接并使用和存在。但这取决于上述点是否清楚。
iv)你在临时表中从InterestBuffer获取记录然后再次更新然后再次插入同一个表中。这个不清楚。
答案 1 :(得分:4)
您可能遇到潜在的死锁问题,因为您在写入后正在对InterestBuffer
表执行另一次读取操作。如果另一个事务已阻止部分InterestBuffer
表进行更新,并且您的事务正在尝试再次读取进行插入所需的选择,则事务可能会死锁。
你说在计算你的InterestBuffer
表格时你已经离开加入了#tempInterestCalc
...为什么不用它来缓存InterestBuffer
所需的一些数据所以你不要必须再读一遍吗?
将临时表更改为:
Create Table #tempInterestCalc
(
AccountNo CHAR(17) PRIMARY KEY,
CalculatedInterestNew MONEY,
CalculatedInterestOld MONEY
)
您可能希望在开始交易之前设置可重复的读取隔离级别:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
这是更严格的锁定,但会阻止其他事务同时处理相同的记录,这可能是您需要的,因为您要组合旧值和新值。请考虑以下情况:
CalculatedInterest
5.0。CalculatedInterest
更新为5.03。如果你的肯定那些交易永远不会触及相同的记录,也许你不需要这个,但是如果这样读取提交将不会让事务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
这不应该死锁......但是你可能会看到由Lock Escalation引起的“不必要的”阻塞,特别是如果你要更新大量的行。一旦表上有超过5000个锁,它就会升级到表。在交易完成之前,其他任何交易都无法继续。这不一定是坏事......你只是想确保你的交易尽可能短,以免长时间锁定其他交易。如果锁定升级导致您出现问题,则有some things you can do to mitigate this,例如:
检查您的查询计划,看看在任何语句中是否有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
因为记录是物理排序在一起的,所以只能锁定几页......即使更新了数千条记录。然后,您可以在0001的同时为分支0003运行事务,而不会出现任何阻塞问题。但是,如果您尝试同时执行相邻分支(例如0002),则可能会出现阻塞问题。这是因为分支0001和0002中的某些记录可能共享同一页面。
如果你真的需要分开你的分支,你可以考虑使用Partitioned Table or Index。我对它们了解不多,但听起来它可能对你要做的事情有用,但它也可能伴随着它自身的一系列复杂功能。
答案 2 :(得分:3)
我刚刚找到了解决方案。因为我正在通过分支同时执行查询,所以我在表中稍作修改,如下所示;
Create Table InterestBuffer
(
AccountNo CHAR(17) PRIMARY KEY,
BranchCode CHAR(4),
CalculatedInterest MONEY,
ProvisionedInterest MONEY,
AccomodatedInterest MONEY,
)
Create Table #tempInterestCalc
(
AccountNo CHAR(17) PRIMARY KEY,
BranchCode CHAR(4),
CalculatedInterest MONEY
)
现在我在#tempInterestCalc中插入数据,由Branch过滤。
--INSERT DATA INTO #tempInterestCalc
SELECT *
into #temp
FROM InterestBuffer A WITH (NOLOCK)
Where A.BranchCode = MY_BRANCH
UPDATE A
SET A.CalculatedInterest = C.CalculatedInterest + B.CalculatedInterest
FROM InterestBuffer A
INNER JOIN #tempInterestCalc B ON A.AccountNo = B.AccountNo
INNER JOIN #temp C ON A.AccountNo = C.AccountNo AND A.BranchCode = C.BranchCode
INSERT INTO InterestBuffer
SELECT A.AccountNo, A.CalculatedInterest, 0, 0
FROM #tempInterestCalc A
LEFT JOIN #temp B ON A.AccountNo = B.AccountNo AND A.BranchCode = B.BranchCode
WHERE B.AccountNo IS NULL
我的问题是在更新/插入期间我试图从同一个表读取并且被其他事务写入锁定。
使用NOLOCK在这里是安全的,因为单个分支的数据不能被另一个事务修改,而只能由它自己的事务修改(没有脏读的可能性)。
不使用NOLOCK仍在寻找其他更好的方法。