AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 333683
Accepted
Shahid Thaika
Shahid Thaika
Asked: 2023-12-03 17:35:00 +0800 CST2023-12-03 17:35:00 +0800 CST 2023-12-03 17:35:00 +0800 CST

A consulta MySQL 8 não prefere índice com maior cardinalidade

  • 772

Eu tenho uma consulta semelhante à seguinte:

FROM example_table
WHERE 
    `date` BETWEEN '2023-11-26' AND '2023-11-28'
    AND location_id IN (3, 4, 6, 7, 8, 10, 11, 12, 14, 18, 19, 22, 23, 24, 28, 29, 30, 31, 32, 36, 39, 40, 41, 43, 45, 46, 48, 49, 50, 51, 52, 54, 55, 56, 57, 59, 60, 61, 62, 68, 69, 75, 121)
    AND ( `type` IS NULL OR ( `type` IN ('type1', 'type2', 'type3') ) )
GROUP BY location_id;

Meu entendimento é que, ao criar um índice multicoluna, a coluna com maior cardinalidade/seletividade vai primeiro. Tentei testar o desempenho com duas chaves de índice:

  1. (data, location_id, tipo, valor)
  2. (location_id, data, tipo, valor)

Na minha tabela real, tenho 11.833 valores exclusivos na coluna de data e apenas 99 em location_id. Atualmente, existem mais de 63 milhões de linhas.

No entanto, o MySQL 8 prefere usar aquele que começa com location_id. Mesmo quando tento FORCE INDEXe EXPLAIN ANALYZE, ele mostra um custo/tempo maior daquele que começa com date.

O que poderia estar acontecendo?

EDITAR:

EXPLICAR ANÁLISE:

  1. data primeiro índice
    -> Group aggregate: sum(ledger_entries.amount_cents)  (cost=1897 rows=6236) (actual time=0.167..4.67 rows=43 loops=1)
        -> Filter: ((ledger_entries.`date` = DATE'2023-11-28') and (ledger_entries.location_id in (3,4,6,7,8,10,11,12,14,18,19,22,23,24,28,29,30,31,32,36,39,40,41,43,45,46,48,49,50,51,52,54,55,56,57,59,60,61,62,68,69,75,121)) and ((ledger_entries.`type` is null) or (ledger_entries.`type` in ('Procedure','Adjustment','AncillarySale'))))  (cost=1273 rows=6236) (actual time=0.0221..4.09 rows=6192 loops=1)
            -> Covering index range scan on ledger_entries using index_le_date_location_type_amount_cents over (date = '2023-11-28' AND location_id = 3 AND type = NULL) OR (date = '2023-11-28' AND location_id = 3 AND type = 'Adjustment') OR (170 more)  (cost=1273 rows=6236) (actual time=0.02..2.83 rows=6192 loops=1)
  1. primeiro índice de localização
    -> Group aggregate: sum(ledger_entries.amount_cents)  (cost=1888 rows=6236) (actual time=0.171..4.74 rows=43 loops=1)
        -> Filter: ((ledger_entries.`date` = DATE'2023-11-28') and (ledger_entries.location_id in (3,4,6,7,8,10,11,12,14,18,19,22,23,24,28,29,30,31,32,36,39,40,41,43,45,46,48,49,50,51,52,54,55,56,57,59,60,61,62,68,69,75,121)) and ((ledger_entries.`type` is null) or (ledger_entries.`type` in ('Procedure','Adjustment','AncillarySale'))))  (cost=1265 rows=6236) (actual time=0.0244..4.15 rows=6192 loops=1)
            -> Covering index range scan on ledger_entries using ledger_entries_location_date_type_amount_cents over (location_id = 3 AND date = '2023-11-28' AND type = NULL) OR (location_id = 3 AND date = '2023-11-28' AND type = 'Adjustment') OR (170 more)  (cost=1265 rows=6236) (actual time=0.022..2.91 rows=6192 loops=1)
mysql
  • 2 2 respostas
  • 38 Views

2 respostas

  • Voted
  1. Rick James
    2023-12-04T00:31:29+08:002023-12-04T00:31:29+08:00

    GROUP BY location_id.

    • Se o índice escolhido começar com location_id, o processamento poderá passar pelo índice,

    • Caso contrário, será necessário haver uma tabela temporária e uma classificação.

    O Otimizador não possui informações suficientes para saber com certeza qual plano de execução seria realmente mais rápido, mas os itens acima são os melhores com os quais ele pode trabalhar.

    Se você quiser discutir isso mais detalhadamente, forneça SHOW CREATE TABLEe o arquivo EXPLAIN ANALYZEs.

    • 2
  2. Best Answer
    bobflux
    2023-12-04T01:38:02+08:002023-12-04T01:38:02+08:00

    Meu entendimento é que, ao criar um índice multicoluna, a coluna com maior cardinalidade/seletividade vai primeiro.

    Isso faz sentido se você também quiser reutilizar esse índice com consultas que não usam todas as colunas indexadas. Se você tiver um índice em (a,b,c), ele também servirá como índice em (a,b) e (a) gratuitamente. Esses índices "livres" são muito mais úteis se (a) e/ou (a,b) tiverem boa seletividade (alta cardinalidade). Caso contrário, se (a) tiver baixa cardinalidade, então um índice apenas em (a) será inútil.

    Agora sua consulta faz:

    date BETWEEN date1 and date2
    AND location_id IN (big list)
    GROUP BY location_id
    

    Com um índice btree em (location_id,date) é bem simples, o algo seria assim:

    for loc_id in (big_list):
        range query on (location_id,date) BETWEEN (loc_id,date1) AND (loc_id,date2)
    

    Um índice btree em (a,b,c) é ordenado por (a,b,c), portanto, suporta consultas de intervalo em qualquer subconjunto de colunas, desde que sejam (a), (a,b) ou (a, b,c). Mas não qualquer outra combinação ou qualquer outra ordem.

    Hmm... Agora tenho que explicar a ordem das tuplas... É como ordenar por (sobrenome,nome). Nesse caso, uma consulta de intervalo em (location_id,date) entre (1,'2022-01-02') e (1,'2022-01-04') selecionará estas linhas:

    loc   date
    -----------
    1     2023-01-01
    1     2023-01-02 <-range start and read next
    1     2023-01-03 <-read next
    1     2023-01-04 <-read and stop
    2     2023-01-01
    2     2023-01-02
    2     2023-01-03
    2     2023-01-04
    

    ... tudo o que faz é encontrar a primeira linha do intervalo e depois ler as linhas do índice em ordem até o final do intervalo, o que é muito rápido. Então você tem uma pesquisa de índice por loc_id e depois lê o intervalo. Como bônus, os dados já estão ordenados por location_id, portanto, não é necessário fazer nenhum trabalho extra para o grupo. Parece bom.

    Com um índice btree ativado (date,location_id) , é muito mais complicado. Vamos pegar os dados anteriores e fazer um índice ordenado novamente.

    date         loc
    ----------------
    2023-01-01   1
    2023-01-01   2
    2023-01-02   1
    2023-01-02   2
    2023-01-03   1
    2023-01-03   2
    2023-01-04   1
    2023-01-04   2
    

    O problema aqui é que as colunas do índice são trocadas, mas a consulta de intervalo ainda está na mesma coluna de antes. Ainda é a data. Se você tiver um índice em (data, loc), ele poderá fazer uma consulta de intervalo com eficiência em (data), mas o índice não filtrará por loc. Isso deve ser feito depois de ler as linhas do índice. Vamos fazer a consulta do intervalo de datas entre '2022-01-02' e '2022-01-04':

    date         loc
    ----------------
    2023-01-01   1
    2023-01-01   2
    2023-01-02   1 <- range start and read next
    2023-01-02   2 <- read next
    2023-01-03   1 <- read next
    2023-01-03   2 <- read next
    2023-01-04   1 <- read next
    2023-01-04   2 <- read and stop
    

    Portanto, ele irá verificar e ler muitas linhas com location_id's que não estão na sua (grande lista) e depois jogá-las fora. Isso ainda é bom se o intervalo de datas for pequeno, é melhor ler 1% da tabela e jogar fora a maior parte do que não ter o índice, ler a tabela inteira e também jogar fora a maior parte para acabar com o mesmo resultado.

    Além disso, as linhas resultantes não são ordenadas por location_id, portanto o agrupamento por precisa de trabalho extra.

    Portanto a escolha do plano de consulta é lógica.

    Os índices também podem ser usados ​​para evitar a classificação, portanto, um índice em (loc,data) otimizará "WHERE loc=... AND data BETWEEN... ORDER B data", mas não funcionará se a consulta tiver "loc IN (...)" porque então ele realmente leria vários pedaços em ordem de data, mas ainda teria que classificar todo o resultado.

    • 0

relate perguntas

  • Existem ferramentas de benchmarking do MySQL? [fechado]

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

  • Quando é o momento certo para usar o MariaDB em vez do MySQL e por quê?

  • Como um grupo pode rastrear alterações no esquema do banco de dados?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve