SQL更新状态需要很长时间/大量磁盘使用


8

是的,这听起来像是一个非常笼统的问题,但是我还不能将其范围缩小很多。

所以我在sql批处理文件中有一个UPDATE语句:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID

B有40k条记录,A有4M条记录,它们通过A.B_ID一对一相关,尽管两者之间没有FK。

因此,基本上,我正在为数据挖掘目的预先计算一个字段。尽管我为这个问题更改了表的名称,但是我没有更改该语句,实际上就这么简单。

这需要几个小时才能运行,因此我决定取消所有操作。数据库已损坏,因此我删除了它,恢复了我在运行该语句之前所做的备份,并决定使用游标进一步介绍细节:

DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB 
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id

WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
    RAISERROR(@Msg, 10, 1) WITH NOWAIT

    UPDATE A
    SET A.X = B.X
    FROM A JOIN B ON A.B_ID = B.ID
    WHERE B.ID = @Id

    FETCH NEXT FROM CursorB INTO @Id
END

现在,我可以看到它正在运行,并且带有ID降序的消息。从id = 40k到id = 13大约需要5分钟

然后在ID 13,由于某种原因,它似乎挂起了。除了SSMS之外,该数据库没有任何连接,但实际上并未挂起:

  • 硬盘驱动器连续运行,因此肯定是在做某事(我在Process Explorer中检查了它确实是使用它的sqlserver.exe进程)
  • 我运行了sp_who2,找到了SUSPENDED会话的SPID(70),然后运行了以下脚本:

    从sys.dm_exec_requests中选择* r在r.session_id = t.session_id上加入sys.dm_os_tasks,其中r.session_id = 70

这给了我wait_type,在大多数情况下是PAGEIOLATCH_SH,但实际上有时更改为WRITE_COMPLETION,我猜这是在刷新日志时发生的

  • 日志文件(当我还原数据库时(和ID为13时)为1.6GB)现在为3.5GB

其他可能有用的信息:

  • 表A中B_ID 13的记录数不大(14)
  • 我的同事在她的计算机上没有相同的问题,该数据库的副本(来自几个月前)具有相同的结构。
  • 表A是迄今为止数据库中最大的表
  • 它具有多个索引,并且几个索引视图都使用它。
  • 数据库上没有其他用户,它是本地用户,没有应用程序在使用它。
  • LDF文件的大小不受限制。
  • 恢复模型为SIMPLE,兼容性级别为100
  • Procmon没有给我太多信息:sqlserver.exe从MDF和LDF文件中读取和写入大量内容。

我仍在等待它完成(已经1h30了),但我希望也许有人会给我一些其他操作,我可以尝试解决此问题。

编辑:从procmon日志中添加摘录

15:24:02.0506105    sqlservr.exe    1760    ReadFile    C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf  SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897    sqlservr.exe    1760    WriteFile   C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF    SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal

通过使用DBCC PAGE,它似乎是在读取和写入类似于表A(或其索引之一)的字段,但是对于不同的B_ID,则为13.重建索引?

编辑2:执行计划

因此,我取消了查询(实际上删除了数据库及其文件,然后将其还原了),并检查了执行计划:

UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13

(估计的)执行计划与任何B.ID相同,并且看起来相当简单。WHERE子句在B的非聚集索引上使用索引查找,JOIN在表的两个PK上使用聚集索引查找。A上的聚集索引查找使用并行度(x7),占CPU时间的90%。

更重要的是,实际上是立即执行ID为13的查询。

编辑3:索引碎片

索引的结构如下:

B有一个群集的PK(不是ID字段)和一个非群集的唯一索引,第一个字段是B.ID-第二个索引似乎总是使用。

A具有一个群集的PK(字段无关)。

在A上也有7个视图(均包含AX字段),每个视图都有其自己的集群PK,另一个索引也包含AX字段

视图被过滤(具有不在此等式中的字段),因此我怀疑UPDATE A是否会任何方式使用视图本身。但是它们确实有一个包含AX的索引,因此更改AX意味着要编写7个视图以及包含该字段的7个索引。

尽管预计UPDATE的速度会变慢,但是没有理由为什么一个特定的ID比其他ID长得多。

我检查了所有索引的碎片,所有碎片的索引均<0.1%,除了视图的二级索引(均在25%和50%之间)。所有索引的填充因子似乎都不错,介于90%和95%之间。

我重组了所有二级索引,并重新运行了脚本。

它仍然被挂起,但是在另一个地方:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

以前,消息日志如下所示:

...
(0 row(s) affected)

        Updating A for B_ID=14

(4 row(s) affected)

        Updating A for B_ID=13

这很奇怪,因为这意味着它甚至没有挂在WHILE循环中的同一点。其余的看起来相同:sp_who2中有相同的UPDATE行等待,sqlserver.exe中有相同的PAGEIOLATCH_EX等待类型和相同的HD使用量。

我认为,下一步是删除所有索引和视图,然后重新创建它们。

编辑4:删除然后重建索引

因此,我删除了表上的所有索引视图(其中有7个,每个视图2个索引,包括聚簇的1个)。我运行了初始脚本(没有光标),并且实际上在5分钟内运行了。

所以我的问题源于这些索引的存在。

运行更新后,我重新创建了索引,这花了16分钟。

现在,我知道索引需要花费一些时间来重建,而完成20分钟的任务实际上是可以的。

我仍然不明白,为什么当我不首先删除索引而运行更新时需要花费几个小时,但是当我首先删除它们然后重新创建它们时却需要20分钟。两种方式都应该花费大约同一时间吗?


1
SQL Server错误日志中有任何内容吗?另外,从procmon写入文件的偏移量是多少?您可以除以8,192得到页面,然后使用DBCC PAGE来查看正在写入的内容。
马丁·史密斯

3.5GB看起来是32位Windows操作系统可以处理的最大RAM数量。
tschmit007

@MartinSmith自从我在SSMS SQL Server日志中还原以来,没有任何内容,在Windows事件日志中也没有任何内容
GFK

您在表A上的索引是什么样的(什么列,等等)?他们支离破碎吗?
Stuart Ainsworth

@ tschmit007 Win Server 2008 R2 x64上的SQL 2008 R2 x64开发版。它是在Hyper-V上运行自身的VM(主机也是2008 R2 x64);VM的4.2GB物理内存使用了5GB以上的内存,而4.6GB提交则使用了10GB(最大)内存;主机具有7.2GB物理内存(已使用8GB)和7.8提交(最大16GB)。由于使用高清技术,两台计算机的速度均较慢,但并未阻塞。
GFK 2013年

Answers:


0
  1. 坚持使用UPDATE命令。CURSOR的执行速度会变慢。
  2. 删除/禁用所有索引,包括索引视图的索引。如果您在AX上有外键,请将其删除。
  3. 创建仅包含A.B_ID和另一个B.ID的索引。
  4. 即使您使用的是Simple Recovery模型,最后一个事务在刷新到磁盘之前始终会保留在事务日志中。这就是为什么您需要预增长事务日志并将其设置为更大数量(例如100 MB)的原因。
  5. 另外,将数据文件增长设置为更大的数量。
  6. 确保您有足够的磁盘空间来进一步增长日志和数据文件。
  7. 更新完成后,重新创建/启用在步骤2中删除/禁用的索引。
  8. 如果您不再需要它们,请删除在步骤3中创建的索引。

编辑: 由于我无法评论您的原始帖子,因此我将在这里从Edit 4回答您的问题。AX Index上有7个索引是B树,并且对该字段的每次更新都会导致B树重新平衡。从头开始重建这些索引比每次重新平衡都要快。


对于第1点,请参见我对ik_zelf的回答。游标在那里是出于调查原因,并没有太大影响。我将执行您的其余建议,我认为这就是我要做的全部。如果可行,虽然现在发生了什么,但我仍然没有任何解释……
GFK

您可以为表发布DDL(包括所有索引,约束等)。也许有些事情会降低您的表现,但您却错过了。
bojan

1
删除索引/更新/重建索引是可行的,尽管我不想做一些大胆的事情,但我没有选择余地。谢谢!
GFK

0

要看的一件事是在此过程中的系统资源(内存,磁盘,CPU)。我试图在一项大任务中将700万个单独的行插入到一个表中,并且服务器的挂起方式与您的类似。

原来我的服务器上没有足够的内存来运行此大容量插入作业。在这种情况下,SQL喜欢保留内存而不放手....即使在上述插入命令可能完成或未完成之后。在大型作业中处理的命令越多,占用的内存就越多。快速重启后释放了所述内存。

我要做的就是在运行任务管理器的情况下从头开始此过程。如果内存使用率超过75%,则系统/进程将天文数字冻结的可能性很大。

如果如上所述,您的内存/资源确实受到限制,那么您的选择是将过程分成较小的部分(如果内存使用率很高,则偶尔重新启动),而不是一项繁重的工作,或者升级到具有大量内存的64位服务器。


0

更新方案始终比使用过程更快。

由于您要更新表A中所有行的X列,因此请确保首先将索引放在该行上。还要确保该列上没有活动的触发器和约束之类的东西。

更新索引是一项昂贵的业务,验证约束和执行在其他数据中进行查找的行级触发器也是如此。


我认为那不是重点。我意识到索引记录的更新需要时间,而且我知道,总的来说,部分时间是由于这个原因。但是我希望如此,我也同意:正如我所说,更新99%的行需要5分钟(即使使用游标),但是由于某种原因,一行(并且并不总是一样)需要5h。让我担心的是这种特殊的行为。
GFK 2013年

锁不是您说的问题....文件系统利用率如何达到90%或更高?
ik_zelf 2013年

没有,它的31GB腾出120GB的,所以我认为这是确定
GFK

如果您尝试复制表(如创建表a_copy)作为select * from a,会发生什么?
ik_zelf 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.