2009-02-04 25 views
34

estoy tratando de encontrar la manera de optimizar una consulta muy lento en MySQL (No diseñé esto):"SELECT COUNT (*)" es lento, incluso con cláusula where

SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'; 
+----------+ 
| COUNT(*) | 
+----------+ 
| 3224022 | 
+----------+ 
1 row in set (1 min 0.16 sec) 

Comparando que a un recuento completo:

select count(*) from change_event; 
+----------+ 
| count(*) | 
+----------+ 
| 6069102 | 
+----------+ 
1 row in set (4.21 sec) 

La declaración explicar no me ayuda aquí:

explain SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'\G 
*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: me 
     type: range 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 8 
      ref: NULL 
     rows: 4120213 
     Extra: Using where; Using index 
1 row in set (0.00 sec) 

OK, todavía piensa que necesita cerca de 4 millones de entradas para contar, bu ¡Podría contar líneas en un archivo más rápido que eso! No entiendo por qué MySQL tarda tanto.

Ésta es la definición de la tabla:

CREATE TABLE `change_event` (
    `change_event_id` bigint(20) NOT NULL default '0', 
    `timestamp` datetime NOT NULL, 
    `change_type` enum('create','update','delete','noop') default NULL, 
    `changed_object_type` enum('Brand','Broadcast','Episode','OnDemand') NOT NULL, 
    `changed_object_id` varchar(255) default NULL, 
    `changed_object_modified` datetime NOT NULL default '1000-01-01 00:00:00', 
    `modified` datetime NOT NULL default '1000-01-01 00:00:00', 
    `created` datetime NOT NULL default '1000-01-01 00:00:00', 
    `pid` char(15) default NULL, 
    `episode_pid` char(15) default NULL, 
    `import_id` int(11) NOT NULL, 
    `status` enum('success','failure') NOT NULL, 
    `xml_diff` text, 
    `node_digest` char(32) default NULL, 
    PRIMARY KEY (`change_event_id`), 
    KEY `idx_change_events_changed_object_id` (`changed_object_id`), 
    KEY `idx_change_events_episode_pid` (`episode_pid`), 
    KEY `fk_import_id` (`import_id`), 
    KEY `idx_change_event_timestamp_ce_id` (`timestamp`,`change_event_id`), 
    KEY `idx_change_event_status` (`status`), 
    CONSTRAINT `fk_change_event_import` FOREIGN KEY (`import_id`) REFERENCES `import` (`import_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

Versión:

$ mysql --version 
mysql Ver 14.12 Distrib 5.0.37, for pc-solaris2.8 (i386) using readline 5.0 

¿Hay algo obvio que me falta? (Sí, ya he intentado con "SELECT COUNT (change_event_id)", pero no hay diferencia de rendimiento).

+0

¿Qué tal si intentas algo como ... SELECCIONAR CUENTA (*) DE change_event me WHERE change_event_id> 0; ¿Afecta el rendimiento? –

+0

ovid: si puede, agregue el resultado de 'SHOW INDEX FROM change_event' – Alnitak

Respuesta

39

InnoDB utiliza claves primarias en clúster, por lo que la clave principal se almacena junto con la fila de las páginas de datos, no en las páginas de índice por separado. Para hacer un escaneo de rango, aún debe escanear todas las filas potencialmente anchas en las páginas de datos; tenga en cuenta que esta tabla contiene una columna de TEXTO.

Dos cosas que me gustaría probar:

  1. optimize table plazo. Esto asegurará que las páginas de datos estén almacenadas físicamente en orden ordenado. Esto podría acelerar un escaneo de rango en una clave primaria agrupada.
  2. crea un índice no primario adicional solo en la columna change_event_id. Esto almacenará una copia de esa columna en páginas de índice que será mucho más rápido de escanear. Después de crearlo, verifique el plan de explicación para asegurarse de que está usando el nuevo índice.

(También es probable que quieren hacer el bigint columna change_event_id sin firmar si es a aumentar desde cero)

+5

La "tabla de optimización" no ayudó mucho, pero el índice redundante resolvió la problema ¡Gracias! – Ovid

+12

Esta es la primera vez que veo a alguien sugerir crear un índice redundante en una columna PRIMARY KEY como un hack de rendimiento en MySQL. Estoy muy interesado en los detalles de por qué esto funciona y los tipos de consultas para las que es útil. ¿Tiene algún enlace para seguir leyendo sobre el tema? –

+0

'OPTIMIZE TABLE' es raramente de uso, especialmente en tablas InnoDB. Cualquier mejora puede ser porque cargó recientemente toda la tabla en el caché. –

1

Ejecute "analyze table_name" en esa tabla; es posible que los índices ya no sean óptimos.

A menudo puede decir esto ejecutando "show index from table_name". Si el valor de cardinalidad es NULL, debe forzar el reanálisis.

+0

"analizar tabla change_event" no tuvo impacto en el rendimiento. Gracias, sin embargo. – Ovid

+0

¿hizo más simple el "conteo de selección (*)" más rápido? Acabo de probar con una tabla MyISAM de 110M. "select count (*)" fue instantáneo.Seleccionar el recuento de ~ la mitad de la tabla tomó 2m48 la primera vez, y 27s la segunda vez. – Alnitak

+2

MyISAM tiene características de rendimiento radicalmente diferentes de InnoDB. Esto se debe a que MyISAM bloquea el nivel de la tabla y, de hecho, solo tiene una transacción a la vez. InnoDB se comporta de manera muy diferente bajo las cubiertas. – Ovid

3

Compruebe para ver qué tan fragmentados son sus índices. En mi empresa tenemos un proceso de importación nocturno que destruye nuestros índices y con el tiempo puede tener un profundo impacto en las velocidades de acceso a los datos. Por ejemplo, tuvimos un procedimiento SQL que tardó 2 horas en ejecutarse un día después de la fragmentación de los índices en 3 minutos. usamos SQL Server 2005 para buscar un script que pueda verificar esto en MySQL.

actualización: Mira este enlace: http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html

+0

Aquí hay un enlace http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html mejor de las suertes con todo –

+0

Es posible que desee poner ese enlace en su respuesta? – MiniQuark

5

me he encontrado un comportamiento similar antes con bases de datos de geolocalización IP. Pasado cierto número de registros, la capacidad de MySQL de obtener alguna ventaja de los índices para las consultas basadas en rango aparentemente se evapora. Con los DB de geolocalización, lo manejamos segmentando los datos en fragmentos que eran lo suficientemente razonables para permitir que se utilizaran los índices.

+0

¡Qué desagradable solución! No obstante, lo traje más temprano y salvo algún arreglo extraña configuración u otra solución, podríamos vernos obligados a seguir este camino :( – Ovid

+0

Esta es una gran solución que respete un principio básico de soluciones informáticas: programación en-el-grande es cualitativamente diferente de la programación en-el-pequeño. en el caso de las bases de datos, los planes de acceso y el uso de índices cambia dramáticamente a medida que aumenta el tamaño últimos ciertos umbrales. –

+0

me encontré con un problema similar con la base de datos de geolocalización, y después de varios intentos de optimización como la indexación , partición, etc. Acabo de darle la oportunidad de dividir las tablas grandes en un conjunto de datos más pequeño, que finalmente resultó aceptable en términos de rendimiento. – shashi009

0

Crearía una tabla de "contadores" y agregaría los activadores "crear fila"/"eliminar fila" a la tabla que está contando. Los activadores deberían aumentar/disminuir los valores de conteo en la tabla de "contadores" en cada inserción/eliminación, por lo que no tendrá que calcularlos cada vez que los necesite.

También puede lograr esto en el lado de la aplicación almacenando en el caché los contadores, pero esto implicará borrar el "contador de caché" en cada inserción/eliminación.

Por alguna referencia a echar un vistazo a este http://pure.rednoize.com/2007/04/03/mysql-performance-use-counter-tables/

+0

Excepto que necesitamos recuentos en rangos, por lo que no funciona una gestión de recuentos mediante desencadenadores (a menos que te he entendido mal). – Ovid

14

Estas son algunas cosas que me ha sugerido:

  • Cambie la columna de una "bigint" a un "int unsigned". ¿Realmente esperas tener más de 4.200 millones de registros en esta tabla? Si no, estás perdiendo espacio (y tiempo) en el campo extra ancho. Los índices de MySQL son más eficientes en tipos de datos más pequeños.

  • Ejecute el comando "OPTIMIZE TABLE" y vea si su consulta es más rápida después.

  • También puede considerar partitioning your table de acuerdo con el campo ID, especialmente si los registros anteriores (con valores de ID más bajos) se vuelven menos relevantes con el tiempo. Una tabla particionada a menudo puede ejecutar consultas agregadas más rápido que una enorme tabla sin particiones.


EDIT:

Mirando más de cerca en esta mesa, se ve como una tabla de estilo de registro, donde se insertan filas pero nunca modificadas.

Si es así, es posible que no necesite toda la seguridad transaccional proporcionada por el motor de almacenamiento InnoDB, y es posible que pueda salirse con switching to MyISAM, que es considerablemente más eficiente en consultas globales.

+1

Dado que tenemos números como "1212281603783397", creo que ya desborda "int unsigned" (es una marca de tiempo de alta resolución). "OPTIMIZE TABLE" no tuvo ningún impacto en el rendimiento :( No es mucho más lento MyISAM con cláusulas "where" ya que necesita hacer un escaneo de tabla? También, perderíamos nuestra restricción FK – Ovid

+0

¿Por qué usar una marca de tiempo para su clave principal, si ya tiene un campo de marca de tiempo? Además, ¿qué sucede si dos eventos suceden en el mismo instante? Si yo fuera usted, usaría un campo de incremento automático simple para la clave. – benjismith

+0

La cláusula WHERE doesn ' t necesariamente causa una exploración de tabla completa. Para una consulta simple (igual, menor que, mayor que, etc.) en una columna indexada, el optimizador de consultas usa el índice para buscar páginas relevantes, y luego solo escanea esas páginas. sería necesario si estuvieras haciendo una matemática de fecha o subcadenas. – benjismith

1

MySQL dice "Usar donde" primero, ya que necesita leer todos los registros/valores de los datos de índice para contarlos realmente. Con InnoDb, también trata de "agarrar" ese rango de registro de 4 mil para contarlo.

Es posible que tenga que experimentar con diferentes niveles de aislamiento de transacción: http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-uncommitted

y ver cuál es mejor.

Con MyISAM sería simplemente rápido, pero con un modelo de escritura intenso se producirán problemas de bloqueo.