2009-06-23 15 views
22

¿Cómo acelerar select count(*) con group by?
Es demasiado lento y se usa con mucha frecuencia.
Tengo un gran problema usando select count(*) y group by con una tabla que tiene más de 3,000,000 filas.¿Cómo acelerar "seleccionar conteo (*)" con "agrupar por" y "dónde"?

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

relation_title, object_title es VARCHAR. donde relation_title = 'XXXX', que devuelve más de 1,000,000 filas, conduce a los índices en object_title no podría funcionar bien.

+0

¿Podría proporcionar más detalles, por ejemplo? todo el Select y la estructura de la tabla? Otra primera oportunidad: ¿Está utilizando correctamente los índices? – Kosi2801

+0

Agregué algunas posibles soluciones a continuación, pero estoy de acuerdo con Kosi en que la definición de tablas (¡especialmente la longitud de las columnas varchar!) Y las definiciones de índices serían muy útiles para diagnosticar esto. –

+0

¿Las relaciones son una tabla Innodb o MyISAM? –

Respuesta

47

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

(más fácil) - Asegúrese de que tiene el derecho cubriendo índice de

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 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 el total el índice debe escanearse) 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 son lo más pequeño posible

Uno de los retos Potencia con índices varchar en MySQL es que, cuando se procesa una consulta, el declarada tamaño completo de la el campo será arrastrado a la RAM. Por lo tanto, si tiene un varchar (256) pero solo usa 4 caracteres, todavía está pagando el uso de 256 bytes de RAM mientras se procesa la consulta. ¡Ay! Entonces, si puede reducir sus límites de varchar fácilmente, esto debería acelerar sus consultas.

(más duro) - Normalizar

30% de sus filas que tienen un único valor de cadena es un claro grito de normalización en otra tabla lo que no está duplicando cadenas millones de veces. Considere la posibilidad de normalizar en tres tablas y usar ID enteros para unirlas.

En algunos casos, puede normalizar bajo las cubiertas y ocultar la normalización con vistas que coinciden con el nombre de la tabla actual ... entonces solo necesita hacer que sus consultas INSERT/UPDATE/DELETE sean conscientes de la normalización pero puede deja tus SELECT solo.

(el más duro) - Hash sus columnas de cadena y el índice de los hashes

Si los medios que cambian demasiado código normalización, pero puede cambiar su esquema un poco, es posible que desee considerar la creación de hash de 128 bits para sus columnas de cadena (usando el MD5 function). 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á actualizar sus 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 el SELECT para asegurarse de que está haciendo el cálculo a través del índice hash y no tirando en el índice agrupado (necesario para resolver el valor del texto de object_title con el fin para satisfacer la consulta).

Además, si relation_title tiene un tamaño pequeño pero varchar título objeto tiene un tamaño de largo, a continuación, que potencialmente puede desmenuzar única object_title y crear el índice en (relation_title, 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 hash.

También tenga en cuenta que hay interesantes efectos de intercalación/intercalación de hashing, ya que el hash de una cadena en minúscula no es lo mismo que el hash de una letra mayúscula. Por lo tanto, deberá asegurarse de aplicar la canonicalización a las cadenas antes de mezclarlas. En otras palabras, solo hash minúsculas si está en una base de datos insensible a mayúsculas y minúsculas. También es posible que desee recortar espacios desde el principio hasta el final, dependiendo de cómo maneje su DB los espacios iniciales/finales.

+0

El índice de cobertura que Justin menciona aquí es absolutamente la mejor manera de obtener un buen rendimiento de esta consulta. – BradC

+0

Gracias, muy útil – mOna

+0

Un campo CHAR es una longitud fija, y VARCHAR es un campo de longitud variable. Esto significa que los requisitos de almacenamiento son diferentes: un CHAR siempre ocupa la misma cantidad de espacio independientemente de lo que almacene, mientras que los requisitos de almacenamiento para un VARCHAR varían según la cadena específica almacenada. Por lo tanto, hacer que Varchar sea lo más pequeño posible no tendría mucho impacto en el rendimiento. – NPE

0

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

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

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

+0

Pensé que era MS SQL para empezar, pero el póster ha agregado la etiqueta mysql ... –

+0

Tenga en cuenta que la pregunta está etiquetada "mysql" no "mssql". – Kosi2801

+0

sí, 'mysql'. He intentado "forzar índice (primario)" para que mysql no use el índice por sí mismo. Es efectivo, de 20 a 15 s. –

0

Si lo que el tamaño de toda la tabla, debe consultar las tablas del meta o información de esquema (que existen en cada 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 pueda hacer.

10

La indexación de las columnas en la cláusula GROUP BY sería lo primero que se debe intentar, utilizando un índice compuesto. Una consulta de este tipo se puede responder 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 ordenación separada como parte del procesamiento grupal. Sin embargo, el índice ralentizará las actualizaciones de la tabla, así que tenga cuidado con esto si su tabla experimenta actualizaciones pesadas.

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

En general, los índices de mapa de bits serían otra alternativa efectiva, pero MySQL actualmente no los admite, por lo que yo sé.

Una vista materializada sería otro enfoque posible, pero una vez más esto no se admite directamente en MySQL. Sin embargo, si no necesita que las estadísticas COUNT estén completamente actualizadas, puede ejecutar periódicamente una declaración CREATE TABLE ... AS SELECT ... para almacenar en caché manualmente los resultados. 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 mediante desencadenadores. 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 particular.Cada vez que se agrega o se 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 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 un impacto menor en los recursos. Sin embargo, creo que tendrías que tener cuidado con la contención de bloqueo en la tabla de caché.

+1

Las columnas más pequeñas pueden ayudar: si el escaneo de la tabla es inevitable, una tabla más pequeña tomará menos tiempo para escanear. Quizás podría publicar la estructura de la tabla y algunos datos de muestra junto con la consulta exacta. – cheduardo

6

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

  1. Utilice desencadenantes y almacene agregados en una tabla separada. Pros: integridad. Contras: actualizaciones lentas
  2. Utilice las colas de procesamiento. Pros: actualizaciones rápidas. Contras: el estado anterior puede persistir hasta que se procesa la cola, por lo que el usuario puede sentir una falta de integridad.
  3. Separar completamente la capa de acceso de almacenamiento y almacenar agregados en una tabla separada. La capa de almacenamiento tendrá en cuenta la estructura de datos y puede aplicar deltas en lugar de realizar recuentos completos. Por ejemplo, si proporciona una funcionalidad "addObject" dentro, sabrá cuándo se ha agregado un objeto y, por lo tanto, el agregado se vería afectado. Entonces solo haces un update table set count = count + 1. Pros: actualizaciones rápidas, integridad (aunque es posible que desee utilizar un bloqueo en caso de que varios clientes puedan alterar el mismo registro). Contras: usted combina un poco de lógica de negocios y almacenamiento.
+0

+1, ive gota intente ese concepto ... Tengo problemas similares –

1

prueba recuento (myprimaryindexcolumn) y comparar el desempeño de su conteo (*)

2

Veo que algunas personas han preguntado qué motor estaba utilizando para la consulta. Yo recomendaría altamente utiliza MyISAM para los siguientes reasions:

InnoDB - @Sorin Mocanu debidamente identificado que va a hacer una exploración de tabla completa independientemente de índices.

MyISAM - siempre mantiene el recuento de filas actual a la mano.

Por último, como se indica @justin, asegúrese de que tiene el índice de cobertura adecuada:

CREATE INDEX ix_temp ON relations (relation_title, object_title); 
+4

Para su información, el gran beneficio de velocidad de MyISAM para COUNT (*) consultas solo se aplica cuando está contando filas en toda la tabla. Si hay una cláusula WHERE, entonces MyISAM e InnoDB calculan el recuento contando filas en el índice. Consulte http://www.mysqlperformanceblog.com/2006/12/01/count-for-innodb-tables/ para obtener más información. –

0

sugeriría a archivar los datos a menos que haya alguna razón específica para mantenerlo en la base de datos o podría dividir el datos y ejecutar consultas por separado.