MySQL"IN"运算符的性能(大?)数值

Mic*_*jen 79 mysql sql performance operators

我最近一直在试验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)
Run Code Online (Sandbox Code Playgroud)

想象一下像产品类别表这样简单的东西,你通常可以将它们加在一起以获得某个类别产品.在上面的示例中,您可以看到在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
Run Code Online (Sandbox Code Playgroud)

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

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*ler 34

一般来说,如果该IN列表变得太大(对于某些不明确的"过大"即通常在100或更小的范围价值),它变得更加有效地使用一个连接,创建一个临时表,如果需要,所以要拿数字.

如果数字是密集的(没有间隙 - 样本数据表明),那么你可以做得更好WHERE id BETWEEN 300 AND 3000.

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

WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836
Run Code Online (Sandbox Code Playgroud)

或者无论差距如何.

  • 你能举个例子"使用连接,创建临时表"吗? (36认同)
  • @Chaim,当然单独的查询并不慢。每个人只需获取一条记录。探查器不知道一组查询是相关的并且需要聚合以进行比较。 (3认同)
  • 根据我的经验 - 在电子商务网站上工作,我们必须显示约 50 个不相关的产品 ID 的搜索结果,“1. 50 个单独的查询”与“2. 一个在“IN”中包含多个值的查询相比,我们得到了更好的结果条款””。目前我没有任何方法可以证明这一点,除了查询 #2 在我们的监控系统中始终会显示为慢速查询,而查询 #1 永远不会出现,无论执行量如何数百万...有人有同样的经历吗?(我们也许可以将其与更好的缓存联系起来,或者允许其他查询在查询之间交织......) (2认同)

jba*_*ina 20

我一直在做一些测试,正如David Fells在他的回答中所说,它已经得到了很好的优化.作为参考,我创建了一个包含1,000,000个寄存器的InnoDB表,并使用"IN"运算符进行选择,其中包含500,000个随机数,在我的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>";
Run Code Online (Sandbox Code Playgroud)

结果如下:

Insert execution time =35.2927210331s
Prepare execution time =0.0161771774292s
Random selection = 499102 Time execution time =2.40285992622s
Pairs = 500000 Exdcution time=0.465420007706s
Run Code Online (Sandbox Code Playgroud)


Vla*_*tov 11

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

CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));
Run Code Online (Sandbox Code Playgroud)

并选择:

SELECT id, name, price
FROM products
WHERE id IN (SELECT ID FROM tmp_IDs);
Run Code Online (Sandbox Code Playgroud)

  • 最好加入临时表而不是使用子查询 (6认同)
  • @loopkin你能解释一下你如何通过连接和子查询来做到这一点吗? (2认同)
  • @jeffSolomon SELECT products.id,name,price FROM products JOIN tmp_IDs on products.id = tmp_IDs.ID; (2认同)

yoy*_*nno 6

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

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

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

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

  • 我没有明白你的意思“通过使用 IN 语句的子查询(将参数放入它们自己的索引表中)”。您的意思是不是应该使用“WHERE ID IN(1,2,3)”,而应该使用“WHERE ID IN(SELECT id FROM xxx)”? (8认同)

Dav*_*lls 5

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

它在功能上相当于:

(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
Run Code Online (Sandbox Code Playgroud)

就数据库引擎而言。

编辑:请注意这个答案是在 2011 年写的,并查看这个答案的评论讨论最新的 MySQL 功能。

  • 这个答案不正确。来自*高性能 MySQL*:在 MySQL 中并非如此,它对 IN( ) 列表中的值进行排序,并使用快速二分搜索来查看某个值是否在列表中。列表大小为 O(log n),而等效的一系列 OR 子句的列表大小为 O(n)(即,对于大列表来说,速度要慢得多)。 (7认同)