Como forçar o MySQL a ignorar todos os índices?

12

Eu li artigos sobre FORCEíndice, mas como posso forçar o MySQL a IGNORE ALLíndices?

Eu tentei SELECT * FROM tbl IGNORE INDEX(*), mas não tive sucesso.

Quanto ao motivo pelo qual eu (e outros) precisamos fazer isso: Por exemplo, eu precisava resumir as estatísticas dos referenciadores por tld assim:

SELECT 
    count(*) as c, 
    SUBSTRING
    (
        domain_name, 
        LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2
    ) as tld
FROM `domains_import` 
    IGNORE INDEX(domain_name)
GROUP BY tld
ORDER BY c desc
LIMIT 100

... mas eu sempre tenho que olhar para quais índices são definidos ou determinar qual índice será usado pelo Explain. Seria muito útil simplesmente escrever IGNORE INDEX ALLe simplesmente não se importar.

Alguém conhece a sintaxe ou um hack? (Dezenas de linhas através de tabelas de definição do MySQL não são realmente um atalho).

Adicionado a partir da discussão no chat :

Bechmark:

  • sem índice = 148,5 segundos

  • com índice = 180 segundos e ainda em execução com o envio de dados A matriz SSD é tão poderosa que você quase não se importa com o cache de dados ...

Definição para benchmark:

CREATE TABLE IF NOT EXISTS `domains_import` (
`domain_id` bigint(20) unsigned NOT NULL,
`domain_name` varchar(253) CHARACTER SET ascii COLLATE ascii_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `domains_import`
ADD PRIMARY KEY (`domain_id`),
ADD UNIQUE KEY `domain_name` (`domain_name`);

ALTER TABLE `domains_import`
MODIFY `domain_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT;

No InnoDB, o teste com o índice (sem USE INDEX () ou similar) ainda está em execução, 250 segundos, acabei de o matar.

mvorisek
fonte

Respostas:

24

Não está absolutamente claro por que você deseja isso, mas pode usar a dica USE INDEX ()para informar ao otimizador para não usar nenhum índice. Dos documentos do MySQL: dicas de índice

É sintaticamente válido para omitir index_listparaUSE INDEX , que significa “usar nenhum índice.” Omitir index_list por FORCE INDEXou IGNORE INDEXé um erro de sintaxe.

Sua consulta se torna:

SELECT count(*) AS c, 
       substring_index(domain_name, '.', -1) AS tld
FROM domains_import 
       USE INDEX ()        -- use no indexes
GROUP BY tld
ORDER BY c DESC
LIMIT 100 ;

Nota lateral: a expressão complexa:

SUBSTRING(domain_name, LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2) 

pode ser simplificado de 4 chamadas de função para 1:

SUBSTRING_INDEX(domain_name, '.', -1)
ypercubeᵀᴹ
fonte
11
Isso foi útil para mim quando o otimizador MySQL 5.7.10 alterou seu plano de consulta para o pior quando removeu alguns dos que LEFT JOINeu tinha. `USE INDEX ()` fez o MySQL fazer uma varredura de tabela em uma tabela de 20K linhas e 1 para JOIN1s em vez de cruzar 500 linhas entre dois índices. Ficou 20x mais rápido.
Xenos
2

Você também pode incorporar WHERE 1=1

SELECT 
    count(*) as c, 
    SUBSTRING
    (
        domain_name, 
        LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2
    ) as tld
FROM `domains_import` 
WHERE 1=1
GROUP BY tld
ORDER BY c desc
LIMIT 100

ypercube acabou de me perguntar

Rolando, o otimizador do MySQL é tão burro que uma condição simples, sempre verdadeira, proibirá o uso de índices?

Sim, mas você deu ao MySQL uma consulta realmente idiota. 1=1reverteria para o Clustered Index. Não obstante, ainda existe outro caminho, mas requer ser um pouco malicioso para o Optimizer.

SELECT 
    count(*) as c, 
    SUBSTRING
    (
        domain_name, 
        LENGTH(domain_name) - LOCATE('.', REVERSE(domain_name)) + 2
    ) as tld
FROM `domains_import` 
WHERE domain_name = domain_name
GROUP BY tld
ORDER BY c desc
LIMIT 100

Isso lançará todos os índices sob o barramento, com certeza, porque o valor de cada linha para domain_namemuito será verificado. Se domain_nameestiver indexado, você deve escolher uma coluna para a WHERE column_name=column_nameque não está indexada.

Eu apenas tentei isso em uma tabela grande em um servidor intermediário

mysql > explain SELECT COUNT(1) FROM VIDEO WHERE EMBEDDED_FLG=EMBEDDED_FLG;
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | VIDEO | ALL  | NULL          | NULL | NULL    | NULL | 354327 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)

Nenhum índice é escolhido

RolandoMySQLDBA
fonte
Rolando, o otimizador do MySQL é tão burro que uma condição simples e sempre verdadeira proibirá o uso de índices?
precisa saber é o seguinte
@ ypercube sim, mas você precisa fazer uma simulação da consulta o suficiente para que isso aconteça.
RolandoMySQLDBA
11
Ei, votei na resposta do yercube a mim mesmo. Minha resposta é apenas outra maneira e explica a brecha do otimizador.
RolandoMySQLDBA
11
Rolando, não é verdade: o índice será usado: SQLfiddle . Mesmo se você tornar algo mais complicado, WHERE id+0 = id*1o índice ainda será usado e um extra Using whereaparecerá.
usar o seguinte comando
4
@PaulWhite seria. (é idiota, mas não tão idiota;) E talvez seja por isso que a consulta de Roalndo não use índice, a coluna deve ter sido definida como NULL.
precisa saber é o seguinte
0

Supondo que você tenha esses dois índices:

ADD PRIMARY KEY (`domain_id`),
ADD UNIQUE KEY `domain_name` (`domain_name`);

Então não importa o que o otimizador faz; deve escanear essencialmente uma quantidade idêntica de material.

Caso 1: Faz uma varredura de tabela (ou usa domain_id): Ele varre (pares de identificação, nome), localizando todos os nomes, executando SUBSTRING..LOCATE, GROUP BY e, finalmente, ORDER BY. O GROUP BY e o ORDER BY provavelmente precisam de uma tabela tmp e de arquivos. Verifique EXPLAIN SELECT ...para ver se faz.

Caso 2: Ele faz uma varredura de índice (de domain_name): esse índice realmente contém pares (nome, id) - porque o InnoDB coloca implicitamente a PK no final de qualquer chave secundária. O restante do processamento é paralelo ao Caso 1.

Uma coisa pode ser diferente - o tamanho dos dois BTrees. Faça SHOW TABLE STATUS LIKE domains_importpara ver o Data_length (para o Caso 1) e o Index_length (para o Caso 2). O BTree maior será mais lento.

Outra coisa pode ser diferente - armazenamento em cache. Qual é o valor de innodb_buffer_pool_size? Quanta RAM você tem? Os Dados (ou Índice) podem estar contidos no buffer pool. (Ou será 37%, por ser uma varredura de tabela / índice?) Se for o caso, execute a consulta duas vezes. A segunda vez será cerca de 10 vezes mais rápida devido a não bater no disco (cache).

Se esta for uma tarefa única, o SSD ajudará. Caso contrário, e você pode armazenar em cache a tabela inteira, ela não ajudará depois que o buffer_pool for carregado.

Rick James
fonte