SQL UPDATE WHERE IN(List)或UPDATE各自单独?

时间:2015-10-19 01:04:23

标签: sql sql-server

我最近在努力寻找在SQL中运行某些查询的最佳方法,这些查询可能会以多种不同的方式完成。在我的研究中,由于其工作原理固有的低效率,我对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);

在上面,@ defineTable是一个SQL'用户定义表类型',其中内部数据以(在C#中)类型SqlDbType.Structured

进入存储过程

人们在问ID是如何进入的: ID在代码中位于List<string>中,用于代码中的其他内容,然后再发送到存储过程。目前,ID作为“用户定义的表格类型”进入存储过程。只有一列(ID&#39; s)。

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

4 个答案:

答案 0 :(得分:9)

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

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

在程序中有一个声明,没有循环,就像你说的那样:

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

最好一次调用该程序一次,超过1500次。最好有一笔交易,而不是1,500笔交易。

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

你的第一个变体在IN子句中的几个值是可以的,但是当你得到非常高的数字(60K +)时,你可以看到类似的东西,如this answer所示:

  

消息8623,级别16,状态1,行1查询处理器用完了   内部资源,无法生成查询计划。这是一种罕见的   事件,仅适用于非常复杂的查询或查询   引用了大量的表或分区。请简化   查询。如果您认为自己错误地收到了此邮件,   有关详细信息,请联系客户支持服务。

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

绝对不应该使用循环并为每个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);

答案 3 :(得分:1)

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

更新mytable SET somefield =(当(id = 100)THEN'某值1'时(ID = 101)THEN'某值2'END时)ID IN(100,101);