11

表架构 (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
)

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

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_LOCKInterestBuffer 表。它说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:如果我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]

查询分支 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 并同时运行它们。 分行一

分支二

三分店

4

3 回答 3

4

i) 看看脏读是否没有问题,然后你可以使用 Nolock,没有问题,或者你可以在你的 proc 顶部设置 TRANSACTION ISOLATION LEVEL READ UNCOMMITTED。没有问题,两者都是一样的。在使用 nolock 之前,您应该只考虑“脏读问题”数据

ii) 你没有很好地解释你的问题。#tempInterestCalc 和#temp 有什么用。

iii) #tempInterestCalc 从哪里填充?

iv)在插入过程中#temp B的记录未被使用,因此您可以删除左连接并使用并存在。但这取决于上述几点何时清楚。

iv)您从临时表中的 InterestBuffer 获取记录,然后再次更新,然后再次插入同一个表中。这不清楚。

于 2015-10-01T07:17:41.313 回答
4

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

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

将您的临时表更改为:

Create Table #tempInterestCalc
(
    AccountNo CHAR(17) PRIMARY KEY,
    CalculatedInterestNew MONEY,
    CalculatedInterestOld MONEY
)

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

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

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

  • 事务 1 读取数据并希望将 0.03 添加到现有 CalculatedInterest的 5.0。
  • 事务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

这不应该死锁......但是您可能会看到由于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

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

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

于 2015-10-06T22:02:53.383 回答
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。

于 2015-09-23T06:22:35.483 回答