SQL Server 删除性能

Posted

技术标签:

【中文标题】SQL Server 删除性能【英文标题】:SQL Server delete performance 【发布时间】:2013-02-27 14:24:34 【问题描述】:

我的 .NET Web 应用程序中有一个例程,允许我们平台上的用户清除他们的帐户(即删除他们的所有数据)。该例程在存储过程中运行,本质上是循环通过相关数据表并清除他们创建的所有各种项目。

存储过程看起来像这样。

ALTER procedure [dbo].[spDeleteAccountData](
    @accountNumber varchar(30) ) 
AS
BEGIN
    SET ANSI_NULLS ON ;
    SET NOCOUNT ON;

    BEGIN TRAN  
    BEGIN TRY
        DELETE FROM myDataTable1 WHERE accountNumber = @accountNumber
        DELETE FROM myDataTable2 WHERE accountNumber = @accountNumber
        DELETE FROM myDataTable3 WHERE accountNumber = @accountNumber
        //Etc.........

    END TRY
    BEGIN CATCH
        //CATCH ERROR
    END CATCH

IF @@TRANCOUNT > 0
    COMMIT TRANSACTION; 
SET ANSI_NULLS OFF;
SET NOCOUNT OFF;
END

问题在于,在某些情况下,我们可以在一个表上拥有超过 10,000 行,并且该过程可能需要 3-5 分钟。在此期间,数据库上的所有其他连接都会受到限制,从而导致超时错误,如下所示:

System.Data.SqlClient.SqlException (0x80131904):超时。在操作完成之前超时时间已过或服务器没有响应。

我可以进行任何常规更改来提高性能吗?我很欣赏与我们的数据库模式设计相关的许多未知数,但欢迎一般的最佳实践建议!我曾考虑将这项任务安排在凌晨运行以尽量减少影响,但这远非理想,因为用户在完成这项任务之前无法重新获得对其帐户的访问权限。

其他信息:

SQL Server 2008 R2 标准版 所有表都有一个聚集索引 没有触发器与任何相关表上的任何删除命令相关联 许多表中存在外键引用,但删除顺序说明了这一点。

编辑:格林威治标准时间 16:52

delete proc 影响大约 20 个表。最大的有大约 500 万条记录。其他的没有超过 200,000 条记录,有些只包含 1000-2000 条记录。

【问题讨论】:

所有表都有聚集索引,很棒。但它在哪一栏?这些表上有多少其他索引?他们多大?您是否有一个您不想显示的回滚?如果 myDataTable2 上的删除失败,您真的有任何理由想要将删除回滚到 myDataTable1 吗?鉴于您已经按照正确的 FK 依赖顺序构建了删除,您是否希望找到失败的条件? 每个表都有一个 bigint 标识列设置为主键。 accountNumber 列没有针对它们设置索引,所以我猜这就是我出错的地方! 删除过程影响了大约 20 个表。最大的有大约 500 万条记录。其他的没有更多的 200,000 条记录,有些只包含 1000-2000 条记录。关于回滚的好点!如果删除语句失败,则无需回滚,但用户的帐户将损坏,因此如果发生这种情况,他们需要被弹出并锁定其帐户。回滚选项会带来很大的性能损失吗? 如果您为 all 的删除操作保持事务打开,那么 all 表可能会在整个事务长度内被阻塞。所以一些选项 - 我喜欢@marc_s 提出的想法,您可以在其中标记要删除的帐号,但直到稍后才真正删除它。 “被弹出”部分是您可以基于标志使用的逻辑,而不是基于行的存在/不存在。您还可以考虑对从这些表中读取的查询使用已提交的读取快照隔离——性能命中 tempdb,但读取器不会被删除阻塞。 【参考方案1】:

您在所有表中都有accountNumber 的索引吗?

看到您使用该列的WHERE 子句删除,这可能会有所帮助。

另一种选择(可能甚至更好的解决方案)是在晚上安排删除操作,例如当用户选择删除他的帐户时,您只是设置了一个标志,并且删除作业在晚上运行,实际上删除了那些标记为要删除的帐户。

【讨论】:

我同意索引评论。我只是将其重组为一个陈述而不是一个问题——例如“如果你没有索引,你应该!” 10K 条记录并不大,所以要花这么长时间似乎有很多未索引的数据和/或很多约束,FK 检查它正在做的事情。 感谢您的建议!此删除过程很可能只会在帐户的生命周期内执行一次,并且通常会使用少量数据。因此 90% 的删除案例在 3-5 秒内执行。造成问题的是大帐户,因此我们可以有选择地安排这些案例以进行非高峰删除。我担心在所有这些表中添加一个额外的“accountNumber”索引,我认为这会导致更严重的性能问题。【参考方案2】:

如果您在 accountNumber 字段上有索引,那么我猜删除的时间很长是由于锁(由其他进程生成)或受各个表影响的外键。

    如果是由于锁定,那么您应该看看是否可以使用 nolock 来减少它们,而实际上您可以做到这一点。 如果存在外键问题 .. 那么您必须等待 .. 如果您不想等待并且您的应用程序逻辑不依赖于强制执行 FK(例如向应用程序发送 FK 违规错误,并针对它们进行测试)或者您觉得您的应用程序很完美,然后在短时间内不需要 FK,那么您可以在删除之前使用 ALTER TABLE xxx NOCHECK CONSTRAINT all 禁用相关的 FK,然后重新启用它。李>

当然,纯粹主义者会因为后者而责备我,但我在需要时使用了很多次。

【讨论】:

谢谢。尽管在我的删除的 WHERE 子句上有索引,但我的删除速度非常慢(从 15k 中删除 100 行花了将近 2 分钟)。禁用/重新启用 FK 使其在不到一秒的时间内运行。【参考方案3】:

SqlCommand.CommandTimeout 是简短的回答。增加它的价值。

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtimeout.aspx

请注意,连接超时与 CommandTimeout 不同。

...

每个表的“accountNumber”都有索引吗?

您可以在表的代理键上使用聚簇键,但不能在“accountNumber”上使用。

...

基本上,您必须在这里查看执行计划(或发布执行计划)。

但这里有一些“入门代码”,用于尝试在该列上建立索引。

if exists (select * from dbo.sysindexes where name = N'IX_myDataTable1_accountNumber' and id = object_id(N'[dbo].[myDataTable1]'))
    DROP INDEX [dbo].[myDataTable1].[IX_myDataTable1_accountNumber]
GO

CREATE INDEX [IX_myDataTable1_accountNumber] ON [dbo].[myDataTable1]([accountNumber]) 
GO

【讨论】:

您希望用户等待 3-5 分钟才能使用该应用程序? 这将防止错误,但真正的问题是删除需要足够长的时间才能达到超时阈值。将超时时间增加到 5 或 10 分钟仍然意味着用户将被阻止 5 或 10 分钟或直到查询完成。 不,用户不必在 Web 应用程序上等待 3-5 分钟。但这是我的主观意见,不一定是他的。 没有用户会等待 3-5 分钟以获取任何基于 Web 的内容。有些事情需要 5 秒钟,我已经非常沮丧。 33 秒还是太长了。增加一个从一开始就不应该花这么长时间的查询的超时就像把燕尾服放在大便上一样。 YMMV。【参考方案4】:

可能值得将数据库切换到已提交读快照模式。这会对性能产生影响,具体程度取决于您的应用程序。

在 Read Committed Snapshot 模式下,写入者和读取者不再相互阻止,尽管写入者仍然阻止写入者。您没有说删除阻止了表上的哪种活动,所以很难说这是否会有所帮助?

http://msdn.microsoft.com/en-us/library/ms188277(v=sql.105).aspx

话虽如此,删除约 10k 行的表需要 3-5 分钟似乎慢得离谱。你提到外键,外键是否被索引?如果没有,删除可能会导致另一端的表扫描以确保您没有破坏 RI,所以也许先检查一下? SQL Server Profiler 对这些删除查询的读/写说了什么?

【讨论】:

有很多 FK / 表,即使键被索引,删除也可能非常慢..【参考方案5】:

您可能想尝试的一种方法是:

    创建一个 SP。 对于每个表,以适合您的大小批量删除行(例如每批 10 行)。 将每个批量删除放在一个事务中,并在每个事务之间添加自定义延迟。

例子:

    DECLARE @DeletedRowsCount INT = 1, @BatchSize INT = 300;
    WHILE (@DeletedRowsCount> 0) BEGIN
        BEGIN TRANSACTION  
            DELETE TOP (@BatchSize) dbo.Table
            FROM dbo.Table
            WHERE Id = @PortalId;
            SET @DeletedRowsCount = @@ROWCOUNT;
        COMMIT;

        WAITFOR DELAY '00:00:05';
    END

我想你也可以在没有 SP 的情况下做同样的事情。 事实上,这样可能会更好。

【讨论】:

以上是关于SQL Server 删除性能的主要内容,如果未能解决你的问题,请参考以下文章

如何编写此查询以在 Sql Server 中获得更好的性能?删除子字符串行

SQL Server 数据库一次性能删除的最大上限是多少?

sqlserver 中一些常看的指标和清除缓存的方法

SQL Server 2005删除日志文件的几种方法小结

sql server 2008中“Merge”子句的性能如何?

请有经验的DBA进来回答一下,sqlserver大批量数据迁移问题