Pregunta

¿Cómo acelerar select count (*) con group by ?
Es demasiado lento y se usa con mucha frecuencia.
Tengo un gran problema al usar select count (*) y group by con una tabla que tiene más de 3,000,000 de filas.

select object_title,count(*) as hot_num   
from  relations 
where relation_title='XXXX'   
group by object_title  

relacion_titulo , objeto_titulo es varchar. donde ratio_title = 'XXXX' , que devuelve más de 1,000,000 de filas, conducen a que los índices en object_title no funcionen bien.

¿Fue útil?

Solución

Aquí hay varias cosas que probaría, en orden creciente de dificultad:

(más fácil) - Asegúrese de tener el índice de cobertura correcto

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Esto debería maximizar el rendimiento dado su esquema existente, ya que (¡a menos que su versión del optimizador de mySQL sea realmente tonta!) minimizará la cantidad de E / S necesarias para satisfacer su consulta (a diferencia de si el índice está en el orden inverso donde se debe escanear todo el índice) y cubrirá la consulta para que no tenga que tocar el índice agrupado.

(un poco más difícil): asegúrese de que sus campos varchar sean lo más pequeños posible

Uno de los desafíos de rendimiento con los índices varchar en MySQL es que, al procesar una consulta, el tamaño completo declarado del campo se extraerá en la RAM. Entonces, si tiene un varchar (256) pero solo usa 4 caracteres, todavía está pagando el uso de RAM de 256 bytes mientras se procesa la consulta. ¡Ay! Entonces, si puede reducir fácilmente sus límites de varchar, esto debería acelerar sus consultas.

(más difícil) - Normalizar

El 30% de sus filas que tienen un solo valor de cadena es un claro clamor por normalizar en otra tabla para que no esté duplicando cadenas millones de veces. Considere la normalización en tres tablas y el uso de ID enteros para unirlas.

En algunos casos, puede normalizar debajo de las cubiertas y ocultar la normalización con vistas que coincidan con el nombre de la tabla actual ... luego solo necesita hacer que sus consultas INSERT / UPDATE / DELETE estén al tanto de la normalización, pero puede dejar sus SELECCIONADOS solos.

(más difícil) - Hash sus columnas de cadena e indexe los hashes

Si normalizar significa cambiar demasiado código, pero puede cambiar un poco su esquema, puede considerar crear hashes de 128 bits para sus columnas de cadena (usando función MD5 ). En este caso (a diferencia de la normalización), no tiene que cambiar todas sus consultas, solo los INSERT y algunos de los SELECT. De todos modos, querrás dividir en hash tus campos de cadena y luego crear un índice en los hash, p.

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

Tenga en cuenta que tendrá que jugar con SELECT para asegurarse de que está haciendo el cálculo a través del índice hash y no está tirando del índice agrupado (requerido para resolver el valor de texto real de object_title para satisfacer la consulta ).

Además, si ratio_title tiene un tamaño varchar pequeño pero el título del objeto tiene un tamaño largo, entonces puede potencialmente hash solo object_title y crear el índice en (relacion_titulo, object_title_hash) .

Tenga en cuenta que esta solución solo ayuda si uno o ambos de estos campos es muy largo en relación con el tamaño de los hashes.

También tenga en cuenta que el hash tiene impactos interesantes entre mayúsculas y minúsculas, ya que el hash de una cadena en minúscula no es lo mismo que el hash de una mayúscula. Por lo tanto, deberá asegurarse de aplicar la canonicalización a las cadenas antes de cifrarlas; en otras palabras, solo use hash en minúsculas si está en una base de datos que no distingue entre mayúsculas y minúsculas. También es posible que desee recortar espacios desde el principio o el final, dependiendo de cómo su DB maneje los espacios iniciales / finales.

Otros consejos

Indexar las columnas en la cláusula GROUP BY sería lo primero que se intentaría, utilizando un índice compuesto. Una consulta como esta puede responderse utilizando solo los datos del índice, evitando la necesidad de escanear la tabla. Como los registros en el índice están ordenados, el DBMS no debería necesitar realizar una clasificación separada como parte del procesamiento del grupo. Sin embargo, el índice ralentizará las actualizaciones de la tabla, así que tenga cuidado con esto si su tabla experimenta grandes actualizaciones.

Si usa InnoDB para el almacenamiento de la tabla, las filas de la tabla se agruparán físicamente por el índice de clave principal. Si eso (o una parte inicial) coincide con su clave GROUP BY, eso debería acelerar una consulta como esta porque los registros relacionados se recuperarán juntos. Nuevamente, esto evita tener que realizar una clasificación por separado.

En general, los índices de mapas de bits serían otra alternativa efectiva, pero MySQL actualmente no los admite, que yo sepa.

Una vista materializada sería otro enfoque posible, pero nuevamente esto no es compatible directamente en MySQL. Sin embargo, si no requirió que las estadísticas COUNT estuvieran completamente actualizadas, podría ejecutar periódicamente una instrucción CREATE TABLE ... AS SELECT ... para almacenar en caché los resultados manualmente. Esto es un poco feo ya que no es transparente, pero puede ser aceptable en su caso.

También podría mantener una tabla de caché de nivel lógico utilizando disparadores. Esta tabla tendría una columna para cada columna en su cláusula GROUP BY, con una columna Count para almacenar el número de filas para ese valor de clave de agrupación en particular. Cada vez que se agrega o actualiza una fila en la tabla base, inserte o incremente / disminuya la fila del contador en la tabla de resumen para esa clave de agrupación en particular. Esto puede ser mejor que el enfoque de vista materializada falsa, ya que el resumen almacenado en caché siempre estará actualizado, y cada actualización se realiza de forma incremental y debería tener menos impacto en los recursos. Sin embargo, creo que debería tener cuidado con la contención de bloqueo en la tabla de caché.

Si tiene InnoDB, count (*) y cualquier otra función agregada realizará un escaneo de tabla. Veo algunas soluciones aquí:

  1. Use disparadores y almacene agregados en una tabla separada. Pros: integridad. Contras: actualizaciones lentas
  2. Usar colas de procesamiento. Pros: actualizaciones rápidas. Contras: el estado anterior puede persistir hasta que se procese la cola, por lo que el usuario puede sentir una falta de integridad.
  3. Separe completamente la capa de acceso al almacenamiento y almacene los agregados en una tabla separada. La capa de almacenamiento estará al tanto de la estructura de datos y puede aplicar deltas en lugar de hacer recuentos completos. Por ejemplo, si proporciona un " addObject " dentro de esa funcionalidad, sabrá cuándo se ha agregado un objeto y, por lo tanto, el agregado se vería afectado. Luego, solo hace una conjunto de tablas de actualización count = count + 1 . Pros: actualizaciones rápidas, integridad (es posible que desee utilizar un bloqueo, en caso de que varios clientes puedan alterar el mismo registro). Contras: combina un poco de lógica empresarial y almacenamiento.

Veo que algunas personas han preguntado qué motor estaba utilizando para la consulta. Recomiendo encarecidamente que use MyISAM para las siguientes razones:

InnoDB : @Sorin Mocanu identificó correctamente que realizará un escaneo completo de la tabla independientemente de los índices.

MyISAM : siempre mantiene a mano el recuento de filas actual.

Por último, como dijo @justin, asegúrese de tener el índice de cobertura adecuado:

CREATE INDEX ix_temp ON relations (relation_title, object_title);

prueba  cuenta (myprimaryindexcolumn) y compara el rendimiento con tu cuenta (*)

hay un punto en el que realmente necesitas más RAM / CPU / IO. Es posible que haya golpeado eso para su hardware.

Notaré que generalmente no es efectivo usar índices (a menos que sean cobertura) para consultas que alcanzan más del 1-2% del total de filas en una tabla. Si su consulta grande está haciendo búsquedas de índice y búsquedas de marcadores, podría ser debido a un plan en caché que era solo de una consulta de día total. Intenta agregar en WITH (INDEX = 0) para forzar un escaneo de tabla y ver si es más rápido.

toma esto de: http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4- 0104-47aa-b548-e8428073b6e6 & amp; cat = & amp; lang = & amp; cr = & amp; sloc = & amp; p = 1

Si tiene el tamaño de la tabla completa, debe consultar las tablas meta o el esquema de información (que existen en todos los DBMS que conozco, pero no estoy seguro acerca de MySQL). Si su consulta es selectiva, debe asegurarse de que haya un índice para ella.

AFAIK, no hay nada más que puedas hacer.

Sugeriría archivar datos a menos que exista algún motivo específico para mantenerlos en la base de datos o podría particionar los datos y ejecutar consultas por separado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top