【问题标题】:Why is my Azure SQL Database delete query performance so slow?为什么我的 Azure SQL 数据库删除查询性能这么慢?
【发布时间】:2015-09-28 18:22:12
【问题描述】:

我有一个大小约为 9GB 的 Azure Sql 数据库。它为一个每小时处理大约 135K 请求的 Web 应用程序提供服务。大多数数据是暂时的,它在数据库中的存在时间从几分钟到五天不等,然后被删除。每天大约有 10GB 移动通过数据库。

我尝试对表运行删除查询,以从总共 350,000 条记录中删除大约 250,000 条记录。大约 10% 的记录有一个或两个 nvarchar(max) 值,足以存储在 LOB 存储中。

周末,我试图一次将它们全部删除。在我取消查询之前它运行了四个小时,然后又回滚了 8 个小时——这是一个糟糕的举动。我真的没想到会这么糟糕。

然后我尝试了另一种方法。当 Web 应用程序每小时处理大约 10 万个请求时,该批处理在晚上运行。 tblJobs Id 字段是作为主键的唯一标识符。

insert @tableIds select Id from dbo.tblJobs with(nolock) 
where (datediff(day, SchedDate, getDate()) > 60)  
   or (datediff(day, ModifiedDate, getDate()) > 3 and ToBeRemoved = 1)

set @maintLogStr = 'uspMaintenance [tblJobs] Obsolete J records count @tableIds: ' + convert(nvarchar(12), (select count(1) from @tableIds))
insert dbo.admin_MaintenanceLog(LogEntry) values(@maintLogStr)

set @maintLogId = newid()
set @maintLogStr = 'uspMaintenance [tblJobs] Obsolete J records beginning loop...'
insert dbo.admin_MaintenanceLog(Id, LogEntry) values(@maintLogId, @maintLogStr)

while exists(select * from @tableIds)
begin
    delete @tableIdsTmp
    begin transaction
        insert @tableIdsTmp select top 1000 id from @tableIds
        delete p from @tableIdsTmp i join dbo.tblJobs p on i.id = p.Id
        delete x from @tableIdsTmp t join @tableIds x on t.id = x.id
        set @maintLogStr = 'uspMaintenance [tblJobs] Obsolete J records remaining count @tableIds: ' + convert(nvarchar(12), (select count(1) from @tableIds))
        update dbo.admin_MaintenanceLog set LogEntry = @maintLogStr, RecordCreated = getdate() where Id = @maintLogId
    commit transaction
    if @dowaits = 1 WAITFOR DELAY '00:00:01.000'
end

SchedDate、ModifiedDate 和 ToBeRemoved 未编入索引,因此在 @tableIds 中收集 Id 大约需要 3 分钟 - 还不错。

然后从日志条目中,从 tblJobs 中删除 11,000 条记录需要 1 小时 55 分钟,此时从远程机器调用的作业超时。

为什么要花这么长时间?我该怎么做才能加快速度?

【问题讨论】:

  • 是否有 Azure SQL 数据库性能专家可以帮助我解决这个问题?
  • 你不发布问题删除

标签: tsql azure azure-sql-database


【解决方案1】:

您的很多性能将与您使用的预留大小相关(如之前的答案中所述)。但是,您根本不需要在代码中执行表变量来实现您想要的。事实上,当涉及到一个连接时,你几乎不应该使用它们,因为它们没有关于它们的统计信息(因此当优化器需要做出复杂的选择时,可能会有糟糕的计划选择)。你可以在这里阅读官方指南:table variables documentation

所以,如果你退后一步,看看你正在尝试做的事情的核心,你可以这样做: 删除 top(1000) dbo.TblJobs 其中 (datediff(day, SchedDate, getDate()) > 60)
或 (datediff(day, ModifiedDate, getDate()) > 3 and ToBeRemoved = 1)

您可能会从此查询中获得表扫描,因为:

  • 您正在使用析取 (OR),这使得优化器很难找到一个访问路径来快速检索您的结果。
  • 您已使用 guid 作为密钥(我认为) - 有效地在可能的 guid 空间中随机生成 id
  • 将谓词放在内部函数的输出上,使优化器很难(更)确定如何对可以在列上设置范围的索引进行更智能的扫描。

当您进行扫描时,您可能会遇到锁定问题,因为您的工作负载同时在表上运行。因此,如果其他请求正在执行选择语句,您可能会在更新查询扫描表时阻止它。 (顺便说一句,发布查询计划对讨论扩展/并发问题非常有帮助)。

此外,假设您有一个循环,您从表中取出 1000 行,将它们复制到表变量中,然后最终将它们复制到另一个并与删除中的原始表连接,您正在转向O(N) 变成 O(N^2) 的问题。从算法上讲,使用这种方法添加到表中的行越多,您的查询可能会变得越来越慢。

您可以做一些事情来改进这个查询(可能):

  • 完全删除表变量并使用带有@@rowcount 的循环来确定您是否更新了任何内容
  • 从同一个数据库中删除日志记录(它竞争 IO 并且您已经被限制在那里)
  • 将查询谓词拆分为两个查询(其中析取的每个部分都在一个单独的查询中)。如果您碰巧在 scheddate 或 modifieddate 有一个索引,这将让您有更好的机会扫描索引。
  • 我不一定建议在这两个字段上添加索引(因为这样做存在潜在的并发问题),但如果您可以安全地这样做而不影响生产工作负载,您可以尝试将其作为实验。
  • 完成将查询拆分为 2 个查询的更改后,请考虑将 datediff 的计算更改为在查询之外 - 计算一次并传入一个值作为参数 (col
  • 如果您对对象的生命周期有所了解,则可能会切换到使用 newsequentialid 而不是 newid(或只是移至 bigint),以消除为 id 创建字段时的随机性。这将减少插入路径上的 b-tree 碎片,并可能在删除路径中打开更多机会(因为如果您在 id 上有一个聚集索引并且它们没有被另一个访问,那么扫描旧值可能会更容易用户,因为他们很可能正在接触更新的数据)。
  • 您可以使用 readpast 选项跳过被其他用户锁定的行 - 对于这种模式,您会很乐意这样做,除非它们都被锁定,因为您可能会提前结束循环,但如果您正在运行定期清理应该没问题。您可以在此处阅读有关该提示的信息:readpast hint docs

了解每个操作的成本有助于大多数性能调整和分析。使用“set statistics time on”和“set statistics io on”可以很好地衡量查询的物理成本。 “set statistics profile on”更适合查看每个查询运算符的算法成本(对于那个 N^2 问题)。

迟到总比没有好,但我希望这有助于您(和其他人)了解如果您将来遇到类似情况,如何提高 SQL Azure 性能。

【讨论】:

    【解决方案2】:

    IT 取决于数据库的 DTU(性能层)。在查询执行期间检查数据库的资源消耗以查看是否达到任何资源限制。将来在发出删除时也将查询分解为多个事务。这有助于您的事务必须回滚(例如升级到 SQL 数据库)或从连接到数据库的一端出现短暂网络故障的情况

    【讨论】:

    • 数据库是V12,S2层。 DTU 百分比图表显示在此期间大约为 95%,100% 没有延长时间 - 我在 >= 100% 上有 5 分钟的警报,但从未激活过。我不确定您将删除分成多个事务是什么意思。它一次发行 1000 个。
    【解决方案3】:

    作为一种快速解决方法/hack,在 SSMS 中,我右键单击数据库,然后选择生成脚本,在高级选项中,我选择创建 DROP ONLY 脚本。从那里把它放在一个新的查询窗口中,我做了一个查找和替换,将DROP TABLE更改为DELETE FROM。它仍然存在一些问题,即外键依赖项的顺序错误,但经过一些调整,我很快就能删除所有表。

    【讨论】:

      【解决方案4】:

      我在 sql azure 上的一个客户端数据库上发生了同样的事情。 delete 在 where 过滤器中使用日期,仅此而已。我在日期添加了一个聚集索引。制作一个 800 万行需要 25 分钟,但不幸的是我没有看到大的改进。

      截断整个表要快得多 - 立即完成并再次插入所有记录。

      我正在考虑放弃 sql azure,这不适用于任何类型的 BI 应用程序。为微软感到羞耻。

      【讨论】:

      • 截断本质上是除删除之外的另一种操作。因此,您的答案未达标。另外,请不要发表您对公司的看法。您可能只是使用了错误的工具,或者以错误的方式使用了正确的工具。也许您应该提出一个新问题,尽可能详细地详细说明您遇到的问题。
      猜你喜欢
      • 1970-01-01
      • 2014-03-12
      • 1970-01-01
      • 2011-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-11
      • 2012-12-19
      相关资源
      最近更新 更多