【问题标题】:MySQL "IN" operator performance on (large?) number of valuesMySQL“IN”运算符在(大?)个值上的性能
【发布时间】:2011-05-29 17:16:03
【问题描述】:

我最近一直在尝试使用 Redis 和 MongoDB,似乎经常会在 MongoDB 或 Redis 中存储 id's 数组。因为我在询问 MySQL IN 运算符,所以我会坚持使用 Redis 来回答这个问题。

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

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

想象一下像 productscategories 表这样简单的东西,您通常可以将它们连接在一起以从某个 获取 products类别。在上面的示例中,您可以看到在 Redis (category:4:product_ids) 中的给定类别下,我从 id 4 的类别中返回所有产品 id,并将它们放在上述 SELECT 查询中的 IN 运算符中。

性能如何?

这是“视情况而定”的情况吗?或者是否有具体的“这是(不)可接受的”或“快”或“慢”,或者我应该添加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-ing 从查询内部将其添加到 25?

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

非常感谢任何建议/反馈!

【问题讨论】:

  • 我不确定您要问什么?一个“id IN(1,2,3, ...3000))”的查询比 3000 个“id = value”的查询快。但是使用“category = 4”的连接会比上述两个都快。
  • 对,但由于一个产品可以属于多个类别,我不能执行“category = 4”。使用 Redis,我将存储属于某个类别的产品的所有 id,然后对其进行查询。我想真正的问题是,与products_categories 的 JOIN 表相比,id IN (1,2,3 ... 3000) 的性能如何。还是你说的是这个?
  • 请小心 MySql stackoverflow.com/questions/3417074/…中的那个错误
  • 当然,这没有理由不应该像任何其他检索索引行的方法一样有效;这仅取决于数据库作者是否对其进行了测试和优化。就计算复杂度而言,我们将在最坏的情况下对IN 子句进行 O(n log N) 排序(这甚至可能在您展示的排序列表上是线性的,具体取决于算法),然后是线性的交点/查找。

标签: mysql sql performance operators


【解决方案1】:

一般来说,如果IN 列表变得太大(对于一些定义不明确的“太大”值,通常在 100 或更小的区域内),使用连接会变得更有效,创建一个如果需要,可以使用临时表来保存数字。

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

但是,假设集合中存在间隙,此时最好使用有效值列表(除非间隙的数量相对较少,在这种情况下您可以使用:

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

或者任何差距。

【讨论】:

  • 你能举一个“使用连接,创建临时表”的例子吗?
  • 如果数据集来自接口(多选元素)并且所选数据中存在间隙并且该间隙不是顺序间隙(缺失:457、490、658,..)那么AND id NOT BETWEEN XXX AND XXX 将不起作用,最好坚持使用@David Fells 所写的等效(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
  • 根据我的经验 - 在电子商务网站上工作时,我们必须显示大约 50 个不相关产品 ID 的搜索结果,“1. 50 个单独的查询”与“2. 一个”相比,我们的结果更好在“IN 子句”中使用许多值进行查询。目前我没有任何方法可以证明这一点,除了查询 #2 在我们的监控系统中将始终显示为慢查询,而 #1 将永远不会出现,无论执行的数量是多少数百万……有人有同样的经历吗? (我们也许可以将其与更好的缓存联系起来,或者允许其他查询在查询之间交错......)
  • @Chaim,当然单独的查询并不慢。每个人只需要获取一条记录。探查器不知道一组查询是相关的,需要汇总以进行比较。
【解决方案2】:

我一直在做一些测试,as David Fells says in his answer,优化得很好。作为参考,我创建了一个包含 1,000,000 个寄存器的 InnoDB 表,并使用带有 500,000 个随机数的“IN”运算符进行选择,在我的 MAC 上只需要 2.5 秒;仅选择偶数寄存器需要 0.5 秒。

我遇到的唯一问题是我必须从my.cnf 文件中增加max_allowed_packet 参数。如果没有,就会产生一个神秘的“MYSQL has gone away”错误。

这是我用来进行测试的 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

【讨论】:

  • 为了其他人,我将添加在我的 2013 年末 MBP 上的 VirtualBox (CentOS) 中运行 i7,输出的第三行(与问题相关的行)是: 随机选择 = 500744 时间执行时间 =53.458173036575s.. 53 秒可能是可以容忍的,具体取决于您的应用程序。对于我的用途,不是真的。另外请注意,偶数测试与手头的问题无关,因为它使用模运算符 (%) 和等于运算符 (=) 而不是 IN()
  • 它是相关的,因为它是一种将带有 IN 运算符的查询与没有此功能的类似查询进行比较的方法。可能是你得到的时间更长是因为它是下载时间,因为你的机器是 swapipng 或在另一个虚拟机中工作。
【解决方案3】:

您可以创建一个临时表,您可以在其中放置任意数量的 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);

【讨论】:

  • 最好加入临时表而不是使用子查询
  • @loopkin 你能解释一下你将如何使用连接和子查询来做到这一点吗?
  • @jeffSolomon SELECT products.id, name, price FROM products JOIN tmp_IDs on products.id = tmp_IDs.ID;
  • 这个答案!是我一直在寻找的,对于长注册来说非常非常快
  • 非常感谢,伙计。它的运行速度非常快。
【解决方案4】:

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

在我最近解决的案例中,我有两个 where 子句,一个有 2,50 个参数,另一个有 3,500 个参数,查询包含 4000 万条记录的表。

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

根据我的经验,曾为 MySQL 和 Oracle 工作过。

【讨论】:

  • 我没有明白你的意思“通过使用 IN 语句的子查询(将参数放在他们自己的索引表中)”。你的意思是我们应该使用“WHERE ID IN(SELECT id FROM xxx)”而不是“WHERE ID IN(1,2,3)”?
  • 同意istiyak,因为你的说法不明确
  • @ManishGupta 抱歉不清楚,但是我认为这就是我的意思 - 将所有值放入索引表中,并将 SELECT 语句作为子查询添加到 IN 语句中。很难记住,因为这是几年前的事了。
【解决方案5】:

IN 很好,并且优化得很好。确保在索引字段上使用它就可以了。

它在功能上等同于:

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

就数据库引擎而言。

编辑:请注意此答案写于 2011 年,请参阅此答案的 cmets 讨论最新的 MySQL 功能。

【讨论】:

  • 不是真的。我使用 IN clouse 从数据库中获取 5k 记录。 IN clouse 包含 PK 列表,因此相关列被索引并保证是唯一的。 EXPLAIN 说,执行全表扫描,而不是使用“fifo-queue-alike”风格的 PK 查找。
  • 在 MySQL 上,我不相信它们是“功能等效”IN 使用优化以获得更好的性能。
  • 乔希,答案是从 2011 年开始的 - 我敢肯定从那时起情况已经发生了变化,但在那天 IN 被完全转换为一系列 OR 语句。
  • 这个答案不正确。来自高性能 MySQL:在 MySQL 中并非如此,它对 IN( ) 列表中的值进行排序并使用快速二进制搜索来查看值是否在列表中。这是列表大小的 O(log n),而等效的一系列 OR 子句是列表大小的 O(n)(即,对于大型列表来说要慢得多)。
  • 伯特 - 是的。这个答案已经过时了。欢迎提出修改建议。
【解决方案6】:

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

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

【讨论】:

  • 我知道这是 7 年过去了,但这个答案的问题只是它是基于有根据的猜测的评论。
猜你喜欢
  • 2016-12-22
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
  • 2021-08-21
  • 2017-03-14
  • 1970-01-01
相关资源
最近更新 更多