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_packet参数。 如果没有,会产生一个神秘的“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显着慢。