群集表上的群集索引更新缓慢更新

时间:2017-09-20 06:27:07

标签: sql sql-server

我在更新包含数百万行的大表时遇到问题,请建议减少更新时间。

表格定义

CREATE TABLE [dbo].[tbl_sms_job_detail](
    [JobDetailID] [int] IDENTITY(1,1) NOT NULL,
    [JobID] [int] NULL,
    [DistributorID] [int] NULL,
    [ResellerID] [int] NULL,
    [CustomerID] [int] NULL,
    [SenderID] [nvarchar](50) NULL,
    [PhoneNumber] [nvarchar](100) NULL,
    [SMSMessage] [nvarchar](1000) NULL,
    [MessageType] [nvarchar](50) NULL,
    [MessageLength] [int] NULL,
    [MessageParts] [int] NULL,
    [ClientRate] [decimal](18, 5) NULL,
    [ClientCost] [decimal](18, 5) NULL,
    [ResellerRate] [decimal](18, 5) NULL,
    [ResellerCost] [decimal](18, 5) NULL,
    [DistributorRate] [decimal](18, 5) NULL,
    [DistributorCost] [decimal](18, 5) NULL,
    [RouteDetailID] [int] NULL,
    [SMSID] [nvarchar](200) NULL,
    [DLRStatus] [nvarchar](100) NULL,
    [ErrorCode] [int] NULL,
    [ErrorDescription] [nvarchar](2000) NULL,
    [SentDate] [datetime] NULL,
    [SentDateUTC] [datetime] NULL,
    [SMSSource] [nvarchar](50) NULL,
    [SMSType] [nvarchar](100) NULL,
    [APISMSID] [int] NULL,
    [DLRDate] [datetime] NULL,
    [DLRDateUTC] [datetime] NULL,
 CONSTRAINT [PK_tbl_sms_job_detail] PRIMARY KEY CLUSTERED 
(
    [JobDetailID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX [NonClusteredIndex-20170919-173756] ON [dbo].[tbl_sms_job_detail] (   [JobID] ASC,    [DLRStatus] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) GO

CREATE NONCLUSTERED INDEX [NonClusteredIndex-20170919-174142] ON [dbo].[tbl_sms_job_detail]
(
    [SMSID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

更新程序

CREATE Procedure [dbo].[sp_update_message_status]
@SMSID nvarchar(200),
@DLRStatus nvarchar(100),
@ErrorCode int,
@ErrorDescription nvarchar(2000)
AS
UPDATE tbl_sms_job_detail SET DLRStatus = @DLRStatus, ErrorCode = @ErrorCode, ErrorDescription = @ErrorDescription WHERE SMSID = @SMSID

执行计划

Execution Plan Image

此过程在几分钟内被调用多达1000次,其中一些无法更新,因为更新上一条记录需要时间来增加此表中记录的更新。

1 个答案:

答案 0 :(得分:0)

我怀疑问题不是由实际的聚集索引引起的,而是由更新查询产生的影响引起的。

MSSQL是一个基于页面的存储系统。向表中添加记录时,由于聚簇索引位于字段[JobDetailID] [int] IDENTITY(1,1)NOT NULL,因此每个新记录都应用于当前在表中的最后一页(如果适合)或将新页面添加到表格的末尾并将记录存储在其中。

假设[DLRStatus]和/或[ErrorDescription]启动空字符串或空字符串,当更新sproc运行时,它必须在页面中找到记录已存在的空间以存储新值。为此目的,SQL在每个页面文件中保留一点空间,但是当该空间用完时,它将不得不进行页面拆分 - 将一个页面文件的内容拆分为现有页面文件和新创建的空白页面文件。由于主键是群集的,因此必须插入此新页面文件,以便以聚簇索引顺序保存存储在表中的记录。这个页面拆分很可能是问题的根源。

SQL在创建新页面之前保留的空间量是可配置的,因此一种解决方案是最初创建具有大量“扩展”空间的页面文件。在索引上它被称为填充因子,但我不确定数据页的正确用语是什么(可能仍然是填充因子,但不确定)。

另一种方法是将返回的错误信息存储在单独的表中,然后在表[tbl_sms_job_detail]中存储“错误信息”记录的主键。只要密钥不是nvarchar / varchar(无论如何都会这样做),页面文件中所需的空间将被保留。因此,记录错误信息需要将可变文本信息附加到新表的最后一页文件的末尾,并更新原始表中已经为其保留空间的外键,因此不会触发页面切换。