Aqui está minha tabela com ~ 10.000.000 linhas de dados
CREATE TABLE `votes` (
`subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`subject_id` int(11) NOT NULL,
`voter_id` int(11) NOT NULL,
`rate` int(11) NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
KEY `voter_timestamp` (`voter_id`,`updated_at`),
CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Aqui estão os índices de cardinalidades
Então, quando eu faço essa consulta:
SELECT SQL_NO_CACHE * FROM votes WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Eu estava esperando que ele usa índice, voter_timestamp
mas o mysql escolhe usar isso:
explain select SQL_NO_CACHE * from votes where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`
type:
index_merge
possible_keys:
PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
102,98
ref:
NULL
rows:
9255
filtered:
10.00
Extra:
Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort
E eu tenho 200-400ms de tempo de consulta.
Se eu forçá-lo a usar o índice correto, como:
SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Mysql pode retornar os resultados em 1-2ms
e aqui está a explicação:
type:
ref
possible_keys:
voter_timestamp
key:
voter_timestamp
key_len:
4
ref:
const
rows:
18714
filtered:
1.00
Extra:
Using where
Então, por que o mysql não escolheu o voter_timestamp
índice para minha consulta original?
O que eu tinha tentado é analyze table votes
, optimize table votes
, queda nesse índice e adicioná-lo novamente, mas mysql ainda usa o índice errado. não entendo bem qual é o problema.
subject_name = "medium"
parte ele também pode escolher o indicador direito, sem necessidade de índicerate
(voter_id, updated_at)
. Outro índice seria(voter_id, subject_name, updated_at)
ou(subject_name, voter_id, updated_at)
(sem a taxa).subject_name='medium' and rate=1
)LIMIT
mesmo, aORDER BY
menos que o índice satisfaça primeiro toda a filtragem. Ou seja, sem as quatro colunas completas, ele coletará todas as linhas relevantes, classificará todas e depois selecionará aLIMIT
. Com o índice de 4 colunas, a consulta pode evitar a classificação e parar depois de ler apenas asLIMIT
linhas.Respostas:
O MySQL está usando um modelo de custo relativamente simples (mais simples que outros RDBMS) para planejar consultas em que a filtragem do conjunto de dados tem uma prioridade bastante alta. Na sua primeira consulta com o índice de mesclagem, estima-se que a digitalização ~ 9000 linhas será necessária, enquanto a segunda com a dica do índice exigirá 18000. Minha aposta seria que isso pesa no cálculo o suficiente para mover a escala em direção à mesclagem . Você pode confirmar isso (ou encontrar outros motivos) ativando
optimizer_trace
, executando sua consulta e avaliando os resultados.Uma observação sobre
index_merge
: na maioria dos casos, você verá que é bastante caro. Embora seja muito útil para cenários do tipo OLAP, pode não ser muito adequado para OLTP, porque a operação pode levar um tempo significativo da sua consulta e, como você pode ver às vezes, o plano de execução abaixo do ideal é realmente mais rápido.Felizmente, o MySQL fornece opções para otimizador, para que você possa personalizá-lo como desejar.
Para toda a opção, você pode executar:
Para alterar um, você não precisa copiar e colar toda a string. Funciona como
dict.update()
em python.Se possível, eu também daria uma olhada na estrutura da sua tabela e melhoraria. Ter uma chave primária de ~ 100 bytes com muitas chaves secundárias não é realmente recomendado.
Você tem quatro chaves secundárias e algumas delas são supérfluas, por exemplo,
(voter_id)
índice é um subconjunto de(voter_id, updated_at)
fonte
OR
-UNION
se geralmente é tão bom ou melhor.Para essa consulta, você precisa deste índice:
O
updated_at
deve ser o último; os outros três podem estar em qualquer ordem. (os índices de três colunas do ypercube não são muito úteis, pois não finalizam asWHERE
colunas antes de atingi-ORDER BY
las.)À medida que você adiciona esse índice, você provavelmente pode se livrar de todas as outras chaves secundárias:
KEY
IDX_518B7ACFEBB4B8AD
(voter_id
), - O FK pode usar a minha chave de índicesubject_timestamp
(subject_name
,subject_id
,updated_at
), - CHAVE principalmente redundantevoter_timestamp
(voter_id
,updated_at
), - pode ter sido sua tentativaCom o índice de 4 colunas, você tem a chance de otimizar a "paginação" e evitar
OFFSET
. Veja este blog.Em outro tópico ... Quando eu vejo
X_name
eX_id
, presumo que a "normalização" está acontecendo. Eu esperaria ver essas duas colunas em uma tabela, praticamente sem mais nada. Eu não esperaria ver os dois em alguma outra tabela.(voter_id, updated_at)
não passará,voter_id
pois não terminou a filtragem (theWHERE
). Então, como um outro índice é menor, ele é escolhido. O meu possui 3 colunas para cuidar da filtragem e, em seguida, a coluna paraORDER BY
.fonte