MySQL“ IN”运算符在(大)数量值上的性能


93

我最近一直在使用Redis和MongoDB进行实验,似乎经常会在MongoDB或Redis中存储ID数组。因为我要询问MySQL IN运算符,所以我会坚持使用Redis 。

我想知道在IN运算符中列出大量(300-3000)id的性能如何,看起来像这样:

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)

想象一下简单的产品类别表,您通常可以将它们结合在一起以从某个类别中获得产品。在上面的示例中,您可以看到在Redis()中的给定类别下,我返回了ID为4的类别中的所有产品ID,并将其放置在运算符内的上述查询中。category:4:product_idsSELECTIN

这表现如何?

这是“取决于情况”的情况吗?还是有一个具体的“不可接受”,“快速”或“缓慢”,或者我应该添加LIMIT 25,还是没有帮助?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25

还是应该修剪Redis返回的产品ID的数组以将其限制为25,并且仅将25个ID添加到查询中而不是添加3000,并LIMIT从查询内部将其添加到25?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)

任何建议/反馈深表感谢!


我不确定您要问什么?一个带有“ id IN(1,2,3,... 3000))的查询比带有“ id = value”的3000个查询要快。但是,使用“ category = 4”进行连接将比上述两个都快。
罗尼斯(Ronnis)2010年

是的,尽管由于产品可以属于多个类别,所以您不能执行“类别= 4”。使用Redis,我将存储属于特定类别的产品的所有ID,然后对其进行查询。我想真正的问题是,id IN (1,2,3 ... 3000)与的JOIN表相比,性能如何products_categories?还是你在说什么?
Michael van Rooijen 2010年


当然,没有理由不应该使它比其他任何检索索引行的方法都高效。它仅取决于数据库作者是否为此进行了测试和优化。在计算复杂度方面,我们将在最坏的情况下对IN子句执行O(n log N)排序(根据您的算法,它甚至可能在您显示的排序列表上是线性的),然后进行线性相交/查找。
jberryman '17

Answers:


39

一般而言,如果IN列表太大(对于某些“过大”的未定义值,通常在100或更小范围内),则使用联接会变得更加高效,并在需要时创建临时表。保留数字。

如果数字是一个密集的集合(没有间隙-样本数据表明),那么使用甚至可以做得更好WHERE id BETWEEN 300 AND 3000

但是,大概在集合中存在缺口,这时最好还是使用有效值列表(除非缺口的数量相对较少,在这种情况下,您可以使用:

WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836

或任何差距。


46
您能否举一个“使用联接,创建临时表”的例子?
杰克

如果数据集来自接口(多选元素),并且所选数据中存在间隙,并且该间隙不是顺序间隙(缺失:457、490、658,..),AND id NOT BETWEEN XXX AND XXX则将无法正常工作,最好坚持使用(x = 1 OR x = 2 OR x = 3 ... OR x = 99)@David Fells撰写的内容。
deepcell

以我的经验-在电子商务网站上工作,我们必须显示约50个不相关的产品ID的搜索结果,“ 1。50个独立查询”比“ 2.一个查询中“ IN条款””。目前,我没有任何方法可以证明这一点,除了查询#2在我们的监视系统中始终显示为慢速查询,而无论执行次数在多少,#1都不会显示。数以百万计的人...有没有同样的经历?(我们可以将其与更好的缓存相关联,或者允许其他查询在查询之间交织...)
Chaim Klar,

24

我一直在做一些测试,正如David Fells在回答中所说的,它已经很好地进行了优化。作为参考,我创建了一个具有1,000,000个寄存器的InnoDB表,并使用带有500,000个随机数的“ IN”运算符进行选择,在我的MAC上仅花费2.5秒。仅选择偶数寄存器需要0.5秒。

我唯一的问题是必须max_allowed_packetmy.cnf文件中增加参数。如果不是,则会生成一个神秘的“ MYSQL已消失”错误。

这是我用来进行测试的PHP代码:

$NROWS =1000000;
$SELECTED = 50;
$NROWSINSERT =15000;

$dsn="mysql:host=localhost;port=8889;dbname=testschema";
$pdo = new PDO($dsn, "root", "root");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$pdo->exec("drop table if exists `uniclau`.`testtable`");
$pdo->exec("CREATE  TABLE `testtable` (
        `id` INT NOT NULL ,
        `text` VARCHAR(45) NULL ,
        PRIMARY KEY (`id`) )");

$before = microtime(true);

$Values='';
$SelValues='(';
$c=0;
for ($i=0; $i<$NROWS; $i++) {
    $r = rand(0,99);
    if ($c>0) $Values .= ",";
    $Values .= "( $i , 'This is value $i and r= $r')";
    if ($r<$SELECTED) {
        if ($SelValues!="(") $SelValues .= ",";
        $SelValues .= $i;
    }
    $c++;

    if (($c==100)||(($i==$NROWS-1)&&($c>0))) {
        $pdo->exec("INSERT INTO `testtable` VALUES $Values");
        $Values = "";
        $c=0;
    }
}
$SelValues .=')';
echo "<br>";


$after = microtime(true);
echo "Insert execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);  
$sql = "SELECT count(*) FROM `testtable` WHERE id IN $SelValues";
$result = $pdo->prepare($sql);  
$after = microtime(true);
echo "Prepare execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);

$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Random selection = $c Time execution time =" . ($after-$before) . "s<br>";



$before = microtime(true);

$sql = "SELECT count(*) FROM `testtable` WHERE id %2 = 1";
$result = $pdo->prepare($sql);
$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Pairs = $c Exdcution time=" . ($after-$before) . "s<br>";

结果:

Insert execution time =35.2927210331s
Prepare execution time =0.0161771774292s
Random selection = 499102 Time execution time =2.40285992622s
Pairs = 500000 Exdcution time=0.465420007706s

为了其他人的缘故,我将添加在运行i2013的2013年末MBP的VirtualBox(CentOS)中运行的输出的第三行(与问题相关的那一行)是:随机选择= 500744时间执行时间= 53.458173036575s .. 53秒可能容忍,具体取决于您的应用程序。就我而言,不是真的。另外,请注意,偶数检验与手头问题无关,因为它使用取模运算符(%)和等号运算符(=)而不是IN()
rinogo

这很重要,因为它是一种将查询与具有类似查询但没有此功能的IN运算符进行比较的方法。您获得的更新时间可能是因为这是下载时间,因为您的计算机是swapipng或在另一台虚拟机上工作。
jbaylina'5

14

您可以创建一个临时表,在其中可以放置任意数量的ID并运行嵌套查询。示例:

CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));

然后选择:

SELECT id, name, price
FROM products
WHERE id IN (SELECT ID FROM tmp_IDs);

6
最好加入临时表而不是使用子查询
scharette

3
@loopkin您能解释一下如何通过连接与子查询进行比较吗?
杰夫·所罗门

3
@jeffSolomon SELECT products.id,名称,价格FROM products将tmp_IDs加入products.id = tmp_IDs.ID;
scharette

这个答案!正是我一直在寻找,非常非常快长登记
的Damián拉斐尔Lattenero

非常感谢你,伙计 它的运行速度非常快。
mrHalfer

4

IN实际上,在较大的记录列表上使用较大的参数集会很慢。

在我最近解决的情况下,我有两个where子句,一个带有2,50个参数的子句,另一个带有3500个参数的子句,查询一个4000万条记录的表。

我的查询使用标准查询花了5分钟WHERE IN。通过对IN语句使用子查询(将参数放入其自己的索引表中),我将查询缩短了两秒钟。

以我的经验为MySQL和Oracle工作。


1
我没有明白“通过对IN语句使用子查询(将参数放入自己的索引表中)”。您是不是要使用“ WHERE ID IN(SELECT id FROM xxx)”而不是“ WHERE ID IN(1,2,3)”?
Istiyak Tailor

4

IN很好,并且经过优化。确保在索引字段上使用它并且一切都很好。

它在功能上等同于:

(x = 1 OR x = 2 OR x = 3 ... OR x = 99)

就数据库引擎而言。


1
没关系 我使用IN Clouse从数据库中获取5k记录。IN clouse包含PK列表,因此相关列被索引并保证是唯一的。EXPLAIN说,全表扫描是使用“类似fifo队列”样式的PK查找来执行的。
Antoniossss16年

在MySQL上,我不认为它们是“功能等效”的IN使用优化以获得更好的性能。
约书亚·品特

1
乔什(Josh),答案是从2011年开始-我确定此后情况已经发生变化,但是在当初,IN被转换为一系列OR语句。
David Fells

1
这个答案是不正确的。来自高性能MySQL:在MySQL中不是这样,MySQL对IN()列表中的值进行排序,并使用快速二进制搜索来查看列表中是否存在值。列表的大小为O(log n),而等效的OR子句系列的列表的大小为O(n)(即,大列表的速度要慢得多)。
Bert

伯特-是的。这个答案已经过时了。随时提出修改建议。
David Fells

-2

当您为IN运算符提供许多值时,它首先必须对其进行排序以删除重复项。至少我怀疑。因此,提供太多的值不是很好,因为排序需要N log N的时间。

我的经验证明,将值的集合切成较小的子集,然后将应用程序中所有查询的结果组合在一起,可以提供最佳性能。我承认我在不同的数据库(普及)上收集了经验,但是同样的情况可能适用于所有引擎。我的每组值计数为500-1000。或多或少明显慢。


我知道这已经有7年了,但是这个答案的问题很简单,这是基于有根据的猜测的评论。
Giacomo1968 '19
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.