Quelle est la différence entre utf8_general_ci et utf8_unicode_ci?

1063

Entre utf8_general_ciet utf8_unicode_ci, y a-t-il des différences de performances?

KahWee Teng
la source
6
Si vous le souhaitez utf8[mb4]_unicode_ci, vous aimerez peut- être utf8[mb4]_unicode_520_ciencore plus.
Rick James
8
Je ne sais pas ce que j'en pense - au lieu de corriger leur implémentation pour suivre la dernière norme Unicode, ils conservent la version obsolète par défaut et les gens doivent ajouter "520" pour utiliser la bonne maintenant. Et ce n'est pas compatible en amont et en aval car vous ne pouvez pas utiliser la version "520" sur les anciennes versions de MySQL. Pourquoi ne pouvaient-ils pas simplement mettre à jour leur classement existant? Idem avec "mb4", vraiment. Quel code dépendait vraiment de l'ancien comportement limité / obsolète pour justifier de le conserver comme valeur par défaut?
thomasrutter
7
Encore mieux est la valeur par défaut de 8.0 utf8mb4_0900_ai_ci.
Rick James

Réponses:

1591

Ces deux classements sont tous deux destinés au codage de caractères UTF-8. Les différences résident dans la manière dont le texte est trié et comparé.

Remarque: Dans MySQL, vous devez utiliser utf8mb4plutôt que utf8. Confusément, utf8est une implémentation UTF-8 défectueuse des premières versions de MySQL qui ne reste que pour la compatibilité descendante. La version fixe a reçu le nom utf8mb4.

Remarque: Les versions plus récentes de MySQL ont mis à jour les règles de tri Unicode, disponibles sous des noms tels que utf8mb4_0900_ai_ci des règles équivalentes basées sur Unicode 9.0 - et sans _general variante équivalente . Les personnes qui lisent ceci maintenant devraient probablement utiliser l'une de ces nouvelles collations au lieu de _unicode ou _general . Une grande partie de ce qui est écrit ci-dessous n'a plus grand intérêt si vous pouvez utiliser l'un des classements les plus récents à la place.

Différences clés

  • utf8mb4_unicode_ci est basé sur les règles Unicode officielles pour le tri et la comparaison universels, qui trient avec précision dans un large éventail de langues.

  • utf8mb4_general_ciest un ensemble simplifié de règles de tri qui vise à faire du mieux qu'il peut tout en prenant de nombreux raccourcis destinés à améliorer la vitesse. Il ne suit pas les règles Unicode et entraînera un tri ou une comparaison indésirable dans certaines situations, comme lors de l'utilisation de langues ou de caractères particuliers.

    Sur les serveurs modernes, cette amélioration des performances sera presque négligeable. Il a été conçu à une époque où les serveurs avaient une infime fraction des performances CPU des ordinateurs d'aujourd'hui.

Avantages de utf8mb4_unicode_ciplusutf8mb4_general_ci

utf8mb4_unicode_ci, qui utilise les règles Unicode pour le tri et la comparaison, utilise un algorithme assez complexe pour un tri correct dans un large éventail de langues et lors de l'utilisation d'un large éventail de caractères spéciaux. Ces règles doivent tenir compte des conventions spécifiques aux langues; tout le monde ne trie pas ses caractères dans ce que nous appellerions «l'ordre alphabétique».

En ce qui concerne les langues latines (c'est-à-dire "européennes"), il n'y a pas beaucoup de différence entre le tri Unicode et le utf8mb4_general_citri simplifié dans MySQL, mais il y a encore quelques différences:

  • Par exemple, le classement Unicode trie "ß" comme "ss" et "Œ" comme "OE" comme le voudraient normalement les personnes utilisant ces caractères, alors qu'il les utf8mb4_general_citrie comme des caractères uniques (vraisemblablement comme "s" et "e" respectivement) .

  • Certains caractères Unicode sont définis comme ignorables, ce qui signifie qu'ils ne doivent pas compter dans l'ordre de tri et que la comparaison doit passer au caractère suivant à la place. utf8mb4_unicode_ciles gère correctement.

Dans les langues non latines, telles que les langues asiatiques ou les langues avec différents alphabets, il peut y avoir beaucoup plus de différences entre le tri Unicode et le utf8mb4_general_citri simplifié . L'adéquation de utf8mb4_general_cidépendra fortement de la langue utilisée. Pour certaines langues, ce sera tout à fait insuffisant.

Que devez-vous utiliser?

Il n'y a presque certainement aucune raison de l'utiliser utf8mb4_general_ci, car nous avons laissé le point où la vitesse du processeur est suffisamment faible pour que la différence de performance soit importante. Votre base de données sera presque certainement limitée par d'autres goulots d'étranglement que celui-ci.

Dans le passé, certaines personnes recommandaient d'utiliser utf8mb4_general_ci sauf lorsque le tri précis allait être suffisamment important pour justifier le coût des performances. Aujourd'hui, ce coût de performance a pratiquement disparu et les développeurs traitent l'internationalisation plus au sérieux.

Il y a un argument à faire valoir que si la vitesse est plus importante pour vous que la précision, vous pouvez tout aussi bien ne pas faire de tri du tout. Il est trivial de rendre un algorithme plus rapide si vous n'en avez pas besoin pour être précis. C'est donc utf8mb4_general_ciun compromis qui n'est probablement pas nécessaire pour des raisons de vitesse et qui ne convient probablement pas non plus pour des raisons de précision.

Une autre chose que j'ajouterai est que même si vous savez que votre application ne prend en charge que la langue anglaise, elle peut encore avoir besoin de traiter les noms des personnes, qui peuvent souvent contenir des caractères utilisés dans d'autres langues dans lesquelles il est tout aussi important de trier correctement . L'utilisation des règles Unicode pour tout contribue à assurer la tranquillité d'esprit que les personnes Unicode très intelligentes ont travaillé très dur pour que le tri fonctionne correctement.

Que signifient les pièces

Premièrement, ciest pour le tri et la comparaison insensibles à la casse . Cela signifie qu'il convient aux données textuelles et que la casse n'est pas importante. Les autres types de classement sont cs(sensibles à la casse) pour les données textuelles où la casse est importante et bin, lorsque le codage doit correspondre, bit pour bit, ce qui convient aux champs qui sont vraiment des données binaires codées (y compris, par exemple, Base64). Le tri sensible à la casse conduit à des résultats étranges et la comparaison sensible à la casse peut entraîner des valeurs en double ne différant que dans la casse des lettres, de sorte que les classements sensibles à la casse tombent en disgrâce pour les données textuelles - si la casse est importante pour vous, alors une ponctuation autrement ignorable et ainsi de suite est probablement également significatif, et un classement binaire pourrait être plus approprié.

Ensuite, unicodeou generalfait référence aux règles de tri et de comparaison spécifiques - en particulier, la façon dont le texte est normalisé ou comparé. Il existe de nombreux ensembles de règles différents pour l'encodage des caractères utf8mb4, unicodeet generaldeux d'entre eux tentent de bien fonctionner dans toutes les langues possibles plutôt que dans une langue spécifique. Les différences entre ces deux ensembles de règles font l'objet de cette réponse. Notez que unicodeutilise les règles d'Unicode 4.0. Les versions récentes de MySQL ajoutent les ensembles de unicode_520règles en utilisant les règles d'Unicode 5.2 et 0900(en supprimant la partie "unicode_") en utilisant les règles d'Unicode 9.0.

Et enfin, utf8mb4c'est bien sûr l'encodage de caractères utilisé en interne. Dans cette réponse, je ne parle que des encodages basés sur Unicode.

thomasrutter
la source
218
@KahWeeTeng Vous ne devriez jamais, jamais utiliser utf8_general_ci: cela ne fonctionne tout simplement pas. C'est un retour au mauvais vieux temps de la stooopeeedity ASCII d'il y a cinquante ans. La correspondance insensible à la casse Unicode ne peut pas être effectuée sans la carte de casse de l'UCD. Par exemple, «Σίσυφος» contient trois sigmas différents; ou comment les minuscules de «TSCHüẞ» sont «tschüβ», mais les majuscules de «tschüβ» sont «TSCHÜSS». Vous pouvez avoir raison ou être rapide. Par conséquent, vous devez utiliser utf8_unicode_ci, car si vous ne vous souciez pas de l'exactitude, il est trivial de le rendre infiniment rapide.
tchrist
7
Après avoir lu ceci, j'ai également découvert que utf8_unicode_ci considérera tous les caractères ayant le même poids de classement comme égaux à des fins de comparaison d'égalité. Cela conduit à des cas où "か" == "が"ou "ǽ" == "æ". Pour le tri, cela a du sens mais pourrait être surprenant lors de la sélection via des égalités ou du traitement d'indices uniques - bugs.mysql.com/bug.php?id=16526
Mat Schaffer
4
@DanHorvat La seule raison pratique de vous limiter au sous-ensemble plus ancien et plus limité d'Unicode de MySQL est si vous avez une ancienne version de MySQL qui ne prend pas en charge l'utf8mb4 plus complet. 5.5.3 a plus de 5 ans. J'apprécie que Plesk s'exécute sur un calendrier MySQL différent, mais la plupart des distributions sont désormais sur MySQL 5.5 et Plesk 11.x prend en charge MySQL 5.5 si vous mettez à jour ses composants.
thomasrutter
22
Je ne serais pas d'accord pour dire que l'utilisation de la nouvelle variante plus conforme aux normes est une mauvaise pratique, et je pense qu'il est incendiaire d'appeler les gens de mauvais développeurs pour quelque chose comme ça. Vous voudrez peut-être également noter que ma réponse en l'état dit " dans les nouvelles versions de MySQL, utilisez utf8mb4, plutôt que utf8", souligne la mienne.
thomasrutter
24
@DanHorvat utf8mb4est le seul choix correct . Avec utf8vous, vous êtes coincé dans une variante UTF8 de MySQL uniquement sur 3 octets que seuls MySQL (et MariaDB) savent quoi faire. Le reste du monde utilise UTF8, qui peut contenir jusqu'à 4 octets par caractère . Les développeurs MySQL ont mal nommé leur encodage homebrew utf8et pour ne pas briser la compatibilité descendante, ils doivent maintenant se référer au vrai UTF8 utf8mb4.
Stijn de Witt
162

Je voulais savoir quelle est la différence de performances entre l'utilisation de utf8_general_ciet utf8_unicode_ci, mais je n'ai trouvé aucun benchmark répertorié sur Internet, j'ai donc décidé de créer moi-même des benchmarks.

J'ai créé un tableau très simple avec 500 000 lignes:

CREATE TABLE test(
  ID INT(11) DEFAULT NULL,
  Description VARCHAR(20) DEFAULT NULL
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci;

Ensuite, je l'ai rempli de données aléatoires en exécutant cette procédure stockée:

CREATE PROCEDURE randomizer()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE random CHAR(20) ;
  theloop: loop
    SET random = CONV(FLOOR(RAND() * 99999999999999), 20, 36);
    INSERT INTO test VALUES (i+1, random);
    SET i=i+1;
    IF i = 500000 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END

J'ai ensuite créé les procédures stockées suivantes pour comparer les méthodes simples SELECT, SELECTavec LIKEet de tri ( SELECTavec ORDER BY):

CREATE PROCEDURE benchmark_simple_select()
BEGIN
  DECLARE i INT DEFAULT 0;
  theloop: loop
    SELECT *
    FROM test
    WHERE Description = 'test' COLLATE utf8_general_ci;
    SET i = i + 1;
    IF i = 30 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END;

CREATE PROCEDURE benchmark_select_like()
BEGIN
  DECLARE i INT DEFAULT 0;
  theloop: loop
    SELECT *
    FROM test
    WHERE Description LIKE '%test' COLLATE utf8_general_ci;
    SET i = i + 1;
    IF i = 30 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END;

CREATE PROCEDURE benchmark_order_by()
BEGIN
  DECLARE i INT DEFAULT 0;
  theloop: loop
    SELECT *
    FROM test
    WHERE ID > FLOOR(1 + RAND() * (400000 - 1))
    ORDER BY Description COLLATE utf8_general_ci LIMIT 1000;
    SET i = i + 1;
    IF i = 10 THEN
      LEAVE theloop;
    END IF;
  END LOOP theloop;
END;

Dans les procédures stockées ci-dessus, le utf8_general_ciclassement est utilisé, mais bien sûr, pendant les tests, j'ai utilisé à la fois utf8_general_ciet utf8_unicode_ci.

J'ai appelé chaque procédure stockée 5 fois pour chaque classement (5 fois pour utf8_general_ciet 5 fois pour utf8_unicode_ci), puis calculé les valeurs moyennes.

Mes résultats sont:

benchmark_simple_select()

  • avec utf8_general_ci: 9,957 ms
  • avec utf8_unicode_ci: 10,271 ms

Dans cet indice de référence, l'utilisation utf8_unicode_ciest plus lente que utf8_general_cide 3,2%.

benchmark_select_like()

  • avec utf8_general_ci: 11,441 ms
  • avec utf8_unicode_ci: 12 811 ms

Dans cette référence, l'utilisation utf8_unicode_ciest plus lente que utf8_general_cide 12%.

benchmark_order_by()

  • avec utf8_general_ci: 11 944 ms
  • avec utf8_unicode_ci: 12 887 ms

Dans cet indice de référence, l'utilisation utf8_unicode_ciest plus lente que utf8_general_cide 7,9%.

codeur de nuit
la source
16
Belle référence, merci du partage. Je reçois des chiffres sensiblement similaires (MySQL v5.6.12 sous Windows): 10%, 4%, 8%. Je suis d'accord: le gain de performances de utf8_general_ciest tout simplement trop minime pour être utile.
RandomSeed
10
1) Mais cette référence ne devrait-elle pas générer des résultats similaires pour les deux collations par définition? Je veux dire CONV(FLOOR(RAND() * 99999999999999), 20, 36)ne génère que de l'ASCII et aucun caractère Unicode à traiter par les algorithmes des classements. 2) Description = 'test' COLLATE ...et Description LIKE 'test%' COLLATE ...ne traite qu'une seule chaîne ("test") au moment de l'exécution, n'est-ce pas? 3) Dans les applications réelles, les colonnes utilisées dans l'ordre seraient probablement indexées, et la vitesse d'indexation sur différents classements avec du texte réel non ASCII pourrait différer.
Halil Özgür
2
@ HalilÖzgür - votre point est partiellement faux. Je suppose qu'il ne s'agit pas de la valeur du point de code à l'extérieur de l'ASCII (que general_ci gérerait correctement), mais de fonctionnalités spécifiques, comme le traitement des trémas écrits comme "Uml ea ute" ou de telles subtilités.
Tomasz Gandor
38

Ce post le décrit très bien.

En bref: utf8_unicode_ci utilise l'algorithme de classement Unicode tel que défini dans les normes Unicode, tandis que utf8_general_ci est un ordre de tri plus simple qui se traduit par des résultats de tri "moins précis".

Michael Madsen
la source
1
Merci. c'était mon impression. je vais prendre le coup de la performance :)
onassar
7
Si vous ne vous souciez pas de l'exactitude, il est trivial de rendre n'importe quel algorithme infiniment rapide. Utilisez simplement utf8_unicode_ciet prétendez que l'autre n'existe pas.
tchrist
1
@tchrist mais si vous vous souciez d'un certain équilibre entre la justesse et la vitesse, utf8_general_cipeut-être pour vous
Shelvacu
@tchrist Ne devenez jamais programmeur de jeux;)
Stijn de Witt
1
@onassar - MySQL 8.0 prétend avoir considérablement amélioré les performances de tous les classements.
Rick James
9

Voir le manuel mysql, section Jeux de caractères Unicode :

Pour tout jeu de caractères Unicode, les opérations effectuées à l'aide du classement _general_ci sont plus rapides que celles du classement _unicode_ci. Par exemple, les comparaisons pour le classement utf8_general_ci sont plus rapides, mais légèrement moins correctes, que les comparaisons pour utf8_unicode_ci. La raison en est que utf8_unicode_ci prend en charge les mappages tels que les extensions; c'est-à-dire lorsqu'un caractère se compare comme étant égal à des combinaisons d'autres caractères. Par exemple, en allemand et dans d'autres langues, «ß» est égal à «ss». utf8_unicode_ci prend également en charge les contractions et les caractères ignorables. utf8_general_ci est un classement hérité qui ne prend pas en charge les extensions, les contractions ou les caractères ignorables. Il ne peut faire que des comparaisons un à un entre les caractères.

Donc pour résumer, utf_general_ci utilise un ensemble de comparaisons plus petit et moins correct (selon la norme) que utf_unicode_ci qui devrait implémenter la norme entière. L'ensemble general_ci sera plus rapide car il y a moins de calcul à faire.

Dana la saine
la source
18
Il n'y a pas de «légèrement moins correct». La correction est une caractéristique booléenne; il n'admet pas de modificateurs de degré. Il suffit d'utiliser utf8_unicode_ciet de faire semblant que la version cassée du buggy n'existe pas.
tchrist
2
J'ai eu des problèmes pour obtenir 5.6.15 pour prendre le paramètre collation_connection, et il s'avère que vous devez le passer dans la ligne SET comme 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'. Nous remercions Mathias Bynens pour la solution, voici son guide très utile: mathiasbynens.be/notes/mysql-utf8mb4
Steve Hibbert
4
@tchrist Le problème de dire que l'exactitude est booléenne est qu'elle ne prend pas en compte les situations qui ne reposent pas sur l'exactitude absolue. Votre point sous-jacent n'est pas invalide et je n'essaie pas de profiter des avantages de general_ci, mais votre déclaration générale sur l'exactitude est facilement réfutée. Je le fais au quotidien dans mon métier. Mis à part la comédie, Stuart a un bon point ici .
Anthony
5
Avec la géolocalisation ou le développement de jeux, nous échangeons tout le temps l'exactitude et la performance. Et bien sûr, l'exactitude est un vrai nombre entre 0et 1pas un booléen. :) Par exemple, la sélection de points géographiques dans un cadre de délimitation est une approximation des «points à proximité» qui n'est pas aussi bonne que le calcul de la distance entre le point et le point de référence et le filtrage sur celui-ci. Mais les deux sont une approximation et en fait, l'exactitude complète n'est généralement pas réalisable. Voir le paradoxe du littoral et IEEE 754
Stijn de Witt
4
TL; DR : Veuillez fournir un programme qui imprime le résultat correct pour1/3
Stijn de Witt
7

En bref:

Si vous avez besoin d'un meilleur ordre de tri - utilisez utf8_unicode_ci(c'est la méthode préférée),

mais si vous êtes totalement intéressé par les performances - utilisez utf8_general_ci, mais sachez que c'est un peu dépassé.

Les différences en termes de performances sont très légères.

simhumileco
la source
1
Les deux sont obsolètes maintenant - voir la réponse acceptée pour en savoir plus
thomasrutter
OK, merci @thomasrutter
simhumileco
6

Quelques détails (PL)

Comme nous pouvons le lire ici ( Peter Gulutzan ), il y a une différence dans le tri / comparaison de la lettre polonaise "Ł" (L avec trait - html esc:) Ł(minuscule: "ł" - html esc:) ł- nous avons l'hypothèse suivante:

utf8_polish_ci      Ł greater than L and less than M
utf8_unicode_ci     Ł greater than L and less than M
utf8_unicode_520_ci Ł equal to L
utf8_general_ci     Ł greater than Z

En polonais, la lettre Łest après la lettre Let avant M. Aucun de ces codages n'est meilleur ou pire - cela dépend de vos besoins.

Kamil Kiełczewski
la source
1

Il y a deux grandes différences entre le tri et la correspondance des caractères:

Tri :

  • utf8mb4_general_ci supprime tous les accents et les trie un par un, ce qui peut créer des résultats de tri incorrects.
  • utf8mb4_unicode_ci trie précis.

Correspondance de caractères

Ils correspondent aux caractères différemment.

Par exemple, en utf8mb4_unicode_civous avez i != ı, mais en utf8mb4_general_cielle tient ı=i.

Par exemple, imaginez que vous avez une dispute avec name="Yılmaz". alors

select id from users where name='Yilmaz';

retournerait la ligne si la colocalisation est utf8mb4_general_ci, mais si elle est colocalisée avec utf8mb4_unicode_cielle ne retournerait pas la ligne!

D'autre part , nous avons que a=ªet ß=ssdans ce utf8mb4_unicode_ciqui est pas le cas utf8mb4_general_ci. Alors imaginez que vous avez une dispute avec name="ªßi", alors

select id from users where name='assi';

renvoie la ligne si la colocalisation est utf8mb4_unicode_ci, mais ne renvoie pas de ligne si la collocation est définie sur utf8mb4_general_ci.

Une liste complète des correspondances pour chaque collocation peut être trouvée ici .

Adam
la source