对于 SQL Server,如何修复并发并行表更新?

Deb*_*Deb 7 sql-server sql-server-2014

我必须在 150 个表的两个(索引)空列上更新所有记录(添加 Guids),每个表有大约 50k 条记录(使用脚本在 c# 中一次创建 40k 更新并将其发布到服务器)和正好 4 个现有列。

在我的本地机器(16GB 内存、500GB 三星 850、SQL Server 2014、核心 i5)上,当我尝试并行运行 10 个表时,总共需要13 分钟,而如果我运行 5,则该过程仅在1.7 分钟内完成。

我确实知道磁盘级别有些事情很忙,但我需要一些帮助来量化这种巨大的时间差异。

是否有确切的 SQL Server DB 视图可以检查此差异?对于给定的硬件,有没有一种确切的方法可以计算出我可以并行运行多少个表更新?(真正的测试服务器有更多 RAM 和 10k rpm 磁盘)。

任何人都可以指出我可以在 SQL Server 上改进以改进并行运行 10 个表的时间吗?

我已经尝试将自动增长大小从 10MB 增加到 100MB,这改善了磁盘队列长度(从大约 5 到 0.1),但实际上并没有减少那么多的总时间。

我在 stackoverflow 上问了完全相同的问题,但到目前为止没有得到任何有用的答案,所以一些或任何见解/帮助都会非常有帮助。:)

Sol*_*zky 4

鉴于您答案中的代码,您很可能通过执行以下两项更改来提高性能:

  • 使用以下命令开始查询批处理并使用以下BEGIN TRAN命令结束查询批处理COMMIT TRAN

  • 将每批更新的数量减少到 5000 以下,以避免锁升级(通常发生在 5000 个锁时)。试试4500。

执行这两件事应该会减少当前通过执行单独的 DML 语句生成的大量 tran 日志写入和锁定/解锁操作。

例子:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 1;
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 2;
        ...
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 4500;
        COMMIT TRAN;
        ", conn));
Run Code Online (Sandbox Code Playgroud)

更新

这个问题的细节有点稀疏。示例代码仅在答案中显示。

一个令人困惑的地方是,描述提到更新两列,但示例代码仅显示正在更新的一列。我上面的答案是基于代码的,因此它只显示一列。如果确实有两列需要更新,那么这两列应该在同一个UPDATE语句中更新:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;
        UPDATE [TestTable]
        SET    Column5 = 'some unique value',
               ColumnN = 'some unique value'
        WHERE  ID = 1;
        UPDATE [TestTable]
               SET Column5 = 'some unique value',
               SET ColumnN = 'some unique value'
        WHERE  ID = 2;
        ...
        UPDATE [TestTable]
               SET Column5 = 'some unique value',
               SET ColumnN = 'some unique value'
        WHERE  ID = 4500;
        COMMIT TRAN;
        ", conn));
Run Code Online (Sandbox Code Playgroud)

另一个不清楚的问题是“独特”数据来自哪里?该问题提到唯一值是 GUID。这些是在应用层生成的吗?它们是否来自应用程序层知道而数据库不知道的另一个数据源?这很重要,因为根据这些问题的答案,提出以下问题可能是有意义的:

  1. GUID 可以在 SQL Server 中生成吗?
  2. 如果#1 是肯定的,那么是否有任何理由从应用程序代码生成此代码,而不是在 T-SQL 中执行简单的批处理循环?

如果 #1 为“是”,但无论出于何种原因,需要在 .NET 中生成代码,那么您可以使用NEWID()并生成UPDATE适用于行范围的语句,在这种情况下,您不需要 / BEGIN TRAN'COMMIT`,因为每个语句一次可以处理所有 4500 行:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"UPDATE [TestTable]
        SET    Column5 = NEWID(),
               ColumnN = NEWID()
        WHERE  ID BETWEEN 1 and 4500;
        ", conn));
Run Code Online (Sandbox Code Playgroud)

如果#1 为“是”,并且没有真正原因在 .NET 中生成这些更新,那么您可以执行以下操作:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 1;
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 2;
        ...
        UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 4500;
        COMMIT TRAN;
        ", conn));
Run Code Online (Sandbox Code Playgroud)

上面的代码仅在ID值不稀疏或者至少值的间隙不大于 时才有效@BatchSize,这样每次迭代中至少更新 1 行。此代码还假设该ID字段是聚集索引。考虑到所提供的示例代码,这些假设似乎是合理的。

但是,如果这些ID值确实有很大的间隙,或者该ID字段不是聚集索引,那么您可以只测试还没有值的行:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;
        UPDATE [TestTable]
        SET    Column5 = 'some unique value',
               ColumnN = 'some unique value'
        WHERE  ID = 1;
        UPDATE [TestTable]
               SET Column5 = 'some unique value',
               SET ColumnN = 'some unique value'
        WHERE  ID = 2;
        ...
        UPDATE [TestTable]
               SET Column5 = 'some unique value',
               SET ColumnN = 'some unique value'
        WHERE  ID = 4500;
        COMMIT TRAN;
        ", conn));
Run Code Online (Sandbox Code Playgroud)

但是,如果 #1 为“否”并且这些值出于充分原因来自 .NET,例如每个值的唯一值ID已存在于另一个源中,那么您仍然可以通过提供来加快速度(超出我最初的建议)派生表:

conn.Open();
using (SqlCommand cmd = new SqlCommand(
      @"BEGIN TRAN;

        UPDATE tbl
        SET    tbl.Column5 = tmp.Col1,
               tbl.ColumnN = tmp.Col2
        FROM   [TestTable] tbl
        INNER JOIN (VALUES
          (1, 'some unique value A', 'some unique value B'),
          (2, 'some unique value C', 'some unique value D'),
          ...
          (1000, 'some unique value N1', 'some unique value N2')
                   ) tmp (ID, Col1, Col2)
                ON tmp.ID = tbl.ID;

        UPDATE tbl
        SET    tbl.Column5 = tmp.Col1,
               tbl.ColumnN = tmp.Col2
        FROM   [TestTable] tbl
        INNER JOIN (VALUES
          (1001, 'some unique value A2', 'some unique value B2'),
          (1002, 'some unique value C2', 'some unique value D2'),
          ...
          (2000, 'some unique value N3', 'some unique value N4')
                   ) tmp (ID, Col1, Col2)
                ON tmp.ID = tbl.ID;

        COMMIT TRAN;
        ", conn));
Run Code Online (Sandbox Code Playgroud)

我相信可以通过连接的行数限制VALUES是 1000,因此我在一个显式事务中将两个集合分组在一起。您可以使用最多 4 组这些UPDATEs 进行测试,以对每个事务执行 4000 次操作,并保持在 5000 个锁定的限制以下。