【问题标题】:Clustered Index Update Slow Update On Large Table大表上的聚集索引更新慢
【发布时间】:2017-09-20 16:36:12
【问题描述】:

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

表定义

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

执行计划

此过程在几分钟内被调用多达 1000 次,其中一些无法更新,因为更新先前的记录需要时间,可以采取哪些措施来增加此表中记录的更新。

【问题讨论】:

    标签: sql sql-server


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 如果键是字符怎么办?我看到在我们的一个表上使用单个字段 nchar 索引更新一个 int 字段需要 4 秒。
    猜你喜欢
    • 1970-01-01
    • 2011-12-01
    • 2017-12-02
    • 2011-09-18
    • 1970-01-01
    • 1970-01-01
    • 2012-08-26
    • 2019-02-13
    • 2018-05-08
    相关资源
    最近更新 更多