Como acelerar as consultas em uma grande tabela de 220 milhões de linhas (dados de 9 GB)?

31

O problema:

Temos um site social em que os membros podem avaliar um ao outro por compatibilidade ou correspondência. Esta user_match_ratingstabela contém mais de 220 milhões de linhas (dados de 9 GB ou quase 20 GB em índices). As consultas nessa tabela são rotineiramente exibidas em slow.log (limite> 2 segundos) e é a consulta lenta mais frequentemente registrada no 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;"

Versão do MySQL:

  • versão do protocolo: 10
  • versão: 5.0.77-log
  • versão bdb: Sleepycat Software: Berkeley DB 4.1.24: (29 de janeiro de 2009)
  • versão compilar máquina: x86_64 version_compile_os: redhat-linux-gnu

Informações da tabela:

SHOW COLUMNS FROM user_match_ratings;

Dá:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 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 amostra:

select * from mutual_match_ratings where id=221673540;

dá:

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

Índices

A tabela possui 3 índices configurados:

  1. índice único em rated_user_id
  2. índice composto em rater_user_idecreated_at
  3. índice composto em rated_user_iderater_user_id
mostre o índice de user_match_ratings;

dá:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 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                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Mesmo com os índices, essas consultas são lentas.

Minha pergunta:

Separar essa tabela / dados em outro banco de dados em um servidor com memória RAM suficiente para armazenar esses dados na memória aceleraria essas consultas? De qualquer forma, existe alguma coisa em que as tabelas / índices estejam configurados e que possamos melhorar para tornar essas consultas mais rápidas?

Atualmente, temos 16 GB de memória; no entanto, estamos pensando em atualizar a máquina existente para 32 GB ou adicionar uma nova máquina com pelo menos essa quantidade, talvez também em unidades de estado sólido.

Ranknoodle
fonte
1
Sua pergunta é incrível. Estou muito interessado na sua solução atual e em como você conseguiu obter resultados em <= 2 segundos? Porque eu tenho uma tabela que tem apenas 20 milhões de registros e ainda leva 30 segundos para SELECT QUERY. Você poderia sugerir? PS Sua pergunta me forçou a participar desta comunidade (y);)
NullPointer
2
Observe os índices na tabela que você está consultando. Muitas vezes, muitas melhorias podem ser feitas criando o índice apropriado. Nem sempre, mas são vistas muitas instâncias em que as consultas são feitas rapidamente, fornecendo um índice nas colunas da cláusula where em uma consulta. Especialmente se uma mesa cresce cada vez mais.
Ranknoodle
Claro @Ranknoodle. Obrigado. Vou verificar respectivamente.
NullPointer

Respostas:

28

Pensamentos sobre o assunto, lançados em ordem aleatória:

  • O índice óbvia para esta consulta é: (rated_user_id, rating). Uma consulta que obtém dados para apenas um dos milhões de usuários e precisa de 17 segundos está fazendo algo errado: lendo no (rated_user_id, rater_user_id)índice e depois lendo na tabela os valores (centenas a milhares) da ratingcoluna, como ratingnão está em nenhum índice. Portanto, a consulta precisa ler muitas linhas da tabela que estão localizadas em vários locais de disco diferentes.

  • Antes de começar a adicionar vários índices nas tabelas, tente analisar o desempenho de todo o banco de dados, todo o conjunto de consultas lentas, examine novamente as opções dos tipos de dados, o mecanismo usado e as definições de configuração.

  • Considere mudar para uma versão mais recente do MySQL, 5.1, 5.5 ou até 5.6 (também: versões do Percona e MariaDB.) Vários benefícios, como bugs foram corrigidos, o otimizador melhorou e você pode definir o limite baixo para consultas lentas para menos de 1 segundo (como 10 milissegundos). Isso fornecerá informações muito melhores sobre consultas lentas.

  • A escolha para o tipo de dados de ratingé estranha. VARCHAR(1)? Por que não CHAR(1)? Por que não TINYINT? Isso economizará algum espaço, tanto na tabela quanto nos índices que incluirão essa coluna. Uma coluna varchar (1) precisa de mais um byte sobre char (1) e, se forem utf8, as colunas char (var) precisarão de 3 (ou 4) bytes, em vez de 1 (tinyint).

ypercubeᵀᴹ
fonte
2
Quanto impacto no desempenho ou desperdício de armazenamento em termos de% se você usar o tipo de dados errado?
FlyingAtom 8/02
1
@FlyingAtom Depende do caso, mas para algumas colunas indexadas que ainda precisam ser varridas (por exemplo, quando você não possui uma cláusula where, mas está recuperando apenas essa coluna), o mecanismo pode decidir varrer o índice em vez de na tabela e, se você otimizar o tipo de dados para metade do tamanho, a verificação será duas vezes mais rápida e a resposta será metade do tamanho. Se você ainda estiver examinando a tabela em vez de um índice (por exemplo, quando recuperar mais colunas, não apenas as do índice), os benefícios serão menos significativos.
Sebastián Grignoli 27/08
-1

Tratei de mesas para o governo alemão com às vezes 60 milhões de registros.

Tivemos muitas dessas tabelas.

E precisávamos conhecer muitas vezes o total de linhas de uma tabela.

Depois de conversar com os programadores da Oracle e da Microsoft, não ficamos tão felizes ...

Então, nós, o grupo de programadores de banco de dados, decidimos que em todas as tabelas há um registro, sempre o registro em que o número total de registros é armazenado. Atualizamos esse número, dependendo das linhas INSERT ou DELETE.

Tentamos de todas as formas. Esta é de longe a maneira mais rápida.

Agora, usamos esse caminho desde 1998 e nunca tivemos um número errado de linhas, em todas as nossas tabelas de vários milhões de registros.

FrankyBkk
fonte
7
Eu sugeriria examinar alguns dos recursos introduzidos nos últimos 18 anos. Entre outros, count(*)tem algumas melhorias.
Dezso
Como você sabe que nunca teve um número errado se não podia contá-los? uhmmmm ...
Tonca
-3

Vou tentar particionar em tipos de classificação, como:

mutual_match_ratings_N, mutual_match_ratings_S, etc.

Você deve executar uma consulta para cada tipo, mas talvez isso seja mais rápido que o contrário. De uma chance.

Isso pressupõe que você tenha um número fixo de tipos de classificação e que não precisa dessa tabela para outras consultas que seriam piores com essa nova estrutura.

Se for esse o caso, você deve procurar outra abordagem ou manter duas cópias da tabela (sua tabela inicial e as particionadas) se isso for acessível em termos de espaço e manutenção (ou lógica do aplicativo).

appartisan
fonte