web-dev-qa-db-fra.com

Quel est le meilleur classement à utiliser pour MySQL avec PHP?

Je me demande s’il existe un "meilleur" choix de classement dans MySQL pour un site Web général où vous n’êtes pas sûr à 100% de ce qui sera saisi. Je comprends que tous les encodages doivent être les mêmes, tels que MySQL, Apache, le HTML et tout ce qui se trouve à l'intérieur de PHP.

Dans le passé, j’ai défini PHP pour une sortie en "UTF-8", mais quel classement correspond-il dans MySQL? Je pense que c'est l'un des UTF-8, mais j'ai déjà utilisé utf8_unicode_ci, utf8_general_ci et utf8_bin.

692
Darryl Hein

La principale différence est la précision du tri (lors de la comparaison de caractères dans la langue) et les performances. La seule spéciale est utf8_bin, qui sert à comparer des caractères au format binaire.

utf8_general_ci est un peu plus rapide que utf8_unicode_ci, mais moins précis (pour le tri). Le codage spécifique à la langue utf8 (tel que utf8_swedish_ci) contient des règles de langue supplémentaires qui en font le tri le plus précis pour ces langues. La plupart du temps, j'utilise utf8_unicode_ci (je préfère la précision aux petites améliorations de performances), sauf si j'ai une bonne raison de préférer un langage spécifique.

Vous pouvez en savoir plus sur des jeux de caractères unicode spécifiques dans le manuel MySQL - http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html

583
Eran Galperin

Soyez très conscient de ce problème qui peut survenir lors de l’utilisation de utf8_general_ci.

MySQL ne fera pas la distinction entre certains caractères dans les instructions select si le classement utf8_general_ci est utilisé. Cela peut conduire à de très méchants bugs - en particulier par exemple, lorsque des noms d'utilisateur sont impliqués. En fonction de l'implémentation qui utilise les tables de base de données, ce problème pourrait permettre à des utilisateurs malveillants de créer un nom d'utilisateur correspondant à un compte d'administrateur.

Ce problème s’expose tout au moins dans les premières versions de la version 5.x. Je ne sais pas si ce comportement a changé ultérieurement.

Je ne suis pas un administrateur de base de données, mais pour éviter ce problème, j'utilise toujours utf8-bin au lieu d'un code insensible à la casse.

Le script ci-dessous décrit le problème par exemple.

-- 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;
115
Guus

En fait, vous voudrez probablement utiliser utf8_unicode_ci ou utf8_general_ci.

  • utf8_general_ci trie en supprimant tous les accents et trie comme s'il s'agissait d'ASCII
  • utf8_unicode_ci utilise l'ordre de tri Unicode afin qu'il soit trié correctement dans plusieurs langues

Cependant, si vous ne l'utilisez que pour stocker du texte anglais, cela ne devrait pas différer.

112
Vegard Larsen

Il est préférable d'utiliser le jeu de caractères utf8mb4 avec le classement utf8mb4_unicode_ci.

Le jeu de caractères utf8 ne prend en charge qu'une petite quantité de points de code UTF-8, soit environ 6% des caractères possibles. utf8 prend uniquement en charge le plan multilingue de base (BMP). Il y a 16 autres avions. Chaque avion contient 65 536 caractères. utf8mb4 supporte les 17 avions.

MySQL tronquera les caractères UTF-8 sur 4 octets, ce qui entraînera la corruption des données.

Le jeu de caractères utf8mb4 a été introduit dans MySQL 5.5.3 le 2010-03-24.

Certaines des modifications requises pour utiliser le nouveau jeu de caractères ne sont pas triviales:

  • Vous devrez peut-être apporter des modifications à votre adaptateur de base de données d'application.
  • Des modifications devront être apportées à my.cnf, notamment la définition du jeu de caractères, le classement et la commutation de innodb_file_format à Barracuda.
  • Les instructions SQL CREATE peuvent avoir besoin d’inclure: ROW_FORMAT=DYNAMIC
    • DYNAMIC est requis pour les index sur VARCHAR (192) et supérieurs.

REMARQUE: passer de Barracudaà Antelopepeut nécessiter le redémarrage du service MySQL plusieurs fois. innodb_file_format_max ne change pas avant que le service MySQL ait été redémarré à: innodb_file_format = barracuda.

MySQL utilise l'ancien format de fichier InnoDB Antelopename__. Barracudaprend en charge les formats de lignes dynamiques, dont vous aurez besoin si vous ne voulez pas utiliser les erreurs SQL pour créer des index et des clés après avoir basculé sur le jeu de caractères: utf8mb4

  • # 1709 - La taille de la colonne d'index est trop grande. La taille de colonne maximale est de 767 octets.
  • # 1071 - La clé spécifiée était trop longue. la longueur maximale de la clé est de 767 octets

Le scénario suivant a été testé sur MySQL 5.6.17: Par défaut, MySQL est configuré comme suit:

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

Arrêtez votre service MySQL et ajoutez les options à votre fichier my.cnf existant:

[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

Exemple d'instruction CREATE SQL:

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;
  • Vous pouvez voir l'erreur 1709 générée pour INDEX contact_idx (contact) si ROW_FORMAT=DYNAMIC est supprimé de l'instruction CREATE.

REMARQUE: La modification de l'index afin de limiter les 128 premiers caractères de contactélimine la nécessité d'utiliser Barracuda avec ROW_FORMAT=DYNAMIC.

INDEX contact_idx (contact(128)),

Remarque: lorsque la taille du champ est VARCHAR(128), il ne s'agit pas de 128 octets. Vous pouvez utiliser 128, 4 caractères octets ou 128, 1 caractères octets.

Cette instruction INSERTdoit contenir le caractère "caca" de 4 octets dans la ligne 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????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '', '');

Vous pouvez voir la quantité d’espace utilisé par la colonne lastname__:

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

Dans votre adaptateur de base de données, vous voudrez peut-être définir le jeu de caractères et le classement pour votre connexion:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

En PHP, ceci serait défini pour: \PDO::MYSQL_ATTR_INIT_COMMAND

Références:

79

Les classements affectent la manière dont les données sont triées et comment les chaînes sont comparées les unes aux autres. Cela signifie que vous devez utiliser le classement attendu par la plupart de vos utilisateurs.

Exemple tiré de la documentation :

utf8_general_ci est également satisfaisant pour l’allemand et le français, sauf que "ß" est égal à "s" et non à "ss". Si cela est acceptable pour votre application, vous devriez alors utiliser utf8_general_ci car c'est plus rapide. Sinon, utilisez utf8_unicode_ci car il est plus précis.

Donc, cela dépend de votre base d'utilisateurs attendue et de la quantité dont vous avez besoin pour un tri correct . utf8_general_ci devrait suffire pour une base d'utilisateurs anglaise. Pour d'autres langues, comme le suédois, des classements spéciaux ont été créés.

43
Tomalak

Cela dépend essentiellement de la façon dont vous pensez d'une chaîne.

J'utilise toujours utf8_bin à cause du problème mis en évidence par Guus. À mon avis, en ce qui concerne la base de données, une chaîne n'est toujours qu'une chaîne. Une chaîne est un nombre de caractères UTF-8. Un personnage a une représentation binaire, alors pourquoi a-t-il besoin de connaître la langue que vous utilisez? Habituellement, les gens vont construire des bases de données pour des systèmes avec la possibilité de créer des sites multilingues. C'est tout l'intérêt d'utiliser UTF-8 en tant que jeu de caractères. Je suis un peu puriste, mais je pense que les risques de bugs l'emportent largement sur le léger avantage que vous pouvez obtenir sur l'indexation. Toutes les règles relatives aux langues doivent être définies à un niveau beaucoup plus élevé que le SGBD.

Dans mes livres, "valeur" ne devrait jamais dans un million d'années être égal à "valúe".

Si je veux stocker un champ de texte et faire une recherche sans tenir compte de la casse, je vais utiliser les fonctions de chaîne MYSQL avec les fonctions PHP telles que LOWER () et la fonction php strtolower ().

22
Phil

Pour les informations textuelles UTF-8, vous devez utiliser utf8_general_ci car ...

  • utf8_bin: compare les chaînes à la valeur binaire de chaque caractère de la chaîne

  • utf8_general_ci: compare les chaînes en utilisant des règles de langage générales et en utilisant des comparaisons insensibles à la casse

par exemple, la recherche et l'indexation des données seront plus rapides/plus efficaces/plus utiles.

12
mepcotterell

La réponse acceptée suggère assez définitivement d'utiliser utf8_unicode_ci, et alors que pour les nouveaux projets, c'est génial, je voulais raconter ma récente expérience contraire, au cas où cela ferait gagner du temps à quelqu'un.

Comme utf8_general_ci est le classement par défaut pour Unicode dans MySQL, si vous souhaitez utiliser utf8_unicode_ci, vous devez le spécifier dans un lot .

Par exemple, toutes les connexions client ont non seulement un jeu de caractères par défaut (me semble logique), mais également un classement par défaut (c'est-à-dire que le classement sera toujours par défaut à utf8_general_ci pour unicode).

Il est probable que si vous utilisez utf8_unicode_ci pour vos champs, vos scripts qui se connectent à la base de données devront être mis à jour pour mentionner explicitement le classement souhaité. Dans le cas contraire, les requêtes utilisant des chaînes de texte peuvent échouer lorsque votre connexion utilise le classement par défaut.

Le résultat est que lors de la conversion d'un système existant de toute taille en Unicode/utf8, vous pouvez être obligé d'utiliser utf8_general_ci à cause de la façon dont MySQL traite les valeurs par défaut.

11
George Lund

Pour le cas mis en évidence par Guus, je suggère fortement d'utiliser soit utf8_unicode_cs (sensible à la casse, correspondance stricte, ordre correct pour la plupart), à la place de utf8_bin (correspondance stricte, ordre incorrect).

Si le champ est destiné à être recherché, par opposition à la correspondance d'un utilisateur, utilisez utf8_general_ci ou utf8_unicode_ci. Les deux sont insensibles à la casse, l’un correspondra mal ("ß" est égal à "s" et non à "ss"). Il existe également des versions spécifiques à une langue, comme utf8_german_ci, dans lesquelles la perte de correspondance est plus adaptée à la langue spécifiée.

[Edit - presque 6 ans plus tard]

Je ne recommande plus le jeu de caractères "utf8" sur MySQL, mais plutôt le jeu de caractères "utf8mb4". Ils correspondent presque entièrement, mais permettent un peu (beaucoup) plus de caractères unicode.

De manière réaliste, MySQL devrait avoir mis à jour le jeu de caractères "utf8" et les classements respectifs pour correspondre à la spécification "utf8", mais à la place, un jeu de caractères séparé et des classements respectifs afin de ne pas affecter la désignation de stockage pour ceux qui utilisent déjà leur jeu de caractères "utf8" incomplet. .

8
SEoF

J'ai trouvé ces tableaux de classement utiles. http://collation-charts.org/mysql60/ . Je ne sais pas quel est l'utf8_general_ci utilisé.

Par exemple, voici le tableau pour utf8_swedish_ci. Il montre quels caractères il interprète comme les mêmes. http://collation-charts.org/mysql60/mysql604.utf8_swedish_ci.html

5
jiv-e

Dans votre fichier de téléchargement de base de données, ajoutez la ligne suivante avant toute ligne:

SET NAMES utf8;

Et votre problème devrait être résolu.

2
tapos ghosh