6

我在我们的.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

删除过程影响大约 20 个表。最大的有大约 500 万条记录。其他的没有更多的 200,000 条记录,有些只包含 1000-2000 条记录。

4

5 回答 5

4

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

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

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

于 2013-02-27T14:29:29.567 回答
1

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

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

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

于 2013-02-27T14:34:38.347 回答
0

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
于 2013-02-27T14:26:55.637 回答
0

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

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

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

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

于 2013-02-27T15:01:18.827 回答
0

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

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

例子:

    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 的情况下做同样的事情。事实上,这样可能会更好。

于 2021-05-28T12:51:19.063 回答