Qual é o melhor agrupamento para usar no MySQL com PHP? [fechadas]

731

Gostaria de saber se existe uma "melhor" opção de agrupamento no MySQL para um site geral em que você não tem 100% de certeza do que será inserido? Eu entendo que todas as codificações devem ser as mesmas, como MySQL, Apache, HTML e qualquer coisa dentro do PHP.

No passado, eu configurei o PHP para saída em "UTF-8", mas qual agrupamento isso corresponde no MySQL? Eu estou pensando que é um dos UTF-8 queridos, mas eu usei utf8_unicode_ci, utf8_general_cie utf8_binantes.

Darryl Hein
fonte
35
Nota lateral: "utf8" do MySQL não é apropriado UTF-8 (não há suporte para caracteres Unicode de 4 ou mais bytes como 𝌆), no entanto "utf8mb4" é. Com utf8, um campo será truncado na inserção, começando com o primeiro caractere Unicode não suportado. mathiasbynens.be/notes/mysql-utf8mb4
basic6
6
Pergunto-me se alguma vez vai precisar de 5 bytes para todos os emojis ... suspiro
Álvaro González
1
Pergunta relacionada: stackoverflow.com/questions/38228335/… "Qual agrupamento MySQL corresponde exatamente à comparação de strings do PHP?"
William Entriken
Para uma visão geral das opções sãs: monolune.com/mysql-utf8-charsets-and-collations-explained
Flux

Respostas:

618

A principal diferença é a precisão da classificação (ao comparar caracteres no idioma) e o desempenho. O único especial é utf8_bin, que serve para comparar caracteres em formato binário.

utf8_general_cié um pouco mais rápido que utf8_unicode_ci, mas menos preciso (para classificação). A codificação utf8 de idioma específico (como utf8_swedish_ci) contém regras de idioma adicionais que os tornam mais precisos para ordenar para esses idiomas. Na maioria das vezes eu uso utf8_unicode_ci(prefiro precisão a pequenas melhorias de desempenho), a menos que tenha um bom motivo para preferir um idioma específico.

Você pode ler mais sobre conjuntos de caracteres unicode específicos no manual do MySQL - http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html

Eran Galperin
fonte
4
pequenas melhorias de desempenho? Você tem certeza disso ? publib.boulder.ibm.com/infocenter/db2luw/v9r5/index.jsp?topic=/… O agrupamento escolhido pode afetar significativamente o desempenho das consultas no banco de dados.
23810 Adam Ramadhan
62
Isto é para o DB2, não para o MySQL. Além disso, não há números concretos ou parâmetros de referência; portanto, você está apenas baseando-o na opinião do escritor.
Eran Galperin
3
Observe que, se você deseja usar funções, existe um erro no MySQL (versões mais distribuídas atualmente) em que as funções sempre retornam a string usando utf8_general_ci, causando problemas se você estiver usando outro agrupamento para suas strings - consulte bugs.mysql.com/ bug.php? id = 24690
El Yobo
1
Da minha experiência com diferentes locais que eu sempre usaria #utf8_unicode_*
Shiplu Mokaddim
11
Atualização: para versões mais recentes, recomende utf8mb4e utf8mb4_unicode_520_ci. Eles oferecem o restante dos chineses, além de agrupamento aprimorado.
Rick James
129

Na verdade, você provavelmente deseja usar utf8_unicode_ciou utf8_general_ci.

  • utf8_general_ci classifica removendo todos os sotaques e classificando como se fosse ASCII
  • utf8_unicode_ci usa a ordem de classificação Unicode, para classificar corretamente em mais idiomas

No entanto, se você estiver usando apenas isso para armazenar texto em inglês, eles não deverão diferir.

Vegard Larsen
fonte
1
Eu gosto da sua explicação! Um bom. Mas preciso entender melhor exatamente por que a ordem de classificação unicode é a melhor maneira de classificar corretamente do que remover os acentos.
weia design
14
@ Adam Realmente depende do seu público-alvo. A classificação é um problema complicado para localizar corretamente. Por exemplo, em norueguês, as letras Æ Ø Å são os últimos 3 do alfabeto. Com utf8_general_ci, Ø e Å são convertidos em O e A, o que os coloca na posição completamente errada quando classificados (não sei como Æ é tratado, pois é uma ligadura, não um caractere acentuado). Essa ordem de classificação é diferente em quase qualquer idioma, por exemplo, norueguês e sueco têm ordens diferentes (e letras ligeiramente diferentes que são consideradas iguais): Æ Ø Å é classificado Å Æ Ø (as letras reais são Å Ä Ö). Unicode corrige isso.
Vegard Larsen,
Então, o que estou dizendo basicamente, é que você provavelmente deve usar uma classificação específica de idioma, se puder, mas na maioria dos casos isso é inviável, então escolha a classificação geral Unicode. Ainda será estranho em algum idioma, mas mais correto que o ASCII.
Vegard Larsen
3
@Manatax - com qualquer um dos agrupamentos utf8_, os dados são armazenados como utf8. A classificação é exatamente sobre quais caracteres são considerados iguais e como eles são ordenados.
Frymaster 29/10
2
@frymaster - não é verdade, conforme: mathiasbynens.be/notes/mysql-utf8mb4 "O utf8 do MySQL permite apenas que você armazene 5,88% de todos os pontos de código Unicode possíveis"
data
120

Esteja muito, muito ciente deste problema que pode ocorrer ao usar utf8_general_ci.

O MySQL não fará distinção entre alguns caracteres nas instruções de seleção, se o utf8_general_ciagrupamento for usado. Isso pode levar a erros muito desagradáveis ​​- especialmente por exemplo, onde nomes de usuários estão envolvidos. Dependendo da implementação que usa as tabelas do banco de dados, esse problema pode permitir que usuários mal-intencionados criem um nome de usuário correspondente a uma conta de administrador.

Esse problema se expõe no mínimo nas versões 5.x iniciais - não tenho certeza se esse comportamento foi alterado posteriormente.

Eu não sou DBA, mas para evitar esse problema, eu sempre o uso, em utf8-binvez de não diferenciar maiúsculas de minúsculas.

O script abaixo descreve o problema pelo exemplo.

-- first, create a sandbox to play in
CREATE DATABASE `sandbox`;
use `sandbox`;

-- next, make sure that your client connection is of the same 
-- character/collate type as the one we're going to test next:
charset utf8 collate utf8_general_ci

-- now, create the table and fill it with values
CREATE TABLE `test` (`key` VARCHAR(16), `value` VARCHAR(16) )
    CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO `test` VALUES ('Key ONE', 'value'), ('Key TWO', 'valúe');

-- (verify)
SELECT * FROM `test`;

-- now, expose the problem/bug:
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get BOTH keys here! MySQLs UTF8 collates that are 
-- case insensitive (ending with _ci) do not distinguish between 
-- both values!
--
-- collate 'utf8_bin' doesn't have this problem, as I'll show next:
--

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get just one key now, as you'd expect.
--
-- This problem appears to be specific to utf8. Next, I'll try to 
-- do the same with the 'latin1' charset:
--

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_general_ci

-- next, convert the values that we've previously inserted
-- in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_general_ci;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected). This shows 
-- that the problem with utf8/utf8_generic_ci isn't present 
-- in latin1/latin1_general_ci
--
-- To complete the example, I'll check with the binary collate
-- of latin1 as well:

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected).
--
-- Finally, I'll re-introduce the problem in the exact same 
-- way (for any sceptics out there):

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_generic_ci

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

-- now, re-check for the problem/bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Two keys.
--

DROP DATABASE sandbox;
Guus
fonte
36
-1: isso é certamente remediado aplicando uma chave exclusiva à coluna relevante. Você veria o mesmo comportamento se os dois valores fossem 'value'e 'valUe'. O ponto principal de um agrupamento é que ele fornece regras para (entre outras coisas) quando duas cadeias são consideradas iguais uma à outra.
precisa saber é o seguinte
13
Esse é exatamente o problema que estou tentando ilustrar - o agrupamento torna duas coisas iguais, enquanto na verdade elas não pretendem ser iguais (e, portanto, uma restrição única é exatamente o oposto do que você deseja obter)
Guus
18
Mas você o descreve como um "problema" e que leva a "bugs" quando o comportamento é exatamente o que um agrupamento pretende atingir. Sua descrição está correta, mas apenas na medida em que é um erro por parte do DBA para selecionar um agrupamento inadequado.
Hammerite
32
O fato é que, quando você digitar dois nomes de usuário considerados iguais pelo agrupamento, não será permitido se você definir o nome de usuário do coloumn como único, o que você deve fazer naturalmente!
Student of Hogwarts
12
Votei tanto nessa resposta quanto no comentário de @ Hammerite, porque os dois juntos me ajudaram a entender o agrupamento.
Nacht - Restabelece Monica
86

É melhor usar o conjunto de caracteres utf8mb4com o agrupamento utf8mb4_unicode_ci.

O conjunto de caracteres, utf8suporta apenas uma pequena quantidade de pontos de código UTF-8, cerca de 6% dos caracteres possíveis. utf8suporta apenas o plano multilíngue básico (BMP). Existem outros 16 aviões. Cada plano contém 65.536 caracteres. utf8mb4suporta todos os 17 aviões.

O MySQL truncará caracteres UTF-8 de 4 bytes, resultando em dados corrompidos.

O utf8mb4conjunto de caracteres foi introduzido no MySQL 5.5.3 em 24-03-2010.

Algumas das alterações necessárias para usar o novo conjunto de caracteres não são triviais:

  • Podem ser necessárias alterações no adaptador do banco de dados do aplicativo.
  • Alterações precisarão ser feitas no my.cnf, incluindo a configuração do conjunto de caracteres, o agrupamento e a alternância de innodb_file_format para Barracuda
  • As instruções SQL CREATE podem precisar incluir: ROW_FORMAT=DYNAMIC
    • DINÂMICO é necessário para índices em VARCHAR (192) e maiores.

NOTA: Mudar para Barracudade Antelope, pode exigir a reinicialização do serviço MySQL mais de uma vez. innodb_file_format_maxnão muda até que o serviço MySQL foi reiniciado para: innodb_file_format = barracuda.

O MySQL usa o antigo Antelopeformato de arquivo InnoDB. Barracudasuporta formatos de linha dinâmicos, que você precisará se não desejar obter os erros do SQL para criar índices e chaves após mudar para o conjunto de caracteres:utf8mb4

  • # 1709 - Tamanho da coluna de índice muito grande. O tamanho máximo da coluna é 767 bytes.
  • # 1071 - A chave especificada era muito longa; o comprimento máximo da chave é 767 bytes

O cenário a seguir foi testado no MySQL 5.6.17: Por padrão, o MySQL está configurado assim:

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

Pare o serviço MySQL e adicione as opções ao my.cnf existente:

[client]
default-character-set= utf8mb4

[mysqld]
explicit_defaults_for_timestamp = true
innodb_large_prefix = true
innodb_file_format = barracuda
innodb_file_format_max = barracuda
innodb_file_per_table = true

# Character collation
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci

Instrução SQL CREATE de exemplo:

CREATE TABLE Contacts (
 id INT AUTO_INCREMENT NOT NULL,
 ownerId INT DEFAULT NULL,
 created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
 modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 contact VARCHAR(640) NOT NULL,
 prefix VARCHAR(128) NOT NULL,
 first VARCHAR(128) NOT NULL,
 middle VARCHAR(128) NOT NULL,
 last VARCHAR(128) NOT NULL,
 suffix VARCHAR(128) NOT NULL,
 notes MEDIUMTEXT NOT NULL,
 INDEX IDX_CA367725E05EFD25 (ownerId),
 INDEX created (created),
 INDEX modified_idx (modified),
 INDEX contact_idx (contact),
 PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT=DYNAMIC;
  • Você pode ver o erro # 1709 gerado para INDEX contact_idx (contact)se ROW_FORMAT=DYNAMICfor removido da instrução CREATE.

NOTA: Alterar o índice para limitar os primeiros 128 caracteres contactelimina o requisito de usar o Barracuda comROW_FORMAT=DYNAMIC

INDEX contact_idx (contact(128)),

Observe também: quando diz que o tamanho do campo é VARCHAR(128), isso não é 128 bytes. Você pode usar caracteres de 128, 4 bytes ou 128, caracteres de 1 byte.

Esta INSERTdeclaração deve conter o caractere 'poo' de 4 bytes na linha 2:

INSERT INTO `Contacts` (`id`, `ownerId`, `created`, `modified`, `contact`, `prefix`, `first`, `middle`, `last`, `suffix`, `notes`) VALUES
(1, NULL, '0000-00-00 00:00:00', '2014-08-25 03:00:36', '1234567890', '12345678901234567890', '1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678', '', ''),
(2, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', ''),
(3, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '123💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', '');

Você pode ver a quantidade de espaço usada pela lastcoluna:

mysql> SELECT BIT_LENGTH(`last`), CHAR_LENGTH(`last`) FROM `Contacts`;
+--------------------+---------------------+
| BIT_LENGTH(`last`) | CHAR_LENGTH(`last`) |
+--------------------+---------------------+
|               1024 |                 128 | -- All characters are ASCII
|               4096 |                 128 | -- All characters are 4 bytes
|               4024 |                 128 | -- 3 characters are ASCII, 125 are 4 bytes
+--------------------+---------------------+

No seu adaptador de banco de dados, convém definir o conjunto de caracteres e agrupamento para sua conexão:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

No PHP, isso seria definido para: \PDO::MYSQL_ATTR_INIT_COMMAND

Referências:

Jeremy Postlethwaite
fonte
Mais informações sobre a Wikipedia: Aviões Unicode
Jeremy Postlethwaite
6
utf8mb4_unicode_ci deve absolutamente ser o agrupamento recomendado para novos projetos em 2015.
Trevor Gehman
7
Atualização ... utf8mb4_unicode_520_cié melhor. No futuro, haverá utf8mb4_unicode_800_ci(ou algo parecido), já que o MySQL alcança os padrões Unicode.
Rick James
46

Os agrupamentos afetam como os dados são classificados e como as seqüências de caracteres são comparadas entre si. Isso significa que você deve usar o agrupamento que a maioria de seus usuários espera.

Exemplo da documentação para charset unicode :

utf8_general_citambém é satisfatório para alemão e francês, exceto que 'ß' é igual a 's' e não a 'ss'. Se isso é aceitável para o seu aplicativo, você deve usá- utf8_general_cilo porque é mais rápido. Caso contrário, use utf8_unicode_ciporque é mais preciso.

Portanto, depende da sua base de usuários esperada e de quanto você precisa da classificação correta . Para uma base de usuários em inglês, utf8_general_cideve ser suficiente. Para outros idiomas, como o sueco, foram criados agrupamentos especiais.

Tomalak
fonte
1
eu estava usando utf8_general_ci e levou um par de segundo, enquanto triagem e armscii_general_ci fez isso extremamente quick.Why isso aconteceu mais uma pergunta, O que você acha que agrupamento é usado por sites de redes sociais?
22

Basicamente, depende de como você pensa em uma string.

Eu sempre uso utf8_bin por causa do problema destacado por Guus. Na minha opinião, no que diz respeito ao banco de dados, uma string ainda é apenas uma string. Uma sequência é um número de caracteres UTF-8. Um personagem tem uma representação binária. Por que ele precisa saber o idioma que você está usando? Geralmente, as pessoas estão construindo bancos de dados para sistemas com o escopo de sites multilíngues. Esse é o objetivo de usar UTF-8 como um conjunto de caracteres. Sou um pouco purista, mas acho que os riscos do bug superam fortemente a pequena vantagem que você pode obter na indexação. Quaisquer regras relacionadas ao idioma devem ser feitas em um nível muito superior ao DBMS.

Nos meus livros, "valor" nunca deve em um milhão de anos ser igual a "valor".

Se eu quiser armazenar um campo de texto e fazer uma pesquisa sem distinção entre maiúsculas e minúsculas, usarei funções de string MYSQL com funções PHP como LOWER () e a função php strtolower ().

Phil
fonte
9
Se a comparação binária de strings for a comparação desejada, é claro que você deve usar o agrupamento binário; mas descartar agrupamentos alternativos como um "risco de bug" ou ser simplesmente para conveniência da indexação sugere que você não entende completamente o objetivo de um agrupamento.
precisa saber é o seguinte
13

Para informações textuais UTF-8, você deve usar utf8_general_ciporque ...

  • utf8_bin: compare cadeias pelo valor binário de cada caractere na cadeia

  • utf8_general_ci: compare cadeias usando regras gerais de linguagem e comparações que não diferenciam maiúsculas de minúsculas

aka ele deve tornar a pesquisa e a indexação dos dados mais rápidas / eficientes / mais úteis.

mepcotterell
fonte
12

A resposta aceita sugere definitivamente definitivamente o uso de utf8_unicode_ci e, embora para novos projetos seja ótimo, eu queria relacionar minha experiência contrária recente, para o caso de economizar algum tempo para alguém.

Como utf8_general_ci é o agrupamento padrão para Unicode no MySQL, se você deseja usar utf8_unicode_ci, acaba tendo que especificá-lo em muitos lugares.

Por exemplo, todas as conexões do cliente não apenas têm um conjunto de caracteres padrão (faz sentido para mim), mas também um agrupamento padrão (ou seja, o agrupamento sempre será o padrão utf8_general_ci para unicode).

Provavelmente, se você usar utf8_unicode_ci para seus campos, seus scripts que se conectam ao banco de dados precisarão ser atualizados para mencionar explicitamente o agrupamento desejado - caso contrário, as consultas usando cadeias de texto poderão falhar quando sua conexão estiver usando o agrupamento padrão.

O resultado é que, ao converter um sistema existente de qualquer tamanho para Unicode / utf8, você pode acabar sendo forçado a usar utf8_general_ci devido à maneira como o MySQL lida com os padrões.

George Lund
fonte
8

Para o caso destacado por Guus, eu sugeriria fortemente o uso de utf8_unicode_cs (correspondência estrita entre maiúsculas e minúsculas, ordenação correta na maioria das vezes) em vez de utf8_bin (correspondência estrita, ordenação incorreta).

Se o campo pretender ser pesquisado, em vez de correspondente a um usuário, use utf8_general_ci ou utf8_unicode_ci. Ambos não fazem distinção entre maiúsculas e minúsculas, um corresponderá perdidamente ('ß' é igual a 's' e não a 'ss'). Também existem versões específicas do idioma, como utf8_german_ci, em que a correspondência perdida é mais adequada para o idioma especificado.

[Editar - quase 6 anos depois]

Eu não recomendo mais o conjunto de caracteres "utf8" no MySQL e, em vez disso, recomendo o conjunto de caracteres "utf8mb4". Eles correspondem quase inteiramente, mas permitem um pouco (muito) de caracteres unicode.

Realisticamente, o MySQL deveria ter atualizado o conjunto de caracteres "utf8" e respectivos agrupamentos para corresponder à especificação "utf8", mas, em vez disso, um conjunto de caracteres separado e respectivos agrupamentos para não afetar a designação de armazenamento para aqueles que já usam seu conjunto de caracteres "utf8" incompleto .

SEoF
fonte
5
FYI: utf8_unicode_csnão existe. O único utf8 que diferencia maiúsculas de minúsculas é utf8_bin. O problema está utf8_binclassificando incorreto. Veja: stackoverflow.com/questions/15218077/…
Costa
1
Obrigado pela atualização!
Prometheus
2

No arquivo de upload do banco de dados, adicione a linha a seguir antes de qualquer linha:

SET NAMES utf8;

E seu problema deve ser resolvido.

tapos ghosh
fonte
2
Leia uma pergunta: No passado, configurei o PHP para saída em "UTF-8", mas qual agrupamento corresponde ao MySQL? Eu estou pensando que é um dos UTF-8, mas eu usei utf8_unicode_ci, utf8_general_ci e utf8_bin antes.
Jitesh Sojitra #
5
Esta resposta não tem nada a ver com a pergunta. Além disso, a emissão SET NAMESdireta de uma consulta não informa o cliente sobre a codificação e pode interromper alguns recursos, como instruções preparadas, de maneira muito sutil.
Álvaro González