bri*_*ing 4 sql-server concurrency queue availability-groups
我们有一个看起来像这样的工作表
CREATE TABLE [dbo].[Clearing](
[Skey] [decimal](19, 0) IDENTITY(1,1) NOT NULL,
[BsAcctId] [int] NULL,
[Status] [varchar](20) NULL,
CONSTRAINT [csPk_Clearing] PRIMARY KEY CLUSTERED ( [Skey] ASC )
)
Run Code Online (Sandbox Code Playgroud)
像这样的覆盖索引
CREATE NONCLUSTERED INDEX [IX_Status] ON [dbo].[Clearing]
(
[Status] ASC
)
INCLUDE ( [Skey], [BsAcctId])
Run Code Online (Sandbox Code Playgroud)
我们使用这个查询来选择下一个工作
select top (1) Skey, BsAcctId, Status from Clearing with ( readpast, updlock )
where (Clearing.Status = 'NEW')
order by Clearing.Skey
Run Code Online (Sandbox Code Playgroud)
(真实表大约有 10 列。它们都在索引 include() 子句和选择列列表中。)
执行计划非常简单。它使用 IX_Status 进行索引查找,然后使用顶级运算符。由于索引按 (status, skey) 排序,因此计划不需要排序。
该表位于 AlwaysOn 可用性组中的数据库中。该组有 2 个数据库服务器。(这是一个测试系统。)
通常这个表和查询工作得很好。所以我们去应用 Windows 更新,并照常进行。
在第二次故障转移并且所有工作进程获得到新主进程的新连接之后,查询开始失败,因为多个进程开始获得相同的作业。
问题与负载有关。运行 4 个工作进程时,它没有发生。但是对于 10 名工人,这种情况始终如一。
这是使用 SQL Server 2016 Enterprise。我们没有启用查询存储来查看执行计划是否在某个时候很奇怪。
关于为什么查询会在两次故障转移后开始失败的任何建议?
由于查询只使用索引而不接触表,UPDLOCK可靠吗?
更新 1 - 我们更改了进程以在执行选择后列出 spid 持有的锁(使用 sp_lock @@spid)。对于同一个 skey,我们看到 IX_Status 索引上持有不同的 KEY 锁(indid=9)
KEY (aad9d6e672f9) U
KEY (154698b9131c) U
Run Code Online (Sandbox Code Playgroud)
更新 2 - 使用索引提示没有帮助。
更新 3 - 删除查询中的 order by 子句避免了这个问题。但是我们有第二个表,同样的问题需要 order by。
更新 4 - 我们的工作进程维护一个数据库连接池。ODBC 不会告诉我们何时发生故障转移,因此与旧主服务器的连接会保留在池中,直到我们尝试使用它们但它们失败为止。我们怀疑在故障转移 DB1 -> DB2 -> DB1 之后,与 DB1 的旧连接可能不会像它们应该的那样失败。我们做了一个更改,在任何一个连接丢失后关闭所有池连接,这似乎避免了这个问题。(SQL Server ODBC 添加了“连接弹性”功能,这加剧了这种怀疑。)
正如大卫在他的回答中提到的,如果您碰巧通过不同的索引访问该行,您可以从多个会话中获取同一行。
该UPDLOCK
提示仅适用于特定的访问方法。U
锁定非聚集索引行不会阻止另一个查询获取U
对不同索引(包括聚集索引,如果有)的锁定。
从不同的会话运行这两个查询(带有索引提示)会导致返回同一行:
-- Session 1
BEGIN TRANSACTION;
SELECT TOP (1) Skey, BsAcctId, Status
FROM dbo.Clearing WITH(READPAST, UPDLOCK, INDEX(2))
WHERE ([Status] = 'NEW')
ORDER BY Skey;
-- Session 2
BEGIN TRANSACTION;
SELECT TOP (1) Skey, BsAcctId, Status
FROM dbo.Clearing WITH(READPAST, UPDLOCK, INDEX(1))
WHERE ([Status] = 'NEW')
ORDER BY Skey;
Run Code Online (Sandbox Code Playgroud)
故障转移后,将为传入的查询编译新的执行计划 - 因此这可以解释为什么您在故障转移后最终出现新行为。正如大卫还说的,你可以强制索引来避免这个问题。
作为旁注,您还应该使用ROWLOCK
提示,因为READPAST
只能跳过以行粒度获取的锁。
你也提到了这一点:
问题与负载有关。运行 4 个工作进程时,它没有发生。但是对于 10 名工人,这种情况始终如一。
所以听起来故障转移并不是唯一改变的东西——你还增加了应用程序方面的并发性。
我尝试用一些数据加载你的表/索引:
INSERT INTO dbo.Clearing
([Status])
SELECT TOP 100
'NEW'
FROM master.dbo.spt_values;
INSERT INTO dbo.Clearing
([Status])
SELECT TOP 10000
'COMPLETE'
FROM master.dbo.spt_values v1
CROSS JOIN master.dbo.spt_values v2;
Run Code Online (Sandbox Code Playgroud)
然后我用你的查询加载了 SQL Query Stress,将它设置为一次在 10 个线程上运行,每 100 毫秒:
在运行时,我定期运行相同的查询,EXEC sp_lock @spid1 = @my_spid;
并在 SSMS 中添加到末尾。如果我SELECT
在同一个会话中多次运行查询(不回滚),我可以获得该会话持有的多个锁:
您可以使用%%lockres%%
谓词看到:
SELECT * FROM dbo.Clearing WITH (NOLOCK, INDEX(2)) WHERE %%lockres%% = '(36aaaeef6267)';
SELECT * FROM dbo.Clearing WITH (NOLOCK, INDEX(2)) WHERE %%lockres%% = '(84d6be32a10d)';
Run Code Online (Sandbox Code Playgroud)
没有太多的并发性,如果SELECT
在会话中多次运行,通常会得到相同的行。但是随着其他查询一直在获取和释放锁,很容易获得不同的行。因此,请确保您不依赖于SELECT
两次返回相同的 ID(我们没有您工作负载的全部背景,因此这只是推测/仅供参考)。
仅依赖特定的锁可能是不安全的。考虑 Paul White(或其他类似文章)的这篇博文中描述的优化:缺少共享锁的案例
这篇文章概述了SELECT
查询仍然可以读取受 X 锁保护的行的情况:
SQL Server 包含一项优化,允许它避免在正确的情况下使用行级共享 (S) 锁。具体来说,如果没有读取未提交数据的风险,它可以跳过共享锁。
归档时间: |
|
查看次数: |
384 次 |
最近记录: |