为什么我的Azure SQL数据库删除查询性能如此之慢?

时间:2015-09-28 18:22:12

标签: tsql azure azure-sql-database

我有一个大小约为9GB的Azure Sql数据库。它提供一个Web应用程序,每小时处理大约135K请求。大多数数据都是短暂的,它会在几分钟到五天内存放在数据库中并被删除。每天大约有10GB在数据库中移动。

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

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

然后我尝试了另一种方法。这个批处理在晚上运行,当时Web应用程序每小时处理大约100K请求。 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分钟 - 不错。

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

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

4 个答案:

答案 0 :(得分: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
  • 将谓词放在固有函数的输出上会使优化器难以确定如何对索引进行更智能的扫描,从而可以在列上设置范围。

进行扫描时,由于工作负载正在表上同时运行,因此可能会遇到锁定问题。因此,如果其他请求正在执行select语句,则在扫描表时可能会阻止更新查询。 (顺便说一句,发布查询计划确实有助于讨论扩展性/并发性问题。

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

您可以做一些事情来改善此查询(可能):

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

通过了解每个操作的成本,可以帮助大多数性能调整和分析。使用“设置统计信息打开时间”和“设置统计信息打开时间”可以为跟踪查询的物理成本提供良好的指标。 “将统计资料设置为开”更适合查看每个查询运算符的算法成本(针对该N ^ 2问题)。

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

作为快速解决方法/黑客,在SSMS中我右键单击数据库,然后选择Generate Scripts,在高级选项中,我选择创建DROP ONLY脚本。从那里把它放在一个新的查询窗口中,我做了一个查找和替换,将resources.py更改为I/O error on POST request for "anothermachine:31112/url/path";: class path resource [fileName.csv] cannot be resolved to URL because it does not exist. 。它仍然存在一些问题,即将它们置于错误的外键依赖关系的顺序,但经过一些调整后,我很快就能删除所有的表。

答案 3 :(得分:-1)

我在SQL Azure的一个客户端数据库上发生了同样的事情。 delete在where过滤器中使用日期,仅此而已。我在日期上添加了聚集索引。仅用800万行就完成了25分钟的工作,但不幸的是,我没有看到什么大的进步。

截断整个表非常有必要-立即完成并再次插入所有记录。

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