2012-06-11 8 views
28

Tengo una tabla con más de 100 millones de filas en Innodb.Rendimiento de recuento Mysql en tablas muy grandes

Tengo que saber si hay más de 5000 filas donde la clave externa = 1. No necesito el número exacto.

Hice algunas pruebas:

SELECT COUNT(*) FROM table WHERE fk = 1 => 16 segundos
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16 segundos
SELECT primary FROM table WHERE fk = 1 => 0,6 segundos

voy a tener una red más grande y tiempo de tratamiento pero puede ser una sobrecarga de 15.4 segundos!

¿Tiene una mejor idea?

Gracias

Editar: [observaciones pertinentes del Agregado OP]

que probé SELECT SQL_NO_CACHE COUNT (FK) FROM tabla WHERE fk = 1 pero tardó 25 segundos

Mysql se ajustaba para una Innodb con Mysql Tuner.

CREATE TABLE table (pk bigint(20) NOT NULL AUTO_INCREMENT, 
fk tinyint(3) unsigned DEFAULT '0', 
PRIMARY KEY (pk), KEY idx_fk (fk) USING BTREE) 
ENGINE=InnoDB AUTO_INCREMENT=100380914 DEFAULT CHARSET=latin1 

DB cosas:

'have_innodb', 'YES' 'ignore_builtin_innodb', 'OFF' 'innodb_adaptive_hash_index', 'ON'  
'innodb_additional_mem_pool_size', '20971520' 'innodb_autoextend_increment', '8' 
'innodb_autoinc_lock_mode', '1' 'innodb_buffer_pool_size', '25769803776' 
'innodb_checksums', 'ON' 'innodb_commit_concurrency', '0', 
'innodb_concurrency_tickets', '500' 'innodb_data_file_path', 
'ibdata1:10M:autoextend' 'innodb_data_home_dir', '', 'innodb_doublewrite', 'ON'  
'innodb_fast_shutdown', '1' 'innodb_file_io_threads', '4' 
'innodb_file_per_table', 'OFF', 'innodb_flush_log_at_trx_commit', '1' 
'innodb_flush_method', '' 'innodb_force_recovery', '0' 'innodb_lock_wait_timeout', '50' 
'innodb_locks_unsafe_for_binlog', 'OFF' 'innodb_log_buffer_size', '8388608' 
'innodb_log_file_size', '26214400' 'innodb_log_files_in_group', '2' 
'innodb_log_group_home_dir', './' 'innodb_max_dirty_pages_pct', '90'  
'innodb_max_purge_lag', '0' 'innodb_mirrored_log_groups', '1' 'innodb_open_files', 
'300' 'innodb_rollback_on_timeout', 'OFF' 'innodb_stats_on_metadata', 'ON' 
'innodb_support_xa', 'ON' 'innodb_sync_spin_loops', '20' 'innodb_table_locks', 'ON' 
'innodb_thread_concurrency', '8' 'innodb_thread_sleep_delay', '10000'  
'innodb_use_legacy_cardinality_algorithm', 'ON' 

actualización '15: que utiliza el mismo método hasta ahora con 600 millones de filas y 640 000 nuevas filas por día. Todavía funciona bien.

+0

el recuento iría más rápido si selecciona una columna en 'COUNT()', como tal: 'SELECT COUNT (fk) FROM tabla DONDE fk = 1' – ClydeFrog

+0

echa un vistazo a [este sitio web] (http : //www.mysqlperformanceblog.com/2007/04/10/count-vs-countcol/) para más información – ClydeFrog

+4

@ClydeFrog: ¿De verdad? De acuerdo con [el manual] (http://dev.mysql.com/doc/en/group-by-functions.html#function_count), * 'COUNT (*)' está optimizado para regresar muy rápido si el 'SELECT' recupera de una tabla, no se recuperan otras columnas, y no hay una cláusula 'WHERE' *. De hecho, el blog al que vinculó sugiere que 'COUNT (*)' es más rápido que 'COUNT (columna)'. – eggyal

Respuesta

2

Finalmente, el más rápido fue consultar las primeras X filas usando C# y contar el número de filas.

Mi aplicación está tratando los datos en lotes. La cantidad de tiempo entre dos lotes están en función del número de filas que necesitan ser tratados

SELECT pk FROM table WHERE fk = 1 LIMIT X 

me dieron el resultado en 0,9 segundos.

¡Gracias a todos por sus ideas!

+3

No veo cómo contó el número de filas. ¿Te importa agregar ese código también? – nischayn22

+0

Mi aplicación está tratando los datos en lotes. La cantidad de tiempo entre dos lotes depende de la cantidad de filas que deben tratarse – hotips

1

Si está utilizando PHP puede hacer mysql_num_rows en el resultado obtenido de SELECT primary FROM table WHERE fk = 1 => 0.6 seconds, creo que será eficiente.

Pero depende de qué lenguaje del lado del servidor que está utilizando

+0

C# con el último controlador oficial. Creo que el controlador da un cursor a los datos. Entonces puedo tener el número de filas sin tener que recuperar todo el conjunto de datos. – hotips

+0

@si2w Hay 2 maneras de obtener datos del servidor: 'mysql_store_result()' donde el conjunto de resultados completo se envía al cliente y usted puede contarlo, y 'mysql_use_result()' donde se envían los datos si es necesario, pero todos los datos deben ser obtenidos antes de emitir otros comandos. – glglgl

+0

No se puede confirmar esta respuesta. En mi caso, el 'COUNT()' dura 1.6s y con un SELECT '' mysql_num_rows' no recupera los datos de sus 1.8s. – mgutt

16

Usted no parece interesado en el recuento real por lo que le daría prueba:

SELECT 1 FROM table WHERE fk = 1 LIMIT 5000, 1 

Si se devuelve una fila, usted tiene 5000 y más registros. Supongo que la columna fk está indexada.

+0

Esto es interesante. ¿Has probado esta solución y funciona bien? –

+1

@ypercube: verifiqué datos ficticios con filas de 3M, sin índice en fk y obtuve resultados consistentemente en <1s (la primera ejecución fue ~ 3s). Esta consulta depende en gran medida de la distribución de los datos para YMMV. –

18

mesas de venta libre o otro mecanismo de almacenamiento en caché es la solución:

InnoDB no mantiene una cuenta interna de filas en una tabla porque las transacciones concurrentes pueden “ver” un número diferente de filas al mismo tiempo. Para procesar una instrucción SELECT COUNT (*) FROM t, InnoDB escanea un índice de la tabla, lo que lleva algún tiempo si el índice no está completamente en el grupo de búferes. Si su tabla no cambia a menudo, usar la caché de consultas MySQL es una buena solución. Para obtener un recuento rápido, debe usar una tabla contraria que cree usted mismo y dejar que la aplicación la actualice de acuerdo con los insertos y la elimine. Si un recuento aproximado de filas es suficiente, se puede usar SHOW TABLE STATUS. Ver Section 14.3.14.1, “InnoDB Performance Tuning Tips”.

+0

Tengo una condición donde condición => mostrar el estado de la tabla no será de ayuda. Tengo 400 000 nuevas filas cada día ... ¡Tengo suerte! – hotips

+0

@ si2w No quise aconsejarte usando 'SHOW TABLE STATUS'. Le conté sobre las tablas de conteo y el almacenamiento en caché. – scriptin

+0

Gracias @ Dmitry Scriptin! – hotips

0

Si no está interesado en saber el número de filas y lo que desea es probar la cuenta en contra de algún valor, puede utilizar la secuencia de comandos estándar de abajo:

SELECT 'X' 
FROM mytable 
WHERE myfield='A' 
HAVING COUNT(*) >5 

Esto devolverá una sola fila o ninguna fila en absoluto, dependiendo de si se cumple la condición.

Este script cumple con ANSI y se puede ejecutar completamente sin evaluar el valor completo de COUNT (*). Si MySQL implementó la optimización para dejar de evaluar filas después de que se cumpla alguna condición (realmente espero que lo haga), obtendrá una mejora en el rendimiento. Desafortunadamente no puedo probar este comportamiento porque no tengo una gran base de datos MySQL disponible. Si hace esta prueba, comparta el resultado aquí :)

+2

es más lento que el conteo normal ... ¡Gracias por la idea! – hotips

+0

Gracias por publicar los resultados, @ si2w! Sin embargo, es decepcionante que el motor MySQL no implemente esta (¿simple?) Optimización. –

+0

Obviamente no ... – hotips

2

Tengo que agregar otra respuesta - Tengo muchas correcciones/adiciones a los comentarios y respuestas hasta el momento.

Para MyISAM, SELECT COUNT(*) sin WHERE ha sido reconocido, muy rápido. Todas las demás situaciones (incluido el InnoDB en la pregunta) deben contar a través del BTree de datos o del BTree de un índice para obtener la respuesta. Entonces necesitamos ver cuánto contar a través de.

InnoDB almacena en caché los bloques de datos e índices (16 KB cada uno). Pero cuando los datos de la tabla o el índice BTree son más grandes que innodb_buffer_pool_size, se garantiza que golpearán el disco. Golpear el disco es casi siempre la parte más lenta de cualquier SQL.

La Caché de consultas, cuando está involucrada, normalmente da como resultado tiempos de consulta de aproximadamente 1 milisegundo; esto no parece ser un problema con ninguno de los tiempos citados. Entonces no voy a detenerme en eso.

Pero ... Runing la consulta misma dos veces en una fila se presentan a menudo:

  • Primera carrera: 10 segundos
  • segunda pasada: 1 segundo

Este es sintomático que la primera ejecución tenga que buscar la mayoría de los bloques del disco, mientras que la segunda lo encontró todo en la RAM (la buffer_pool). Sospecho que algunos de los tiempos enumerados son falsos porque no se dio cuenta de este problema de almacenamiento en caché. (16 segundos frente a 0,6 segundos puede ser explicado por esto.)

Voy a arpa en "golpes de disco" o "bloques necesarios para ser tocado" como el real métrica de que SQL es más rápido.

COUNT(x) comprueba x para IS NOT NULL antes de contar. Esto agrega una pequeña cantidad de procesamiento, pero no cambia la cantidad de hits en el disco.

La tabla ofrecida tiene una PK y una segunda columna. Me pregunto si esa es la tabla real ?? Se hace una diferencia -

  • Si el optimizador decide leer los datos- es decir, escanear en PRIMARY KEY fin - se va a leer los datos BTree, que es por lo general (pero no en este cojo ejemplo) mucho más amplio que el índice secundario BTrees.
  • Si el Optimizador decide leer un índice secundario (pero no necesita hacer una clasificación), habrá menos bloques para tocar. Por lo tanto, más rápido.

Comentarios sobre las consultas originales:

SELECT COUNT(*) FROM table WHERE fk = 1 => 16 seconds 
    -- INDEX(fk) is optimal, but see below 
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16 seconds 
    -- the LIMIT does nothing, since there is only one row in the result 
SELECT primary FROM table WHERE fk = 1 => 0.6 seconds 
    -- Again INDEX(fk), but see below 

WHERE fk = 1 ruega por INDEX(fk, ...), preferiblemente justo INDEX(fk). Tenga en cuenta que en InnoDB, cada índice secundario contiene una copia del pk. Es decir, INDEX(fk) es efectivamente INDEX(fk, primary). Por lo tanto, la tercera consulta puede usar eso como "cobertura" y no es necesario tocar los datos.

Si la tabla es realmente solo las dos columnas, entonces probablemente el índice secundario BTree será más grueso que los datos BTree. Pero en tablas realistas, el índice secundario será más pequeño. Por lo tanto, un escaneo de índice será más rápido (menos bloques para tocar) que un escaneo de tabla.

La tercera consulta también ofrece un gran conjunto de resultados; esto podría hacer que la consulta demore mucho tiempo - pero no se incluirá en el "tiempo" indicado; es tiempo de red, no tiempo de consulta.

innodb_buffer_pool_size = 25,769,803,776 Supongo que la tabla y su índice secundario (del FK) son cada uno de aproximadamente 3-4GB. Por lo tanto, cualquier momento podría primero tiene que cargar muchas cosas. Entonces una ejecución de segunda sería completamente en caché. (Por supuesto, no sé cuántas filas tiene fk=1;? Presumiblemente menos de todas las filas)

... Pero a 600m filas, la tabla y su índice son cada acercarse a la agrupación de almacenamiento de 25 GB . Entonces, llegará el día en que se convierta en E/S: esto hará que desee volver a 16 (o 25) segundos; sin embargo, no podrás. Entonces podemos hablar de alternativas para hacer el COUNT.

SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000,1 - Analicemos esto. Escaneará el índice, pero se detendrá después de 5000 filas. De todo lo que necesita es "más de 5K", esa es la mejor manera de obtenerlo. Será consistentemente rápido (solo tocará una docena de bloques), independientemente del número total de filas en la tabla. (Todavía está sujeto a buffer_pool_size y a las características del sistema en caché, pero una docena de bloques lleva mucho menos de un segundo, incluso con un caché frío.)

Puede que valga la pena investigar el LIMIT ROWS_EXAMINED de MariaDB. Sin eso, se podría hacer

SELECT COUNT(*) AS count_if_less_than_5K 
    FROM (SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000); 

Se puede ser más rápido que la entrega de las filas al cliente; Tendrá que recoger las filas internamente en una tabla tmp, pero solo entregará el COUNT.

Una nota al margen: 640K filas insertadas por día - esto se acerca al límite para una sola fila INSERTs en MySQL con sus configuraciones actuales en una HDD (no SDD). Si necesita hablar sobre un posible desastre, abra otra pregunta.

En pocas palabras:

  • asegúrese de evitar el caché de consultas. (usando SQL_NO_CACHE o apagando el control de calidad)
  • Ejecute cualquier consulta de tiempo dos veces; usar la segunda vez
  • Comprenda la estructura y el tamaño de los BTree involucrados.
  • No use COUNT(x) a menos que necesite la verificación nula.
  • No utilice la interfaz mysql_* de PHP; cambiar a mysqli_* o PDO.
Cuestiones relacionadas