MySQL的“IN”操作符性能(数量很多)

最近我一直在尝试使用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的一个给定的类别( category:4:product_ids )中,我返回了id为4的类别中的所有产品ID,并将它们放在IN运算符的上面的SELECT查询中。

这是如何高性能的?

这是一个“取决于”的情况? 还是有一个具体的“这是(不)接受”或“快”或“缓慢”,或者我应该添加一个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) 

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

一般来说,如果IN列表变得太大(对于一些“太大”,通常在100或更小范围内的不明确的值),使用连接变得更有效率,如果需要的话创build临时表是要保存的数字。

如果数字是一个密集的集合(没有差距 – 样本数据build议),那么你可以使用WHERE id BETWEEN 300 AND 3000做得更好。 然而,大概在这个集合中有一些空白,在这一点上最好还是继续使用有效值的列表(除非差距相对较less,在这种情况下,你可以使用: WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836或任何缺口。

我一直在做一些testing,正如大卫·费尔斯(David Fells)所说,这个testing已经非常好了。 作为一个参考,我创build了一个带有1000000个寄存器的InnoDB表,并使用500000个随机数的“IN”运算符进行select,在我的MAC中只需要2.5s。 (只select偶数寄存器需要0.5s)。

我唯一的问题是我不得不从my.cnf文件中增加max_allowed_pa​​cket参数。 如果没有,会产生一个神秘的“MYSQL已经消失”的错误。

这里是我用来做testing的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 

IN很好,而且优化。 确保你在索引字段上使用它,你很好。 就发动机而言,它的function等价于(x = 1 OR x = 2 OR x = 3 … OR x = 99)。

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

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

并select:

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

实际上,使用IN在一大列logging中设置一个大参数将会很慢。

在我最近解决的情况下,我有两个where子句,一个有250个参数,另一个有3500个参数,查询一张有4000万条logging的表。 我的查询花了5分钟使用标准的WHERE IN。 通过使用IN语句的子查询(把参数放在他们自己的索引表中),我把查询减less到了两秒钟。 根据我的经验,为MySQL和Oracle工作。

IN运算符提供很多值时,首先必须对其进行sorting以删除重复项。 至less我怀疑这一点。 所以提供太多的值是不好的,因为sorting需要N日志N次。

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