SQL:如果不是 CPU 或 IO,什么会降低 INSERT 的速度?

Djo*_*jof 21 performance sql-server-2008 sql-server

我们有一个写大量产品的数据库。我们刚刚购买了一台带有 SSD 的新服务器机器来提供帮助。令我们惊讶的是,插入速度并不比存储速度慢得多的旧机器快。在基准测试期间,我们注意到 SQL Server 进程表现出的 IO 率非常低。

例如,我运行了在此页面上找到的脚本,只是在循环中添加了 BEGIN TRAN 和 COMMIT。我最多只能看到磁盘使用率达到 7Mb/s,而 CPU 几乎没有达到 5%。服务器安装了 64Gb 并使用了 10 个。第一次调用的总运行时间为 2 分 15 秒,后续调用大约需要 1 分钟。数据库正在简单恢复并且在测试期间处于空闲状态。我在每次通话之间放下了桌子。

为什么这么简单的脚本这么慢?硬件几乎没有被使用。专用磁盘基准测试工具和 SQLIO 均表明 SSD 以高达 500Mb/s 的读取和写入速度正确执行。我知道随机写入比顺序写入慢,但我希望像这样的简单插入,对没有聚集索引的表,要快得多。

最终我们的场景要复杂得多,但我觉得我需要先了解一个简单的案例。简而言之,我们的应用程序删除旧数据,然后使用 SqlBulkCopy 将新数据复制到临时表,执行一些过滤,最后根据情况使用 MERGE 和/或 INSERT INTO 将数据复制到最终表。

--> 编辑 1:我按照 Martin Smith 链接的程序进行操作,得到以下结果:

[Wait Type]  [Wait Count] [Total Wait (ms)] [T. Resource Wait (ms)] [T. Signal Wait (ms)]
NETWORK_IO          5008              46735                 46587        148
LOGBUFFER           901               5994                  5977         17
PAGELATCH_UP        40                866                   865          1
SOS_SCHEDULER_YIELD 53279             219                   121          98
WRITELOG            5                 145                   145          0
PAGEIOLATCH_UP      4                 58                    58           0
LATCH_SH            5                 0                     0            0
Run Code Online (Sandbox Code Playgroud)

我觉得很奇怪 NETWORK_IO 花费了大部分时间,考虑到没有要显示的结果,也没有数据可以传输到 SQL 文件以外的任何地方。NETWORK_IO 类型是否包括所有 IO?

--> 编辑 2:我创建了一个 20Gb RAM 磁盘并从那里安装了一个数据库。我在 SSD 上的最佳时间是 48 秒,而 RAM 磁盘则下降到 37 秒。NETWORK_IO 仍然是最大的等待。RAM 磁盘的最大写入速度约为 250Mb/s,而它每秒可以处理数 GB。它仍然没有使用太多 CPU,那么是什么阻碍了 SQL?

Edw*_*and 10

我知道这是一个老问题,但这可能仍然对搜索者有帮助,而且这是一个时不时出现的问题。

您在没有看到任何资源瓶颈的情况下达到性能上限的主要原因是因为您已经达到了在一个会话单线程中可能处理的限制。循环不是并行处理的,但所有插入都是串行完成的。

就我而言,插入 300 万行需要 36 秒。这意味着每行 36/30000000 = 0.000012 秒。这是相当快的。在我的系统上,只需 0.000012 即可完成所有必要的步骤。

让它更快完成的唯一方法是并行启动第二个会话。

如果我并行启动 2 个会话,都执行 1500 万次插入。他们都在 18 秒内完成。我可以横向扩展更多,但我当前的测试设置在两个并行会话中达到了 95% 的 cpu,因此执行 3 会扭曲结果,因为我会遇到 CPU 瓶颈。

如果我开始 2 个同时插入 300 万行的并行会话,它们都在 39 秒内完成。所以现在 39 秒内有 600 万行。

好的,这仍然让我们看到 NETWORK_IO 等待出现。

NETWORK_IO 等待是由于您使用扩展事件来跟踪它们而添加的。在我的情况下,插入需要 36 秒(平均)。当使用扩展事件方式时(来自上面第一个评论中的链接),这是注册的内容:

Wait Type             Wait Count  Total Wait Time (ms) Total Resource Wait Time (ms) Total Signal Wait Time (ms)
NETWORK_IO            3455        68808                68802                         6
PAGEIOLATCH_SH        3           64                   64                            0
PAGEIOLATCH_UP        12          58                   58                            0
WRITE_COMPLETION      8           15                   15                            0
WRITELOG              3           9                    9                             0
PAGELATCH_UP          2           4                    4                             0
SOS_SCHEDULER_YIELD   32277       1                    0                             1
IO_COMPLETION         8           0                    0                             0
LATCH_SH              3           0                    0                             0
LOGBUFFER             1           0                    0                             0
Run Code Online (Sandbox Code Playgroud)

可以看到注册了 68 秒的 NETWORK_IO。但是由于插入循环是一个耗时 36 秒的单线程操作,所以这不可能。(是的,使用了多个线程,但操作是串行的,永远不会并行,因此您不能累积比查询总持续时间更多的等待时间)

如果我不使用扩展事件,而只使用安静实例上的等待统计 DMV(只有我运行插入),我会得到以下信息:

Wait Type                   Wait Count  Total Wait Time (ms)  Total Resource Wait Time (ms) Signal Resource Wait Time (ms)
SOS_SCHEDULER_YIELD             8873                 0.21                                    0.01                                    0.20
PAGEIOLATCH_UP                  3                    0.02                                    0.02                                    0.00
PREEMPTIVE_OS_AUTHENTICATIONOPS 17                   0.02                                    0.02                                    0.00
PAGEIOLATCH_SH                  1                    0.00                                    0.00                                    0.00
Run Code Online (Sandbox Code Playgroud)

因此,您在扩展事件日志中看到的 NETWORK_IO 与您的插入循环无关。(如果你不打开 nocount,你会有大量的异步网络 IO 等待,+1 Martin)

但是我不知道为什么 NETWORK_IO 出现在扩展事件跟踪中。当然,将事件写入异步文件目标会累积 ASYNC_NETWORK_IO,但肯定这一切都是在不同的 SPID 上完成的,然后是我们正在过滤的 SPID。我自己可能会问这个作为一个新问题)


Rem*_*anu 9

通常,您首先查看sys.dm_exec_requests,特别是查看 和wait_timewait_typewait_resource查看您的 INSERT 请求。这将清楚地表明是什么阻止了您的 INSERT。结果将表明是否是锁争用、文件增长事件、日志刷新等待、分配争用(表现为 PFS 页面闩锁争用)等。一旦您进行测量,请相应地更新您的问题。我强烈建议您在继续之前立即停止并阅读等待和队列故障排除方法。