O problema:
Temos um site social em que os membros podem avaliar um ao outro por compatibilidade ou correspondência. Esta user_match_ratings
tabela 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:
- índice único em
rated_user_id
- índice composto em
rater_user_id
ecreated_at
- índice composto em
rated_user_id
erater_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.
fonte
SELECT QUERY
. Você poderia sugerir? PS Sua pergunta me forçou a participar desta comunidade (y);)Respostas:
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) darating
coluna, comorating
nã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ãoCHAR(1)
? Por que nãoTINYINT
? 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).fonte
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.
fonte
count(*)
tem algumas melhorias.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).
fonte