Tenho experimentado com Redis e MongoDB recentemente e parece que muitas vezes há casos em que você armazenaria uma matriz de id no MongoDB ou Redis. Vou ficar com o Redis para essa pergunta, já que estou perguntando sobre o operador MySQL IN .
Eu queria saber qual é o desempenho de listar um grande número (300-3000) de ids dentro do operador IN, que seria mais ou menos assim:
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
Imagine algo tão simples como uma tabela de produtos e categorias que você normalmente juntaria para obter os produtos de uma determinada categoria . No exemplo acima, você pode ver que, sob uma determinada categoria no Redis ( category:4:product_ids
), eu retorno todos os ids de produto da categoria com id 4 e os coloco na SELECT
consulta acima dentro do IN
operador.
Quão performante é isso?
Esta é uma situação do tipo "depende"? Ou existe um concreto "isto é (in) aceitável" ou "rápido" ou "lento" ou devo adicionar um LIMIT 25
, ou isso não ajuda?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25
Ou devo cortar a matriz de ids do produto retornada pelo Redis para limitá-la a 25 e apenas adicionar 25 ids à consulta, em vez de 3000 e LIMIT
colocá-la em 25 de dentro da consulta?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)
Qualquer sugestão / feedback é muito apreciada!
fonte
id IN (1,2,3 ... 3000)
desempenho em comparação com a tabela JOIN deproducts_categories
. Ou era isso que você estava dizendo?IN
cláusula (isso pode até ser linear em uma lista classificada como você mostra, dependendo do algoritmo) e, em seguida, interseção / pesquisas lineares .Respostas:
De um modo geral, se a
IN
lista ficar muito grande (para algum valor mal definido de 'muito grande' que normalmente está na região de 100 ou menor), torna-se mais eficiente usar uma junção, criando uma tabela temporária se necessário para conter os números.Se os números forem um conjunto denso (sem lacunas - o que os dados de amostra sugerem), você pode fazer ainda melhor com
WHERE id BETWEEN 300 AND 3000
.No entanto, presumivelmente há lacunas no conjunto, em cujo ponto pode ser melhor ir com a lista de valores válidos depois de tudo (a menos que as lacunas sejam relativamente poucas em número, caso em que você pode usar:
Ou quaisquer que sejam as lacunas.
fonte
AND id NOT BETWEEN XXX AND XXX
não funcionará e é melhor fique com o equivalente(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
como @David Fells escreveu.Tenho feito alguns testes e, como David Fells diz em sua resposta , está muito bem otimizado. Como referência, criei uma tabela InnoDB com 1.000.000 de registros e fazendo um select com o operador "IN" com 500.000 números aleatórios, leva apenas 2,5 segundos no meu MAC; selecionar apenas os registros pares leva 0,5 segundos.
O único problema que tive é que tive que aumentar o
max_allowed_packet
parâmetro domy.cnf
arquivo. Caso contrário, um erro misterioso “MYSQL desapareceu” é gerado.Aqui está o código PHP que uso para fazer o teste:
$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>";
E os resultados:
Insert execution time =35.2927210331s Prepare execution time =0.0161771774292s Random selection = 499102 Time execution time =2.40285992622s Pairs = 500000 Exdcution time=0.465420007706s
fonte
%
) com um operador igual (=
) em vez deIN()
.Você pode criar uma tabela temporária onde pode colocar qualquer número de IDs e executar uma consulta aninhada. Exemplo:
CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));
e selecione:
SELECT id, name, price FROM products WHERE id IN (SELECT ID FROM tmp_IDs);
fonte
Usar
IN
com um grande parâmetro definido em uma grande lista de registros será de fato lento.No caso que resolvi recentemente tinha duas cláusulas where, uma com 2,50 parâmetros e outra com 3,500 parâmetros, consultando uma tabela de 40 milhões de registros.
Minha consulta levou 5 minutos usando o padrão
WHERE IN
. Em vez disso, usando uma subconsulta para a instrução IN (colocando os parâmetros em sua própria tabela indexada), reduzi a consulta para DOIS segundos.Trabalhei para MySQL e Oracle em minha experiência.
fonte
IN
está bom e bem otimizado. Certifique-se de usá-lo em um campo indexado e você está bem.É funcionalmente equivalente a:
No que diz respeito ao motor DB.
fonte
IN
usa otimizações para melhor desempenho.Quando você fornece muitos valores para o
IN
operador, ele primeiro deve classificá-los para remover duplicatas. Pelo menos eu suspeito disso. Portanto, não seria bom fornecer muitos valores, pois a classificação leva N log N tempo.Minha experiência provou que dividir o conjunto de valores em subconjuntos menores e combinar os resultados de todas as consultas no aplicativo oferece melhor desempenho. Admito que acumulei experiência em um banco de dados diferente (Pervasive), mas o mesmo pode se aplicar a todos os motores. Minha contagem de valores por conjunto foi 500-1000. Mais ou menos foi significativamente mais lento.
fonte