【问题标题】:SQL UPDATE WHERE IN (List) or UPDATE each individually?SQL UPDATE WHERE IN (List) 还是单独更新?
【发布时间】:2015-10-19 01:04:22
【问题描述】:

我最近正在尽最大努力寻找在 SQL 中运行某些查询的最佳方法,这些查询可能会以多种不同的方式完成。在我的研究中,由于 WHERE IN 的工作方式固有的低效率,我对 WHERE IN 概念感到非常厌恶。

例如:WHERE Col IN (val1, val2, val3)

在我当前的项目中,我正在对大量数据进行更新,我想知道以下哪一项更有效:(或是否存在更好的选择)

UPDATE table1 SET somecolumn = 'someVal' WHERE ID IN (id1, id2, id3 ....);

在上面,ID 列表最多可以有 1.5k 个 ID。

VS

循环遍历代码中的所有 ID,并为每个 ID 运行以下语句:

UPDATE table1 SET somecolumn = 'someVal' WHERE ID = 'theID';

对我来说,前者工作得更好/更快似乎更合乎逻辑,因为要运行的查询更少。也就是说,我不是 100% 熟悉 SQL 的进出以及查询队列的工作原理。

我也不确定在表锁和其他一般性能方面哪个对数据库更友好。

一般信息以防万一,我使用的是 Microsoft SQL Server 2014,主要开发语言是 C#。

非常感谢任何帮助。

编辑:

选项 3:

UPDATE table1 SET somecolumn = 'someVal' WHERE ID IN (SELECT ID FROM @definedTable);

在上面,@definedTable 是一个 SQL '用户定义的表类型',其中的数据通过存储过程作为(在 C# 中)类型 SqlDbType.Structured

人们在问身份证是怎么进来的: ID 在代码中的List<string> 中,用于代码中的其他内容,然后再发送到存储过程。目前,ID 将作为只有一列 (ID) 的“用户定义的表类型”进入存储过程。

我认为将它们放在一个表中可能比让代码连接一个大字符串并将其作为一个看起来像 id1, id2, id3, id4 等的变量吐到 SP 中更好

【问题讨论】:

  • 您是否尝试查看两个查询中哪个更快的执行计划?
  • id1,id2,id3 是从哪里来的?在大多数实际情况下,它们来自另一个表,这是在某些条件下过滤的结果。在这种情况下,您最好加入该表,以获得不错的性能。
  • 如何将这些 ID 传递给 SQL?您的 C# 代码中是否有值列表,或者您是否通过另一个 SQL 查询获得它们?
  • 对于 1.5k 个 ID,可能值得先将它们存储在(临时)表中,然后执行 WHERE ID IN (SELECT ID FROM temptable)
  • 把它们放在一张桌子上肯定更好。你只能有大约 2000 个参数,一旦你有更多的参数,你的 sp 就会中断。但是UDT没有这样的问题。

标签: sql sql-server


【解决方案1】:

我正在使用您的第三个选项,效果很好。

我的存储过程有一个table-valued parameter。另见Use Table-Valued Parameters

在过程中有一个语句,没有循环,就像你说的:

UPDATE table1 SET somecolumn = 'someVal' WHERE ID IN (SELECT ID FROM @definedTable);

最好调用一次过程,而不是 1500 次。一笔交易比 1500 笔交易要好。

如果@definedTable 中的行数超过 10K,我会考虑将其分成 10K 的批次。


您的第一个变体对于IN 子句中的少数值是可以的,但是当您获得非常高的数字(60K+)时,您会看到类似这样的内容,如this answer 所示:

Msg 8623, Level 16, State 1, Line 1 查询处理器用完 内部资源,无法生成查询计划。这是一个罕见的 事件,并且仅适用于极其复杂的查询或查询 引用大量的表或分区。请简化 查询。如果您认为自己错误地收到了此消息, 如需更多信息,请联系客户支持服务。

【讨论】:

  • "如果@definedTable 中的行数超过 10K,我会考虑将其拆分为 10K 的批次。"怎么拆分?
  • @Black,如果我有 30K 行要更新,我会调用存储过程 3 次,并在每次调用表值参数期间将 10K ID 传递给它。实际上,如今有了现代硬件和网络,我可能会开始烦恼可能在 100K 或更多之后的批次。
【解决方案2】:

您的第一个或第三个选项是最好的选择。对于其中任何一个,您都需要table1(id) 上的索引。

一般来说,最好运行一个查询而不是多个查询,因为将数据传入和传出数据库的开销会增加。此外,每次更新都会启动一个事务并提交它——更多的开销。也就是说,除非您要更新数千条记录,否则这可能并不重要。在典型系统上,开销以数百微秒或毫秒为单位。

【讨论】:

    【解决方案3】:

    您绝对不应该使用循环并为每个 ID 发送一个全新的 SQL 语句。在这种情况下,SQL 引擎每次都必须重新编译 SQL 语句并提出执行计划等。

    可能最好的办法是使用占位符制作一个准备好的语句,然后循环遍历您的数据,为每个值执行该语句。然后该语句将保留在数据库引擎的内存中,并且每次调用它时它都会以新值快速执行它,而不是从头开始。

    如果您有一个大型数据库和/或经常运行此数据库,还请确保在该 ID 值上创建索引,否则它将必须对每个值进行全表扫描。

    编辑:

    Perl 伪代码如下所述:

    #!/usr/bin/perl
    use DBI;
    $dbh = DBI->connect('dbi:Oracle:MY_DB', 'scott', 'tiger', { RaiseError => 1, PrintError =>1, AutoCommit => 0 });
    $sth = $dbh->prepare ("UPDATE table1 SET somecolumn = ? WHERE id = ?");
    foreach $tuple (@updatetuples) {
        $sth->execute($$tuple[1], $$tuple[0]);
    }
    $dbh->commit;
    $sth->finish;
    $dbh->disconnect;
    exit (0);
    

    【讨论】:

    • 我认为数据来自您的程序,而不是另一个表。它并不完全清楚。但在那种情况下,我仍然认为这是最好的路线。我不懂 C#,所以这里有一些未经测试的 Perl 伪代码:哦,我想我必须把它放在答案中。
    【解决方案4】:

    我在尝试解决一个非常相似的问题时看到了这篇文章,所以我想分享我的发现。我的答案使用 case 关键字,适用于您尝试对键值对列表运行更新时(而不是尝试将一堆行更新为单个值时)。通常我只会运行更新查询并加入相关表,但我使用的是 SQLite 而不是 MySQL,而且 SQLite 不支持加入的更新查询以及 MySQL。你可以这样做:

    UPDATE mytable SET somefield=( CASE WHEN (id=100) THEN 'some value 1' WHEN (id=101) THEN 'some value 2' END ) WHERE id IN (100,101);

    【讨论】:

      猜你喜欢
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-16
      • 2016-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-05
      相关资源
      最近更新 更多