Pergunta

Existe uma diferença de desempenho mensurável entre usando INT vs VARCHAR como uma chave primária no MySQL? Eu gostaria de usar VARCHAR como a chave primária para listas de referência (pense US Unidos, Country Codes) e um colega de trabalho não vai ceder no INT AUTO_INCREMENT como uma chave primária para todas as tabelas.

O meu argumento, conforme detalhado aqui , é que a diferença de desempenho entre INT e VARCHAR é insignificante, uma vez que cada referência INT chave estrangeira vai exigir um JOIN a fazer sentido da referência, uma chave VARCHAR irá apresentar diretamente as informações.

Então, alguém tem experiência com este caso de uso particular e as preocupações de desempenho associados com ele?

Foi útil?

Solução

Você faz um ponto bom que você pode evitar algumas número de consultas unidas usando o que é chamado um chave natural em vez de um surrogate key . Só você pode avaliar se o benefício disso é significativo em sua aplicação.

Isto é, você pode medir as consultas em seu aplicativo que são os mais importantes para ser rápido, porque eles trabalham com grandes volumes de dados ou eles são executados com muita freqüência. Se essas consultas beneficiar da eliminação de uma junção, e não sofrem usando uma chave primária varchar, então fazê-lo.

Não use qualquer estratégia para todas as tabelas no seu banco de dados. É provável que, em alguns casos, uma chave natural é melhor, mas em outros casos uma chave substituta é melhor.

Outros povos fazer um ponto bom que é raro na prática para uma chave natural para nunca mudam ou ter duplicatas, chaves para substitutos são geralmente vale a pena.

Outras dicas

Não se trata de desempenho. É sobre o que faz uma boa chave primária. Única e tempo sobre imutável. Você pode pensar que uma entidade como um código de país nunca muda ao longo do tempo e seria um bom candidato para uma chave primária. Mas a experiência amarga é que raramente é assim.

INT AUTO_INCREMENT satisfaz a condição "único e imutável ao longo do tempo". Daí a preferência.

depende do comprimento .. Se o varchar será de 20 caracteres, eo int é 4, então se você usar um int, o seu índice terá cinco vezes mais nós por página de espaço de índice no disco ... Isso meios que atravessar o índice vai exigir um quinto tanto como física e / ou leituras lógicas ..

Assim, se o desempenho for um problema, dada a oportunidade, sempre usar uma chave não-significativa integral (chamado um substituto) para suas tabelas, e por chaves estrangeiras que fazem referência as linhas nestas tabelas ...

Ao mesmo tempo , a consistência dos dados de garantia, cada mesa onde importa deve também tem uma chave alternativa não numérico significativo, (ou índice exclusivo) para garantir que as linhas duplicadas não pode ser inserido (duplicar com base em atributos da tabela significativas).

Para o uso específico que você está falando (como pesquisas estaduais) que realmente não importa, porque o tamanho da tabela é tão pequena .. Em geral, não há impacto sobre o desempenho de índices em tabelas com menos do que alguns mil linhas ...

Claro que não.

Eu tenho feito verificações várias ... vários ... desempenho entre INT, VARCHAR e CHAR.

10 milhões registro da tabela com uma chave primária (única e cluster) teve exatamente a mesma velocidade e desempenho (e custo sub-árvore), não importa qual dos três que eu usei.

Dito ... use o que é melhor para sua aplicação. Não se preocupe com o desempenho.

Eu estava um pouco irritado com a falta de pontos de referência para esta linha, então eu corri um teste de mim mesmo.

Nota que embora eu não fazê-lo em uma base regular, por isso, verifique a minha configuração e as etapas para quaisquer fatores que podem ter influenciado os resultados sem querer, e postar suas preocupações nos comentários.

A configuração foi a seguinte:

  • Intel® Core ™ i7-7500U CPU @ 2.70GHz × 4
  • 15,6 GiB RAM, da qual eu assegurou cerca de 8 GB estava livre durante o teste.
  • 148,6 GB SSD rígido, com muito espaço livre.
  • Ubuntu 16.04 64 bits
  • MySQL Ver 14,14 Distrib 5.7.20, para Linux (x86_64)

As tabelas:

create table jan_int (data1 varchar(255), data2 int(10), myindex tinyint(4)) ENGINE=InnoDB;
create table jan_int_index (data1 varchar(255), data2 int(10), myindex tinyint(4), INDEX (myindex)) ENGINE=InnoDB;
create table jan_char (data1 varchar(255), data2 int(10), myindex char(6)) ENGINE=InnoDB;
create table jan_char_index (data1 varchar(255), data2 int(10), myindex char(6), INDEX (myindex)) ENGINE=InnoDB;
create table jan_varchar (data1 varchar(255), data2 int(10), myindex varchar(63)) ENGINE=InnoDB;
create table jan_varchar_index (data1 varchar(255), data2 int(10), myindex varchar(63), INDEX (myindex)) ENGINE=InnoDB;

Então, eu enchi 10 milhões de linhas em cada tabela com um script PHP cuja essência é assim:

$pdo = get_pdo();

$keys = [ 'alabam', 'massac', 'newyor', 'newham', 'delawa', 'califo', 'nevada', 'texas_', 'florid', 'ohio__' ];

for ($k = 0; $k < 10; $k++) {
    for ($j = 0; $j < 1000; $j++) {
        $val = '';
        for ($i = 0; $i < 1000; $i++) {
            $val .= '("' . generate_random_string() . '", ' . rand (0, 10000) . ', "' . ($keys[rand(0, 9)]) . '"),';
        }
        $val = rtrim($val, ',');
        $pdo->query('INSERT INTO jan_char VALUES ' . $val);
    }
    echo "\n" . ($k + 1) . ' millon(s) rows inserted.';
}

Para tabelas int, o ($keys[rand(0, 9)]) pouco foi substituído com apenas rand(0, 9), e para tabelas varchar, eu usei completos nomes dos estados norte-americanos, sem cortar ou estendendo-as a 6 caracteres. generate_random_string() gera uma seqüência aleatória de 10 caracteres.

Então eu corri em MySQL:

  • SET SESSION query_cache_type=0;
  • Para a tabela jan_int:
    • SELECT count(*) FROM jan_int WHERE myindex = 5;
    • SELECT BENCHMARK(1000000000, (SELECT count(*) FROM jan_int WHERE myindex = 5));
  • Para outras tabelas, mesmo que acima, com myindex = 'califo' para tabelas char e myindex = 'california' para tabelas varchar.

Tempos de consulta BENCHMARK em cada tabela:

  • jan_int: 21.30 seg
  • jan_int_index: 18,79 seg
  • jan_char: 21.70 seg
  • jan_char_index: 18.85 seg
  • jan_varchar: 21.76 seg
  • jan_varchar_index: 18.86 seg

Em relação tamanhos de mesa e de índice, aqui está a saída do show table status from janperformancetest; (w / algumas colunas não mostrado):

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Name              | Engine | Version | Row_format | Rows    | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Collation              |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| jan_int           | InnoDB |      10 | Dynamic    | 9739094 |             43 |   422510592 |               0 |            0 |   4194304 |           NULL | utf8mb4_unicode_520_ci |  
| jan_int_index     | InnoDB |      10 | Dynamic    | 9740329 |             43 |   420413440 |               0 |    132857856 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_char          | InnoDB |      10 | Dynamic    | 9726613 |             51 |   500170752 |               0 |            0 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_char_index    | InnoDB |      10 | Dynamic    | 9719059 |             52 |   513802240 |               0 |    202342400 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_varchar       | InnoDB |      10 | Dynamic    | 9722049 |             53 |   521142272 |               0 |            0 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_varchar_index | InnoDB |      10 | Dynamic    | 9738381 |             49 |   486539264 |               0 |    202375168 |   7340032 |           NULL | utf8mb4_unicode_520_ci | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Minha conclusão é que não há nenhuma diferença de desempenho para este caso de uso particular.

Para códigos curtos, provavelmente não há diferença. Isto é especialmente verdadeiro quando a mesa segurando esses códigos são susceptíveis de ser muito pequeno (um par mil linhas no máximo) e não mudar frequentemente (quando foi a última vez que adicionou um novo Estado dos EUA).

Para maiores tabelas com uma variação mais ampla entre a chave, isso pode ser perigoso. Pensar sobre o uso de e-mail nome de endereço / usuário a partir de uma tabela de usuário, por exemplo. O que acontece quando você tem alguns milhões de usuários e alguns desses usuários têm nomes longos ou endereços de e-mail. Agora, qualquer tempo que você precisa para se juntar a esta tabela usando essa chave torna-se muito mais caro.

Quanto a chave primária, o que faz fisicamente uma linha única deve ser determinado como a chave primária.

Para uma referência como uma chave estrangeira, utilizando um auto incremento inteiro como um substituto é uma boa idéia por duas razões principais.
- Primeiro, há menos sobrecarga incorrida no normalmente juntar
. - Em segundo lugar, se você precisa atualizar a tabela que contém o varchar único, então a actualização tem em cascata para baixo para todas as tabelas filho e atualizar todos eles, bem como os índices, enquanto que com o substituto int, ele só tem que atualizar o tabela mestre e é índices.

O drawaback a usar o substituto é que você poderia permitir a alteração do significado do substituto:

ex.
id value
1 A
2 B
3 C

Update 3 to D
id value
1 A
2 B
3 D

Update 2 to C
id value
1 A
2 C
3 D

Update 3 to B
id value
1 A
2 C
3 B

Tudo depende do que você realmente precisa se preocupar com a sua estrutura e o que significa que a maioria.

casos comuns onde um AUTO_INCREMENT substituto dói:

Um teste padrão esquema comum é um many-to-many mapeamento :

CREATE TABLE map (
    id ... AUTO_INCREMENT,
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(id),
    UNIQUE(foo_id, bar_id),
    INDEX(bar_id) );

Desempenho deste padrão é muito melhor, especialmente quando se utiliza InnoDB:

CREATE TABLE map (
    # No surrogate
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(foo_id, bar_id),
    INDEX      (bar_id, foo_id) );

Por quê?

  • chaves secundárias InnoDB precisa de uma pesquisa extra; movendo o par para a PK, que é evitado por uma direcção.
  • O índice secundário é "cobertura", por isso não precisa a pesquisa extra.
  • Esta tabela é menor, porque de se livrar de id e um índice.

Outro caso ( país ):

country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii

Muitas vezes o novato normaliza contry_code em um INT de 4 bytes em vez de usar a 2 byte 'natural', quase-imutável seqüência de 2 bytes. Mais rápidos, menores, menos JOINs, mais legível.

No HauteLook, mudamos muitas de nossas tabelas para usar chaves naturais. Nós fez experimentar um aumento real no desempenho. Como você menciona, muitas das nossas consultas utilizam agora menos junta que faz as consultas de maior performance. Iremos até mesmo usar uma chave primária composta se faz sentido. Dito isto, algumas tabelas são apenas mais fácil trabalhar com se eles têm uma chave substituta.

Além disso, se você está deixando as pessoas escrevem interfaces para seu banco de dados, uma chave substituta pode ser útil. O terceiro partido pode contar com o fato de que a chave substituta irá mudar apenas em circunstâncias muito raras.

A pergunta é sobre MySQL, então eu dizer que há uma diferença significativa. Se fosse sobre a Oracle (que armazena números como string - sim, eu não podia acreditar que em um primeiro momento), então não há muita diferença

.

Armazenamento na tabela não é a questão, mas atualizar e referindo-se ao índice é. Consultas envolvendo procurar um registro com base em sua chave primária são freqüentes - você quer que eles ocorram o mais rápido possível, porque eles acontecem com tanta frequência

.

A coisa é uma CPU lida com 4 bytes e 8 inteiros byte naturalmente, no de silício . É muito rápido para ele para comparar dois números inteiros - acontece em um ou dois ciclos de relógio.

Agora olhe para uma string - é composta de muitos personagens (mais de um byte por caractere nos dias de hoje). Comparar duas cadeias de precedência não pode ser feito em um ou dois ciclos. Em vez disso os personagens dos cordões deve ser repetido até que seja detectada uma diferença. Eu tenho certeza que existem truques para torná-lo mais rápido em alguns bancos de dados, mas isso é irrelevante aqui porque uma comparação int é feito naturalmente e muito rápido no silício pela CPU.

A minha regra geral - cada chave primária deve ser um INT autoincrementável especialmente em aplicativos OO usando um ORM (Hibernate, DataNucleus, qualquer que seja), onde há muitas relações entre os objetos - eles geralmente sempre ser implementado como um simples FK eo capacidade para o DB para resolver aqueles rápido é importante para a sua aplicação' s capacidade de resposta.

Eu enfrentado o mesmo dilema. Fiz um DW (esquema Constellation) com mesas de 3 fato, os acidentes rodoviários, veículos em acidentes e de vítimas em acidentes. Os dados incluem todos os acidentes registrados no Reino Unido 1979-2012, e 60 tabelas de dimensões. Todos juntos, cerca de 20 milhões de discos.

tabelas de fatos relacionamentos:

+----------+          +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1      * +----v----+
     1|                    |1
      |    +----------+    |
      +---<| Casualty |>---+
         * +----------+ *

RDMS: MySQL 5.6

nativamente o índice de acidentes é um varchar (números e letras), com 15 dígitos. Tentei não têm chaves substitutas, uma vez que os índices de acidentes nunca iria mudar. Em um computador i7 (8 núcleos), a DW tornou-se demasiado lento para consulta após 12 milhões de registros de carga, dependendo das dimensões. Depois de um monte de re-trabalho e adicionando chaves substitutas bigint I teve um impulso média 20% o desempenho de velocidade. No entanto, para ganho de desempenho baixo, mas tentativa válida. Im trabalhando em sintonia MySQL e clustering.

Não tenho certeza sobre as implicações de desempenho, mas parece um possível compromisso, pelo menos durante o desenvolvimento, seria incluir tanto a tecla, auto-incrementada inteiro "substituto", bem como o seu, chave destina-se, única "natural" . Isto lhe daria a oportunidade de avaliar o desempenho, bem como outros possíveis problemas, incluindo a mutabilidade das chaves naturais.

Como de costume, não há respostas cobertor. 'Depende!' e eu não estou sendo jocoso. O meu entendimento da questão original era para chaves em pequenas mesas -. Como País (Integer id ou caractere código / varchar), sendo uma chave estrangeira para uma potencialmente enorme mesa como tabela de endereços / contact

Há dois cenários aqui quando você quer de volta os dados do DB. Primeiro é uma lista / pesquisar tipo de consulta em que você deseja listar todos os contatos com códigos de estado e de país ou nomes (IDS não vai ajudar e, portanto, vai precisar de um lookup). O outro é um cenário get on chave primária que mostra um único registro de contato onde o nome do estado, as necessidades do país para ser mostrado.

Para o último get, ele provavelmente não importa o que o FK é baseado em uma vez que estamos trazendo mesas juntos para um único registro ou alguns registros e na chave lê. O ex-(pesquisa ou lista) cenário pode ser impactado por nossa escolha. Uma vez que é necessário para mostrar país (pelo menos um código reconhecível e talvez até mesmo a procurar em si inclui um código de país), não ter que se juntar a uma outra tabela através de uma chave substituta pode, potencialmente, (eu estou apenas sendo cauteloso aqui, porque eu não tenho realmente testado isso, mas parece altamente provável) melhorar o desempenho; não obstante o fato de que ele certamente ajuda com a pesquisa.

Como os códigos são de tamanho pequeno -. Não mais de 3 caracteres normalmente para país e estado, pode ser bom para usar as teclas naturais como chaves estrangeiras nesse cenário

O outro cenário onde as chaves são dependentes de valores varchar mais longos e talvez em tabelas maiores; a chave substituta provavelmente tem a vantagem.

Permita-me dizer que sim, há definitivamente uma diferença, levando em consideração o âmbito de atuação (Fora da definição box):

1- Usando substituto int é mais rápido na aplicação, porque você não precisa usar ToUpper (), ToLower (), ToUpperInvarient (), ou ToLowerInvarient () em seu código ou em sua consulta e estas 4 funções têm diferentes padrões de desempenho . Veja as regras de desempenho da Microsoft sobre este assunto. (Desempenho de aplicação)

2 Usando garantias int substitutos não mudar a chave ao longo do tempo. códigos mesmo país pode mudar, ver Wikipedia como códigos ISO mudou ao longo do tempo. Isso levaria muito tempo para mudar a chave primária para subárvores. (Realização da manutenção de dados)

3 Parece que há problemas com soluções ORM, como NHibernate quando PK / FK não é int. (Performance desenvolvedor)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top