¿Cómo acelerar las consultas en una gran tabla de 220 millones de filas (datos de 9 conciertos)?


31

La cuestión:

Tenemos un sitio social donde los miembros pueden clasificarse entre sí por compatibilidad o coincidencia. Esta user_match_ratingstabla contiene más de 220 millones de filas (9 datos de conciertos o casi 20 conciertos en índices). Las consultas en esta tabla aparecen de forma rutinaria en slow.log (umbral> 2 segundos) y es la consulta lenta registrada con más frecuencia en el sistema:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

Versión de MySQL:

  • versión de protocolo: 10
  • versión: 5.0.77-log
  • versión bdb: Sleepycat Software: Berkeley DB 4.1.24: (29 de enero de 2009)
  • máquina de compilación de versiones: x86_64 versión_compilación_os: redhat-linux-gnu

Información de la mesa:

SHOW COLUMNS FROM user_match_ratings;

Da:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Consulta de muestra:

select * from mutual_match_ratings where id=221673540;

da:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

Índices

La tabla tiene 3 índices configurados:

  1. índice único en rated_user_id
  2. índice compuesto en rater_user_idycreated_at
  3. índice compuesto en rated_user_idyrater_user_id
muestra el índice de user_match_ratings;

da:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Incluso con los índices, estas consultas son lentas.

Mi pregunta:

¿Separar esta tabla / datos en otra base de datos en un servidor que tenga suficiente memoria RAM para almacenar estos datos en la memoria aceleraría estas consultas? ¿Hay alguna forma de configurar las tablas / índices que podamos mejorar para agilizar estas consultas?

Actualmente tenemos 16GB de memoria; sin embargo, estamos buscando actualizar la máquina existente a 32 GB o agregar una nueva máquina con al menos esa cantidad, tal vez unidades de estado sólido también.


1
Tu pregunta es increible. Estoy muy interesado en su solución actual de cómo logró obtener resultados en <= 2 segundos. Porque tengo una tabla que tiene solo 20 millones de registros y todavía tarda 30 segundos SELECT QUERY. ¿Podría sugerirme? PD: Tu pregunta me obligó a unirme a esta comunidad (y);)
NullPointer

2
Mire los índices en la tabla que está consultando ... a menudo se pueden hacer muchas mejoras a las consultas creando el índice apropiado. No siempre, pero he visto muchas instancias donde las consultas se realizan rápidamente al proporcionar un índice contra las columnas en la cláusula where en una consulta. Especialmente si una mesa se hace más y más grande.
Ranknoodle

Claro @Ranknoodle. Gracias. Lo comprobaré respectivamente.
NullPointer

Respuestas:


28

Reflexiones sobre el tema, en orden aleatorio:

  • El índice obvia para esta consulta es: (rated_user_id, rating). Una consulta que obtiene datos solo para uno de los millones de usuarios y necesita 17 segundos está haciendo algo mal: leer del (rated_user_id, rater_user_id)índice y luego leer de la tabla los valores (de cientos a miles) para la ratingcolumna, ya ratingque no está en ningún índice. Por lo tanto, la consulta tiene que leer muchas filas de la tabla que se encuentran en muchas ubicaciones de disco diferentes.

  • Antes de comenzar a agregar numerosos índices en las tablas, intente analizar el rendimiento de toda la base de datos, todo el conjunto de consultas lentas, examine nuevamente las opciones de los tipos de datos, el motor que usa y los ajustes de configuración.

  • Considere la posibilidad de pasar a una versión más nueva de MySQL, 5.1, 5.5 o incluso 5.6 (también: versiones de Percona y MariaDB). Se han corregido varios beneficios a medida que se corrigieron los errores, se mejoró el optimizador y puede establecer el umbral bajo para consultas lentas en menos de 1 segundo (como 10 milisegundos). Esto le dará información mucho mejor sobre consultas lentas.

  • La elección del tipo de datos ratinges extraña. VARCHAR(1)? ¿Por qué no CHAR(1)? ¿Por qué no TINYINT? Esto le ahorrará algo de espacio, tanto en la tabla como en los índices que (incluirán) esa columna. Una columna varchar (1) necesita un byte más sobre char (1) y si son utf8, las columnas (var) char necesitarán 3 (o 4) bytes, en lugar de 1 (tinyint).


2
¿Cuánto impacto en el rendimiento o desperdicio de almacenamiento en términos de% si utiliza el tipo de datos incorrecto?
FlyingAtom

1
@FlyingAtom Depende del caso, pero para algunas columnas indexadas que aún necesitan ser escaneadas (por ejemplo, cuando no tiene una cláusula where pero solo está recuperando esa columna), el motor podría decidir escanear el índice en lugar de la tabla, y si optimiza su tipo de datos a la mitad del tamaño, el escaneo sería el doble de rápido y la respuesta sería la mitad del tamaño. Si aún está escaneando la tabla en lugar de un índice (por ejemplo, cuando recupera más columnas, no solo las del índice), los beneficios serían menos significativos.
Sebastián Grignoli

-1

Manejé tablas para el gobierno alemán con a veces 60 millones de registros.

Teníamos muchas de estas mesas.

Y necesitábamos saber muchas veces las filas totales de una tabla.

Después de hablar con los programadores de Oracle y Microsoft, no estábamos tan contentos ...

Entonces, nosotros, el grupo de programadores de bases de datos, decidimos que en cada tabla hay un registro, siempre el registro en el que se almacenan los números de registro totales. Actualizamos este número, dependiendo de las filas INSERT o DELETE.

Intentamos todas las otras formas. Esta es, con mucho, la forma más rápida.

Lo utilizamos ahora desde 1998 y nunca tuvimos un número incorrecto de filas, en todas nuestras tablas de registro multimillonarias.


77
Sugeriría investigar algunas de las características introducidas en los últimos 18 años. Entre otros, count(*)tiene algunas mejoras.
dezso

¿Cómo sabes que nunca has tenido un número incorrecto si no pudiste contarlos? uhmmmm ...
Tonca

-3

Intentaré particionar los tipos de calificación, como:

mutual_match_ratings_N, mutual_match_ratings_S, etc.

Debería realizar una consulta para cada tipo, pero tal vez sea más rápido que a la inversa. Darle una oportunidad.

Esto supone que tiene un número fijo de tipos de calificación y que no necesita esta tabla para otras consultas que serían peores con esta nueva estructura.

Si ese es el caso, debe buscar otro enfoque o mantener dos copias de la tabla (su tabla inicial y las particionadas) si eso es asequible en términos de espacio y facilidad de mantenimiento (o lógica de aplicación).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.