SQL Server 中的并发执行

2024-04-23

表架构 (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:我下面的答案有时会起作用,如果我REBUILD INDEXInterestBuffer 表中的 BranchCode。批量插入/更新是否会导致索引出现问题???

更新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

For Branch 0002, 0003 just change the @BranchCode variable value to 0002 &0003 and Run them simultaneously. Branch One


您可能会遇到潜在的死锁问题,因为您正在对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.03CalculatedInterest of 5.0.
  • 事务2读取数据,想要在5.0的基础上加0.02。
  • 交易1更新CalculatedInterest至 5.03。
  • 事务 2 的更新将事务 1 的值覆盖为 5.03(而不是添加到它并得出 5.05)。

也许你不需要这个,如果你的sure该事务永远不会接触相同的记录,但如果是这样,则读取提交将不会让事务 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

这不应该死锁......但是你可能会看到“不必要的”阻塞,因为锁升级 https://technet.microsoft.com/en-us/library/ms184286%28v=sql.100%29.aspx,特别是当您要更新大量行时。一旦表上的锁超过 5000 个,它将升级为表。在该交易完成之前,其他交易将无法继续。这不一定是坏事......您只是想确保您的事务尽可能短,以免锁定其他事务太久。如果锁升级给您带来了问题,有您可以采取一些措施来缓解这种情况 https://support.microsoft.com/en-us/kb/323630例如:

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

检查您的查询计划并查看是否有任何表扫描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 中的一些记录可能会共享同一页。

如果您确实需要分离分支,您可以考虑使用分区表或索引 https://msdn.microsoft.com/en-us/library/ms190787%28v=sql.100%29.aspx。我对它们了解不多,但听起来它可能对你想做的事情有用,但它也可能伴随着它自己的一系列复杂性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SQL Server 中的并发执行 的相关文章

随机推荐

  • 按日期字符串索引时间序列

    给定一个时间序列 s 使用日期时间索引 我希望能够通过日期字符串对时间序列进行索引 我是否误解了这应该如何运作 import pandas as pd url http ichart finance yahoo com table csvs
  • 错误:“us”中的 Dialogflow 服务器收到了对位于“europe-west2-dialogflow.googleapis.com”中的资源的请求。

    我想使用对话流检测意图 但由于某些原因 我的对话流代理所在区域是 europe west2 因此 为了指定位置 我使用 Dialogflow API 的 v2beta1 版本 如文档中所述 但它不起作用 我有以下错误Dialogflow s
  • Scala:研讨会建议

    我被邀请为 PHP 观众举办一个 Scala 研讨会 这些人中的很多人根本没有接触过函数式编程语言 所以我想知道如何以某种方式介绍他们 以便他们充分利用它 我听说很多人很难理解高阶函数等 你有什么经历 我不想用太先进的东西吓跑他们 也不想让
  • 删除 ImageView 会导致某些手机崩溃

    因此 出于某种原因 在我的 Samsung Galaxy S2 Android 版本 4 0 4 上尝试从relativelayout 中删除 ImageView 时 我得到了 NPE 这是运行的代码行 mainView removeVie
  • 多周期持久化、向量化、时间序列python

    我有一个包含每日值的 DataFrame 并且我正在使用各种方法来预测未来两周的值 作为一个基础的 天真的预测 我只想简单地说今天的价值是未来两周的最佳预测 例如 的值01 Jan 2012 is 100 那么我想要预测02 Jan 201
  • 在 RecyclerView android 中拖放期间面临位置值问题

    如何在 RecyclerView 适配器中拖放时获取新的位置值 拖放工作正常 但是当我设置 setOnClickListener 打开我的详细活动时 它给了我旧的位置值 例如 如果我拖动第二个项目 INDEX 1 并放在第一个项目的位置 I
  • 隐藏图中某些图形对象的 MATLAB 图例条目

    MATLAB 图例列出了绘图中的所有内容 包括您在绘图上放置的指南 绕过这个问题的软糖就是要做的 Plot Add legend Add guidelines 然而 MATLAB 将最新的行放在前面 这意味着指南将位于显示的数据之上 丑陋且
  • varchar 变量的字符串比较失败

    我无法理解为什么会得到以下结果 declare myVar1 varchar Friday declare myVar2 varchar 10 Friday select case when myVar1 Friday then yes e
  • UiAutomator getLastTraversedText()

    我试图使用 Android UiAutomator 测试 Android Webview 据我了解文档 http developer android com tools help uiautomator UiDevice html getL
  • Python 中匹配“中文+数字”模式的正则表达式

    在Python 3 3中 我想匹配下面的模式 但它总是失败 摄氏零下253 我使用了下面的正则表达式 x00 x47 x58 x7F 它不是排除除数字之外的所有 ascii 吗 根据您使用的编程语言 您可以使用以下内容 p Han p N
  • 如何在Linux中使用相对路径打开文件?

    我有一个程序 它使用相对路径 例如 打开文件 现在的问题是 当我从另一个目录执行程序时 相对路径不是相对于程序而是相对于工作目录 因此 如果我使用 path to program myprog 启动程序 它将无法找到该文件 有没有办法独立于
  • 你能检测到 UIViewController 何时被解除或弹出吗?

    每当我的视图控制器之一被解除 弹出 卸载时 我都需要在共享资源中执行一些清理 这可能是当用户点击该单个屏幕上的后退按钮时 或者调用 popToRootViewController 时 在这种情况下 理想情况下我能够清除弹出的每个控制器 显而
  • 创建子列表[重复]

    这个问题在这里已经有答案了 与列表扁平化相反 给定一个列表和长度 n 返回长度为 n 的子列表的列表 def sublist lst n sub result for i in lst sub i if len sub n result s
  • 禁用 RVM 还是使用未安装 RVM 的 Ruby?

    对于 Rails 应用程序 我安装了 Ruby 1 8 7 和 Rails 2 3 11 而且运行良好 之后我安装了 RVM 和 REE 因此 在运行 ruby 脚本 服务器时 它使用 REE 但现在我想使用较旧的 Ruby 1 8 7 它
  • 如何在intelliJ IDEA中分离spring上下文

    我在配置 IntelliJ IDEA 来开发 spring 和 Maven 支持的应用程序时遇到问题 应用程序有两个独立的弹簧配置 用于生产和测试目的 在 IDEA 的 Spring Facet props 中 我创建了两个不同的文件集 但
  • REngine 不调用 R Console

    从其中一个站点收集的代码 它不会从 REngine 调用 R Console 创建REngine后就断掉了 没有去try catch 问题 Rengine re new Rengine args true new TextConsole2
  • 助手在 Rails 3(.2) 中使用大量内存

    在将 Rails 2 应用程序迁移到 Rails 3 时 我遇到了内存使用量的巨大增加 经过一番研究 我发现帮助者是问题所在 取消注释视图中的任何助手会加快一切速度 我试图找到真正问题的事情 include all helpers fals
  • CSS:四个 div,第三个必须占据其余空间

    我在互联网上尝试了几种资源 也如此 但我根本无法解决这个问题 网页上只有四个浮动 div div 1 2 和 4 具有固定宽度 div 3 必须占据剩余的宽度 div 2 和 3 之间必须有填充 所有 div 必须具有 padding 0
  • 这是 GCC 中的错误还是我的代码错误?

    我有这个C代码 int test signed char anim col if anim col gt 31 return 1 else if anim col lt 15 return 2 return 0 使用 Android NDK
  • SQL Server 中的并发执行

    表架构 SQL Server 2012 Create Table InterestBuffer AccountNo CHAR 17 PRIMARY KEY CalculatedInterest MONEY ProvisionedIntere