¿Cuál es la mejor clasificación para usar para MySQL con PHP? [cerrado]

731

Me pregunto si hay una "mejor" opción para la recopilación en MySQL para un sitio web general donde no está 100% seguro de lo que se ingresará. Entiendo que todas las codificaciones deben ser iguales, como MySQL, Apache, HTML y cualquier cosa dentro de PHP.

En el pasado, configuré PHP para que salga en "UTF-8", pero ¿qué cotejo coincide en MySQL? Estoy pensando que es uno de los caracteres UTF-8 queridos, pero he utilizado utf8_unicode_ci, utf8_general_ciy utf8_binantes.

Darryl Hein
fuente
35
Nota al margen: "utf8" de MySQL no es UTF-8 apropiado (no es compatible con caracteres Unicode de 4 bytes como 𝌆), sin embargo, "utf8mb4" sí lo es. Con utf8, un campo se truncará en la inserción comenzando con el primer carácter Unicode no compatible. mathiasbynens.be/notes/mysql-utf8mb4
basic6
66
Me pregunto si alguna vez necesitaremos 5 bytes para todos esos emojis ... suspiro
Álvaro González
1
Pregunta relacionada: stackoverflow.com/questions/38228335/… "¿Qué clasificación de MySQL coincide exactamente con la comparación de cadenas de PHP?"
William Entriken
Para obtener una descripción general de las opciones sensatas: monolune.com/mysql-utf8-charsets-and-collations-explained
Flux

Respuestas:

618

La principal diferencia es la precisión de la clasificación (al comparar caracteres en el idioma) y el rendimiento. El único especial es utf8_bin, que es para comparar caracteres en formato binario.

utf8_general_cies algo más rápido que utf8_unicode_ci, pero menos preciso (para ordenar). La codificación específica del lenguaje utf8 (como utf8_swedish_ci) contiene reglas de idioma adicionales que las hacen las más precisas para ordenar esos idiomas. La mayoría de las veces lo uso utf8_unicode_ci(prefiero la precisión a las pequeñas mejoras de rendimiento), a menos que tenga una buena razón para preferir un idioma específico.

Puede leer más sobre conjuntos de caracteres Unicode específicos en el manual de MySQL: http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html

Eran Galperin
fuente
44
pequeñas mejoras de rendimiento? Estas seguro acerca de esto ? publib.boulder.ibm.com/infocenter/db2luw/v9r5/index.jsp?topic=/… La clasificación que elija puede afectar significativamente el rendimiento de las consultas en la base de datos.
Adam Ramadhan
62
Esto es para DB2, no MySQL. Además, no hay números concretos o puntos de referencia, por lo que solo lo está basando en la opinión del escritor.
Eran Galperin
3
Tenga en cuenta que si desea usar funciones, hay un error en MySQL (la mayoría de las versiones distribuidas actualmente) donde las funciones siempre devuelven la cadena usando utf8_general_ci, causando problemas si está usando otra clasificación para sus cadenas - vea bugs.mysql.com/ bug.php? id = 24690
El Yobo
1
Desde mi experiencia con diferentes lugares que siempre usaríautf8_unicode_*
Shiplu Mokaddim
11
Actualización: para versiones más nuevas, recomiende utf8mb4y utf8mb4_unicode_520_ci. Estos le dan el resto de los chinos, además de una mejor clasificación.
Rick James
129

En realidad, probablemente quieras usar utf8_unicode_cio utf8_general_ci.

  • utf8_general_ci ordena eliminando todos los acentos y clasificándolos como si fueran ASCII
  • utf8_unicode_ci utiliza el orden de clasificación Unicode, por lo que se ordena correctamente en más idiomas

Sin embargo, si solo está usando esto para almacenar texto en inglés, estos no deberían diferir.

Vegard Larsen
fuente
1
Me gusta tu explicación! Bueno Pero necesito comprender mejor por qué el orden de clasificación Unicode es la mejor manera de ordenar correctamente que eliminar los acentos.
weia design
14
@ Adam realmente depende de tu público objetivo. La clasificación es un problema difícil de localizar correctamente. Por ejemplo, en noruego, las letras Æ Ø Å son los últimos 3 del alfabeto. Con utf8_general_ci, Ø y Å se convierten en O y A, lo que los coloca en una posición completamente incorrecta cuando se ordenan (no estoy seguro de cómo se maneja Æ, ya que es una ligadura, no un carácter acentuado). Este orden de clasificación es diferente en casi cualquier idioma, por ejemplo, el noruego y el sueco tienen diferentes órdenes (y letras ligeramente diferentes que se consideran iguales): Æ Ø Å se clasifica Å Æ Ø (las letras reales son Å Ä Ö). Unicode soluciona esto.
Vegard Larsen
Entonces, lo que estoy diciendo básicamente es que probablemente deberías usar un tipo específico de idioma si puedes, pero en la mayoría de los casos eso no es factible, así que ve a la clasificación general Unicode. Todavía será extraño en algún idioma, pero más correcto que ASCII.
Vegard Larsen
3
@Manatax: con cualquiera de las colaciones utf8_, los datos se almacenan como utf8. La recopilación se trata de qué caracteres se consideran iguales y cómo están ordenados.
frymaster
2
@frymaster - no es cierto, según: mathiasbynens.be/notes/mysql-utf8mb4 "El utf8 de MySQL solo le permite almacenar el 5.88% de todos los puntos de código Unicode posibles"
datos
120

Sea muy, muy consciente de este problema que puede ocurrir al usarlo utf8_general_ci.

MySQL no distinguirá entre algunos caracteres en las sentencias select, si utf8_general_cise utiliza la intercalación. Esto puede conducir a errores muy desagradables, especialmente, por ejemplo, cuando los nombres de usuario están involucrados. Dependiendo de la implementación que use las tablas de la base de datos, este problema podría permitir a los usuarios malintencionados crear un nombre de usuario que coincida con una cuenta de administrador.

Este problema se expone al menos en las primeras versiones 5.x: no estoy seguro de si este comportamiento se modificó más adelante.

No soy un DBA, pero para evitar este problema, siempre uso uno en utf8-binlugar de uno que no distinga entre mayúsculas y minúsculas.

El siguiente script describe el problema con un ejemplo.

-- 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
fuente
36
-1: Esto seguramente se remedia aplicando una clave única a la columna correspondiente. Vería el mismo comportamiento si los dos valores fueran 'value'y 'valUe'. El objetivo de una recopilación es que proporciona reglas (entre otras cosas) cuando dos cadenas se consideran iguales entre sí.
Hammerite
13
Ese es exactamente el problema que estoy tratando de ilustrar: la intercalación hace que dos cosas sean iguales, mientras que en realidad no pretenden ser iguales en absoluto (y, por lo tanto, una restricción única es exactamente lo contrario de lo que querría lograr)
Guus
18
Pero usted lo describe como un "problema" y conduce a "errores" cuando el comportamiento es exactamente lo que se pretende lograr con una recopilación. Su descripción es correcta, pero solo en la medida en que es un error por parte del DBA seleccionar una intercalación inapropiada.
Hammerite
32
El hecho es que, cuando ingresa dos nombres de usuario que se consideran iguales por la clasificación, no se permitirá si configura el nombre de usuario de coloumn como único, ¡lo que, por supuesto, debe hacer!
Estudiante de Hogwarts el
12
Voté tanto esta respuesta como el comentario de @ Hammerite, porque ambos combinados me ayudaron a comprender la compilación.
Nacht - Restablece a Monica el
86

Es mejor usar el juego de caracteres utf8mb4con la intercalación utf8mb4_unicode_ci.

El conjunto de caracteres utf8, solo admite una pequeña cantidad de puntos de código UTF-8, aproximadamente el 6% de los posibles caracteres. utf8solo es compatible con el plano multilingüe básico (BMP). Hay otros 16 aviones. Cada plano contiene 65.536 caracteres. utf8mb4Soporta los 17 planos.

MySQL truncará los caracteres UTF-8 de 4 bytes, resultando en datos corruptos.

El utf8mb4conjunto de caracteres se introdujo en MySQL 5.5.3 el 2010-03-24.

Algunos de los cambios requeridos para usar el nuevo juego de caracteres no son triviales:

  • Es posible que sea necesario realizar cambios en el adaptador de la base de datos de su aplicación.
  • Será necesario realizar cambios en my.cnf, incluida la configuración del conjunto de caracteres, la clasificación y el cambio de innodb_file_format a Barracuda
  • Las instrucciones SQL CREATE pueden necesitar incluir: ROW_FORMAT=DYNAMIC
    • DYNAMIC se requiere para índices en VARCHAR (192) y mayores.

NOTA: Cambiar a Barracudadesde Antelope, puede requerir reiniciar el servicio MySQL más de una vez. innodb_file_format_maxno cambia hasta después de que el servicio MySQL se ha reiniciado a: innodb_file_format = barracuda.

MySQL usa el antiguo Antelopeformato de archivo InnoDB. Barracudaadmite formatos de fila dinámicos, que necesitará si no desea obtener los errores de SQL para crear índices y claves después de cambiar al conjunto de caracteres:utf8mb4

  • # 1709 - El tamaño de la columna de índice es demasiado grande. El tamaño máximo de columna es de 767 bytes.
  • # 1071 - La clave especificada era demasiado larga; la longitud máxima de la clave es de 767 bytes

El siguiente escenario se ha probado en MySQL 5.6.17: de forma predeterminada, MySQL está configurado de esta manera:

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

Detenga su servicio MySQL y agregue las opciones a su 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

Ejemplo de sentencia SQL CREATE:

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;
  • Puede ver el error # 1709 generado INDEX contact_idx (contact)si ROW_FORMAT=DYNAMICse elimina de la instrucción CREATE.

NOTA: Cambiar el índice para limitar a los primeros 128 caracteres contactelimina el requisito de usar Barracuda conROW_FORMAT=DYNAMIC

INDEX contact_idx (contact(128)),

También tenga en cuenta: cuando dice que el tamaño del campo es VARCHAR(128), eso no es 128 bytes. Puede usar 128 caracteres de 4 bytes o 128 caracteres de 1 byte.

Esta INSERTdeclaración debe contener el carácter 'poo' de 4 bytes en la fila 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💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩💩', '', '');

Puede ver la cantidad de espacio utilizado por la lastcolumna:

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
+--------------------+---------------------+

En su adaptador de base de datos, puede configurar el juego de caracteres y la clasificación para su conexión:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

En PHP, esto se establecería para: \PDO::MYSQL_ATTR_INIT_COMMAND

Referencias

Jeremy Postlethwaite
fuente
Más información sobre Wikipedia: Aviones Unicode
Jeremy Postlethwaite
66
utf8mb4_unicode_ci debería ser absolutamente la recopilación recomendada para nuevos proyectos en 2015.
Trevor Gehman
77
Actualización ... utf8mb4_unicode_520_cies mejor. En el futuro, habrá utf8mb4_unicode_800_ci(o algo así), ya que MySQL se pone al día con los estándares Unicode.
Rick James
46

Las intercalaciones afectan cómo se ordenan los datos y cómo se comparan las cadenas entre sí. Eso significa que debe usar la recopilación que la mayoría de sus usuarios esperan.

Ejemplo de la documentación para charset unicode :

utf8_general_citambién es satisfactorio tanto para alemán como para francés, excepto que 'ß' es igual a 's' y no a 'ss'. Si esto es aceptable para su aplicación, debe usarlo utf8_general_ciporque es más rápido. De lo contrario, úselo utf8_unicode_ciporque es más preciso.

Entonces, depende de su base de usuarios esperada y de cuánto necesita una clasificación correcta . Para una base de usuarios en inglés, utf8_general_cidebería ser suficiente, para otros idiomas, como el sueco, se han creado intercalaciones especiales.

Tomalak
fuente
1
yo estaba usando utf8_general_ci y se tomó un par de segundos, mientras que la clasificación y el armscii_general_ci hice muy quick.Why esto ocurrió una última pregunta, ¿Qué opinas de colación, que es utilizado por los sitios de redes sociales?
22

Esencialmente, depende de cómo pienses en una cadena.

Siempre uso utf8_bin debido al problema resaltado por Guus. En mi opinión, en lo que respecta a la base de datos, una cadena sigue siendo solo una cadena. Una cadena es un número de caracteres UTF-8. Un personaje tiene una representación binaria, entonces, ¿por qué necesita saber el idioma que está usando? Por lo general, las personas construirán bases de datos para sistemas con el alcance de sitios multilingües. Este es el objetivo de usar UTF-8 como un conjunto de caracteres. Soy un poco purista, pero creo que el riesgo de error supera con creces la ligera ventaja que puede obtener en la indexación. Cualquier regla relacionada con el lenguaje debe hacerse a un nivel mucho más alto que el DBMS.

En mis libros, "valor" nunca debería ser en un millón de años igual a "valúe".

Si quiero almacenar un campo de texto y hacer una búsqueda que no distinga entre mayúsculas y minúsculas, usaré funciones de cadena MYSQL con funciones PHP como LOWER () y la función php strtolower ().

Phil
fuente
99
Si la comparación binaria de cadenas es su comparación deseada, entonces, por supuesto, debe usar la intercalación binaria; pero descartar intercalaciones alternativas como un "riesgo de error" o simplemente por conveniencia de indexación sugiere que no comprende completamente el punto de una intercalación.
Hammerite
13

Para la información textual UTF-8, debe usar utf8_general_ciporque ...

  • utf8_bin: compara cadenas por el valor binario de cada carácter en la cadena

  • utf8_general_ci: compara cadenas usando reglas de lenguaje general y comparaciones que no distinguen entre mayúsculas y minúsculas

también debería hacer que la búsqueda e indexación de los datos sea más rápida / más eficiente / más útil.

mepcotterell
fuente
12

La respuesta aceptada sugiere de manera bastante definitiva el uso de utf8_unicode_ci, y si bien para proyectos nuevos es genial, quería relatar mi experiencia contraria reciente en caso de que ahorre algo de tiempo a alguien.

Debido a que utf8_general_ci es la clasificación predeterminada para Unicode en MySQL, si desea usar utf8_unicode_ci, entonces tendrá que especificarlo en muchos lugares.

Por ejemplo, todas las conexiones de clientes no solo tienen un conjunto de caracteres predeterminado (tiene sentido para mí) sino también una clasificación predeterminada (es decir, la clasificación siempre será predeterminada a utf8_general_ci para Unicode).

Probablemente, si usa utf8_unicode_ci para sus campos, sus scripts que se conectan a la base de datos deberán actualizarse para mencionar explícitamente la clasificación deseada; de lo contrario, las consultas que usan cadenas de texto pueden fallar cuando su conexión usa la clasificación predeterminada.

El resultado es que al convertir un sistema existente de cualquier tamaño a Unicode / utf8, puede terminar siendo obligado a usar utf8_general_ci debido a la forma en que MySQL maneja los valores predeterminados.

George Lund
fuente
8

Para el caso resaltado por Guus, sugeriría encarecidamente usar utf8_unicode_cs (mayúsculas y minúsculas, coincidencia estricta, ordenando correctamente en su mayor parte) en lugar de utf8_bin (coincidencia estricta, orden incorrecta).

Si el campo está destinado a ser buscado, en lugar de coincidir con un usuario, utilice utf8_general_ci o utf8_unicode_ci. Ambos no distinguen entre mayúsculas y minúsculas, uno coincidirá con pérdida ('ß' es igual a 's' y no a 'ss'). También hay versiones específicas del idioma, como utf8_german_ci, donde la coincidencia de pérdida es más adecuada para el idioma especificado.

[Editar - casi 6 años después]

Ya no recomiendo el juego de caracteres "utf8" en MySQL, y en su lugar recomiendo el juego de caracteres "utf8mb4". Coinciden casi por completo, pero permiten un poco (mucho) más caracteres unicode.

Siendo realistas, MySQL debería haber actualizado el conjunto de caracteres "utf8" y las colaciones respectivas para que coincidan con la especificación "utf8", pero en su lugar, un conjunto de caracteres separado y colaciones respectivas para no afectar la designación de almacenamiento para aquellos que ya usan su conjunto de caracteres "utf8" incompleto .

SEoF
fuente
55
FYI: utf8_unicode_csno existe. La única utf8 sensible a mayúsculas y minúsculas es utf8_bin. El problema es que la utf8_binclasificación es incorrecta. Ver: stackoverflow.com/questions/15218077/…
Costa
1
Gracias por actualizar!
Prometeo
2

En el archivo de carga de la base de datos, agregue la siguiente línea antes de cualquier línea:

SET NAMES utf8;

Y tu problema debería resolverse.

tapos ghosh
fuente
2
Lea una pregunta: en el pasado, configuré PHP para que salga en "UTF-8", pero ¿qué cotejo coincide con esto en MySQL? Estoy pensando que es uno de los UTF-8, pero he usado utf8_unicode_ci, utf8_general_ci y utf8_bin antes.
Jitesh Sojitra
55
Esta respuesta no tiene nada que ver con la pregunta. Además, emitir una SET NAMESconsulta directamente no le permite al cliente conocer la codificación y puede romper ciertas características, como las declaraciones preparadas, de una manera muy sutil.
Álvaro González