Map(K, V) armazena pares chave-valor.
Ao contrário de outros bancos de dados, os maps no ClickHouse não exigem chaves únicas, ou seja, um map pode conter dois elementos com a mesma chave.
(O motivo é que os maps são implementados internamente como Array(Tuple(K, V)).)
Você pode usar a sintaxe m[k] para obter o valor da chave k no map m.
Além disso, m[k] faz uma varredura no map, ou seja, o runtime da operação é linear em relação ao tamanho do map.
Parâmetros
K— O tipo das chaves do Map. Qualquer tipo, exceto Nullable e LowCardinality aninhado com tipos Nullable.V— O tipo dos valores do Map. Qualquer tipo.
Query
key2:
Query
Response
k não estiver presente no map, m[k] retornará o default value do tipo de valor, por exemplo, 0 para tipos inteiros e '' para tipos String.
Para verificar se uma chave existe em um map, você pode usar a função mapContains.
Query
Response
Convertendo Tuple para Map
Tuple() podem ser convertidos para o tipo Map() usando a função CAST:
Exemplo
Query
Response
Leitura das subcolunas de Map
keys e values.
Exemplo
Query
Response
Serialização em buckets de Map no MergeTree
Map no MergeTree é armazenada como um único fluxo Array(Tuple(K, V)).
Ler uma única chave com m['key'] exige varrer a coluna inteira — cada par chave-valor de cada linha — mesmo quando apenas uma chave é necessária.
Em maps com muitas chaves distintas, isso se torna um gargalo.
A serialização em buckets (with_buckets) divide os pares chave-valor em vários subfluxos independentes (buckets) com base no hash da chave.
Quando uma consulta acessa m['key'], apenas o bucket que contém essa chave é lido do disco, ignorando todos os outros buckets.
Habilitando a serialização em buckets
basic para as partes de nível zero (criadas durante INSERT) e usar with_buckets apenas para as partes mescladas:
Como funciona
with_buckets:
- O número médio de chaves por linha é calculado com base nas estatísticas do bloco.
- O número de buckets é determinado pela estratégia configurada (consulte Configurações).
- Cada par chave-valor é atribuído a um bucket por meio do hash da chave:
bucket = hash(key) % num_buckets. - Cada bucket é armazenado como um subfluxo independente com suas próprias chaves, valores e offsets.
- Um fluxo de metadados
buckets_inforegistra a contagem de buckets e as estatísticas.
m['key']), o otimizador reescreve a expressão para uma subcoluna de chave (m.key_<serialized_key>).
A camada de serialização calcula a qual bucket a chave solicitada pertence e lê apenas esse bucket do disco.
Quando o map completo é lido (por exemplo, SELECT m), todos os buckets são lidos e remontados no map original. Isso é mais lento do que a serialização basic devido à sobrecarga de ler e mesclar vários subfluxos.
A ordem das chaves dentro de um valor de map pode diferir da ordem de inserção original ao usar a serialização
with_buckets. As chaves são distribuídas entre buckets por hash e remontadas na ordem dos buckets, não na ordem de inserção. Com a serialização basic, a ordem das chaves dos maps inseridos é preservada.basic e with_buckets podem coexistir na mesma tabela e são mescladas de forma transparente.
Configurações
Trade-offs de desempenho
with_buckets em comparação com a serialização basic em diferentes tamanhos de map (de 10 a 10.000 chaves por linha). A quantidade de buckets foi determinada pela estratégia sqrt, limitada a 32. Os números exatos dependem dos tipos de chave/valor, da distribuição dos dados e do hardware.
Recomendações
- Maps pequenas (< 32 chaves em média): Mantenha a serialização
basic. A sobrecarga da divisão em buckets não se justifica para maps pequenas. O valor padrãomap_buckets_min_avg_size = 32garante isso automaticamente. - Maps médias (32–100 chaves): Use
with_bucketscom a estratégiasqrtse as consultas acessarem chaves individuais com frequência. O ganho de velocidade é de 4–8x para buscas de uma única chave. - Maps grandes (100+ chaves): Use
with_buckets. Buscas de uma única chave são 16–49x mais rápidos. Consideremap_serialization_version_for_zero_level_parts = 'basic'para manter a velocidade de inserção próxima à referência. - Varreduras completas do map dominam o workload: Mantenha
basic. A serialização em buckets adiciona ~2x de sobrecarga para varreduras completas. - Workload misto (alguns buscas de chave, algumas varreduras completas): Use
with_bucketscom as partes de nível zero definidas comobasic. A otimizaçãoPREWHERElê apenas o bucket relevante para o filtro e depois lê o map completo somente para as linhas correspondentes, proporcionando um ganho líquido de velocidade significativo.
Abordagens alternativas
Map em buckets não se adequar ao seu caso de uso, há duas abordagens alternativas para melhorar o desempenho do acesso por chave:
Usando o tipo de dados JSON
max_dynamic_paths vão para uma estrutura de dados compartilhada, que pode usar a serialização advanced para otimizar leituras de um único caminho. Consulte o artigo no blog para ter uma visão geral detalhada da serialização advanced.
Use
JSON quando chaves diferentes precisarem de tipos de valor diferentes, quando o conjunto de chaves variar significativamente entre as linhas ou quando chaves acessadas com frequência forem conhecidas de antemão e puderem ser declaradas como caminhos tipados para acesso direto por subcoluna.
Sharding manual em várias colunas do tipo Map
Map em várias colunas com base no hash da chave, no nível da aplicação:
m{hash(key) % 4}. Nas consultas, leia da coluna específica: m{hash('target_key') % 4}['target_key'].
O sharding manual é vantajoso quando as mesclagens verticais são importantes para reduzir o uso de memória durante mesclagens de tabelas com muitas colunas, ou quando o número de shards precisa ser fixo e controlado explicitamente. Para a maioria dos casos de uso, a serialização automática em buckets é mais simples e suficiente.
Veja também
- função map()
- função CAST()
- combinador -Map para o tipo de dado Map