He estado experimentando con Redis y MongoDB últimamente y parece que a menudo hay casos en los que almacenaría una matriz de ID en MongoDB o Redis. Me quedaré con Redis para esta pregunta, ya que estoy preguntando sobre el operador MySQL IN .
Me preguntaba qué tan eficiente es enumerar una gran cantidad (300-3000) de identificadores dentro del operador IN, que se vería así:
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
Imagine algo tan simple como una tabla de productos y categorías a la que normalmente podría UNIRSE para obtener los productos de una categoría determinada . En el ejemplo anterior, puede ver que en una categoría determinada en Redis ( category:4:product_ids
) devuelvo todos los identificadores de producto de la categoría con el identificador 4 y los coloco en la SELECT
consulta anterior dentro del IN
operador.
¿Qué rendimiento tiene esto?
¿Es esta una situación de "depende"? ¿O hay un concreto "esto es (in) aceptable" o "rápido" o "lento" o debería agregar un LIMIT 25
, o eso no ayuda?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25
¿O debería recortar la matriz de identificadores de productos devueltos por Redis para limitarlos a 25 y solo agregar 25 identificadores a la consulta en lugar de 3000 y LIMIT
ponerlos a 25 desde dentro de la consulta?
SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)
¡Cualquier sugerencia / comentario es muy apreciado!
fuente
id IN (1,2,3 ... 3000)
rendimiento en comparación con la tabla JOIN deproducts_categories
. ¿O es eso lo que estabas diciendo?IN
cláusula (esto podría incluso ser lineal en una lista ordenada como la que muestra, según el algoritmo), y luego intersección lineal / búsquedas .Respuestas:
En términos generales, si la
IN
lista se vuelve demasiado grande (para algún valor mal definido de 'demasiado grande' que generalmente está en la región de 100 o menos), se vuelve más eficiente usar una combinación, creando una tabla temporal si es necesario para contener los números.Si los números son un conjunto denso (sin espacios, como sugieren los datos de la muestra), entonces puede hacerlo aún mejor
WHERE id BETWEEN 300 AND 3000
.Sin embargo, es de suponer que hay lagunas en el conjunto, en cuyo caso puede ser mejor ir con la lista de valores válidos después de todo (a menos que las lagunas sean relativamente pocas, en cuyo caso podría usar:
O cualesquiera que sean las lagunas.
fuente
AND id NOT BETWEEN XXX AND XXX
no funcionará y es mejor quédate con el equivalente(x = 1 OR x = 2 OR x = 3 ... OR x = 99)
como escribió @David Fells.He estado haciendo algunas pruebas y, como dice David Fells en su respuesta , está bastante bien optimizado. Como referencia, he creado una tabla InnoDB con 1,000,000 de registros y haciendo una selección con el operador "IN" con 500,000 números aleatorios, solo toma 2.5 segundos en mi MAC; seleccionar solo los registros pares lleva 0,5 segundos.
El único problema que tuve es que tuve que aumentar el
max_allowed_packet
parámetro delmy.cnf
archivo. De lo contrario, se genera un misterioso error "MYSQL se ha ido".Aquí está el código PHP que utilizo para hacer la prueba:
$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>";
Y los 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
fuente
%
) con un operador igual (=
) en lugar deIN()
.Puede crear una tabla temporal donde puede poner cualquier número de ID y ejecutar una consulta anidada. Ejemplo:
CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));
y seleccione:
SELECT id, name, price FROM products WHERE id IN (SELECT ID FROM tmp_IDs);
fuente
De hecho, el uso
IN
con un gran conjunto de parámetros en una gran lista de registros será lento.En el caso que resolví recientemente tenía dos cláusulas where, una con 2,50 parámetros y la otra con 3,500 parámetros, consultando una tabla de 40 Millones de registros.
Mi consulta tomó 5 minutos usando el estándar
WHERE IN
. En su lugar, al usar una subconsulta para la declaración IN (poniendo los parámetros en su propia tabla indexada), reduje la consulta a DOS segundos.Trabajó tanto para MySQL como para Oracle en mi experiencia.
fuente
IN
está bien y bien optimizado. Asegúrese de usarlo en un campo indexado y estará bien.Es funcionalmente equivalente a:
En lo que respecta al motor DB.
fuente
IN
utiliza optimizaciones para un mejor rendimiento.Cuando proporcione muchos valores para el
IN
operador, primero debe ordenarlos para eliminar los duplicados. Al menos lo sospecho. Por lo tanto, no sería bueno proporcionar demasiados valores, ya que la clasificación lleva N log N tiempo.Mi experiencia demostró que dividir el conjunto de valores en subconjuntos más pequeños y combinar los resultados de todas las consultas en la aplicación ofrece el mejor rendimiento. Admito que obtuve experiencia en una base de datos diferente (generalizada), pero lo mismo puede aplicarse a todos los motores. Mi recuento de valores por juego fue 500-1000. Más o menos fue significativamente más lento.
fuente