大表的在线索引重建需要排他锁

Cle*_*ent 6 sql-server clustered-index azure-sql-database index-maintenance online-operations

我正在尝试在 Azure SQL 数据库上重建大表 (77GB) 的聚集索引。表上有高并发事务活动,因此我正在使用该ONLINE=ON选项。

这对于较小的表很有效;但是,当我在这个大表上运行它时,它似乎在表上使用了排他锁。我不得不在 5 分钟后停止它,因为所有事务活动都超时了。

来自 SPID 199 的会话:

ALTER INDEX PK_Customer ON [br].[Customer] 
REBUILD WITH (ONLINE = ON, RESUMABLE = ON);
Run Code Online (Sandbox Code Playgroud)

从另一个会话:

CL

在相同的结果中更进一步:

在此处输入图片说明

  • 对象 978102525 是聚集索引。
  • 对象 1125579048 是表。

在此处输入图片说明

我知道在线重建可以在过程开始和结束时锁定“短”持续时间。但是,这些锁定会持续几分钟,这并不完全是“短”持续时间。

附加信息

在重建运行时,我运行了SELECT * FROM sys.index_resumable_operations;但它返回了 0 行,就好像重建根本没有开始一样。

较小的表也有一个可能大于 900 字节的 PK,并且相同的ALTER语句在没有任何长时间阻塞的情况下工作,所以我认为它与 PK 大小无关。这些较小的表也有相似数量的nvarchar(max)列。我能想到的唯一真正的区别是这个表有更多的行。

表定义

这是 的完整定义br.Customer。没有外键或非聚集索引。

CREATE TABLE [br].[Customer](
    [Id] [bigint] NOT NULL,
    [ShopId] [nvarchar](450) NOT NULL,
    [accepts_marketing] [bit] NOT NULL,
    [address1] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [address2] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [city] [nvarchar](max) NULL,
    [company] [nvarchar](max) NULL,
    [country] [nvarchar](max) NULL,
    [country_code] [nvarchar](max) NULL,
    [email] [nvarchar](max) MASKED WITH (FUNCTION = 'email()') NULL,
    [first_name] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [last_name] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [note] [nvarchar](max) NULL,
    [phone] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [province] [nvarchar](max) NULL,
    [province_code] [nvarchar](max) NULL,
    [state] [nvarchar](max) NULL,
    [tax_exempt] [bit] NOT NULL,
    [verified_email] [bit] NOT NULL,
    [zip] [nvarchar](max) NULL,
    [multipass_identifier] [nvarchar](max) NULL,
    [created_at_local] [datetimeoffset](7) NOT NULL,
    [updated_at_local] [datetimeoffset](7) NOT NULL,
    [tags] [nvarchar](max) NULL,
    [address_phone] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [address_firstname] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [address_lastname] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [ShopId] ASC,
    [Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [br].[Customer] ADD  DEFAULT ('0001-01-01T00:00:00.000+00:00') FOR [created_at_local]
GO

ALTER TABLE [br].[Customer] ADD  DEFAULT ('0001-01-01T00:00:00.000+00:00') FOR [updated_at_local]
GO
Run Code Online (Sandbox Code Playgroud)

sp_WhoIsActive

我今天(9 月 24 日)进一步调查并运行了SP_WHOISACTIVE @get_locks = 1,它清楚地显示UPDATE/INSERT/DELETE了运行ALTER INDEX.

通过查询运行在 Customer 表上持有的锁ALTER INDEX

ALTER INDEX PK_Customer ON [br].[Customer] 
REBUILD WITH (ONLINE = ON, RESUMABLE = ON);
Run Code Online (Sandbox Code Playgroud)

来自UPDATE在同一张表上运行的会话的锁:

CREATE TABLE [br].[Customer](
    [Id] [bigint] NOT NULL,
    [ShopId] [nvarchar](450) NOT NULL,
    [accepts_marketing] [bit] NOT NULL,
    [address1] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [address2] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [city] [nvarchar](max) NULL,
    [company] [nvarchar](max) NULL,
    [country] [nvarchar](max) NULL,
    [country_code] [nvarchar](max) NULL,
    [email] [nvarchar](max) MASKED WITH (FUNCTION = 'email()') NULL,
    [first_name] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [last_name] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [note] [nvarchar](max) NULL,
    [phone] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [province] [nvarchar](max) NULL,
    [province_code] [nvarchar](max) NULL,
    [state] [nvarchar](max) NULL,
    [tax_exempt] [bit] NOT NULL,
    [verified_email] [bit] NOT NULL,
    [zip] [nvarchar](max) NULL,
    [multipass_identifier] [nvarchar](max) NULL,
    [created_at_local] [datetimeoffset](7) NOT NULL,
    [updated_at_local] [datetimeoffset](7) NOT NULL,
    [tags] [nvarchar](max) NULL,
    [address_phone] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [address_firstname] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
    [address_lastname] [nvarchar](max) MASKED WITH (FUNCTION = 'partial(2, "XXX", 0)') NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [ShopId] ASC,
    [Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [br].[Customer] ADD  DEFAULT ('0001-01-01T00:00:00.000+00:00') FOR [created_at_local]
GO

ALTER TABLE [br].[Customer] ADD  DEFAULT ('0001-01-01T00:00:00.000+00:00') FOR [updated_at_local]
GO
Run Code Online (Sandbox Code Playgroud)

Cle*_*ent 5

Microsoft 支持已确认这是 Sql Azure 中的一个错误(不确定它是否会影响 Sql Server)。我的理解是,如果从表中删除一些列,那么下次我们重建索引时,sql server将尝试回收已删除的列空间(我在这里故意含糊其辞,因为我不完全确定这意味着)并且即使提供了 ONLINE = ON 选项,此过程也会在表上有独占锁的情况下发生。他们正在努力修复。

  • 截至 2024 年 1 月 25 日,Azure SQL 托管实例(显然与 Azure SQL DB 共享许多核心代码)中仍然存在此问题。如果修复正在进行中,则似乎尚未发布。我个人现在认为在线索引重建在产品中被破坏。 (4认同)