2016-12-14 1 views
15

S'il vous plaît aidez-moi à comprendre comment les caractères multi-octets comme les emoji sont gérés dans les champs MySQL utf8mb4.Comment puis-je effectuer une recherche par emoji dans MySQL en utilisant utf8mb4?

Voir ci-dessous pour un test SQL simple pour illustrer les défis.

/* Clear Previous Test */ 
DROP TABLE IF EXISTS `emoji_test`; 
DROP TABLE IF EXISTS `emoji_test_with_unique_key`; 

/* Build Schema */ 
CREATE TABLE `emoji_test` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `string` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', 
    `status` tinyint(1) NOT NULL DEFAULT '1', 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 
CREATE TABLE `emoji_test_with_unique_key` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `string` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', 
    `status` tinyint(1) NOT NULL DEFAULT '1', 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `idx_string_status` (`string`,`status`) USING BTREE 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 

/* INSERT data */ 
# Expected Result is successful insert for each of these. 
# However some fail. See comments. 
INSERT INTO emoji_test (`string`, `status`) VALUES ('', 1);     # SUCCESS 
INSERT INTO emoji_test (`string`, `status`) VALUES ('', 1);     # SUCCESS 
INSERT INTO emoji_test (`string`, `status`) VALUES ('', 1);     # SUCCESS 
INSERT INTO emoji_test (`string`, `status`) VALUES ('', 1);     # SUCCESS 
INSERT INTO emoji_test_with_unique_key (`string`, `status`) VALUES ('', 1); # SUCCESS 
INSERT INTO emoji_test_with_unique_key (`string`, `status`) VALUES ('', 1); # FAIL: Duplicate entry '?-1' for key 'idx_string_status' 
INSERT INTO emoji_test_with_unique_key (`string`, `status`) VALUES ('', 1); # SUCCESS 
INSERT INTO emoji_test_with_unique_key (`string`, `status`) VALUES ('', 1); # FAIL: Duplicate entry '??-1' for key 'idx_string_status' 

/* Test data */ 

    /* Simple Table */ 
SELECT * FROM emoji_test WHERE `string` IN ('','','',''); # SUCCESS (all 4 are found) 
SELECT * FROM emoji_test WHERE `string` IN ('');      # FAIL: Returns both and 
SELECT * FROM emoji_test WHERE `string` IN ('');      # FAIL: Returns both and 
SELECT * FROM emoji_test;            # SUCCESS (all 4 are found) 

    /* Table with Unique Key */ 
SELECT * FROM emoji_test_with_unique_key WHERE `string` IN ('','','',''); # FAIL: Only 2 are found (due to insert errors above) 
SELECT * FROM emoji_test_with_unique_key WHERE `string` IN ('');      # SUCCESS 
SELECT * FROM emoji_test_with_unique_key WHERE `string` IN ('');      # FAIL: found instead of 
SELECT * FROM emoji_test_with_unique_key;            # FAIL: Only 2 records found (and) 

Je suis intéressé à apprendre ce qui cause les FAIL s ci-dessus et comment je peux contourner ce problème.

Plus précisément:

  1. Pourquoi choisit pour un résultat de retour de caractères multi-octets pour tout caractères multi-octets?
  2. Comment puis-je configurer un index pour gérer les caractères multi-octets au lieu de ??
  3. Pouvez-vous recommander des modifications au deuxième CREATE TABLE (celui avec une clé unique) ci-dessus de manière à ce que toutes les requêtes de test reviennent avec succès?
+4

Comme tout mexicain peut vous le dire, (['TACO' (U + 1F32E)] (http://www.fileformat.info/info/unicode/char/1f32e/index.htm)) et (['HOT PEPPER '(U + 1F336)] (http://www.fileformat.info/info/unicode/char/1f336/index.htm)) sont clairement liés mais différents. Ce doit être la question la plus merveilleusement composée en années. –

+0

En relation: http://stackoverflow.com/questions/38116984/finding-values-case-insensitively-with-emojis: * La solution est d'utiliser MySQL 5.6+ et d'utiliser le collationnement utf8mb4_unicode_520_ci qui ne traite pas tous les caractères de 4 octets comme equal * - Une bonne raison d'éviter les emojis comme mots de passe :) –

+1

@ ÁlvaroGonzález Bien, si c'est un problème pour les mots de passe, alors il y a un plus gros problème avec l'installation donnée, parce que les mots de passe doivent être stockés. Et pour le hashing, ça ne devrait pas être un problème. Mais je ne suggère pas non plus de les utiliser pour les mots de passe. –

Répondre

11

Vous utilisez utf8mb4_unicode_ci pour vos colonnes, donc la vérification est insensible à la casse. Si vous utilisez utf8mb4_bin à la place, puis les emoji et sont correctement identifiés comme des lettres différentes.

Avec WEIGHT_STRING, vous pouvez obtenir les valeurs utilisées pour le tri et la comparaison pour la chaîne d'entrée.

Si vous écrivez:

SELECT 
    WEIGHT_STRING ('' COLLATE 'utf8mb4_unicode_ci'), 
    WEIGHT_STRING ('' COLLATE 'utf8mb4_unicode_ci') 

vous pouvez voir que les deux sont 0xfffd. En Unicode Character Sets ils disent:

Pour les caractères supplémentaires en principe, les poids est le poids pour 0xfffd CARACTÈRE DE REMPLACEMENT.

Si vous écrivez:

SELECT 
    WEIGHT_STRING('' COLLATE 'utf8mb4_bin'), 
    WEIGHT_STRING('' COLLATE 'utf8mb4_bin') 

Vous obtiendrez leur unicode valeurs à la place 0x01f32e et 0x01f336.

Pour d'autres lettres comme Ä, Á et A qui sont égaux si vous utilisez utf8mb4_unicode_ci, la différence peut être vu dans:

SELECT 
    WEIGHT_STRING ('Ä' COLLATE 'utf8mb4_unicode_ci'), 
    WEIGHT_STRING ('A' COLLATE 'utf8mb4_unicode_ci') 

Ceux carte pour le poids 0x0E33

Ä: 00C4 ; [.0E33.0020.0008.0041][.0000.0047.0002.0308] # LATIN CAPITAL LETTER A WITH DIAERESIS; QQCM 
A: 0041 ; [.0E33.0020.0008.0041] # LATIN CAPITAL LETTER A 

Selon à: Difference between utf8mb4_unicode_ci and utf8mb4_unicode_520_ci collations in MariaDB/MySQL? les poids utilisés pour utf8mb4_unicode_ci sont basés sur UCA 4.0.0 parce que les emoji n'apparaissent pas là, le nous mappés ight est 0xfffd

Si vous avez besoin insensible à la casse et compare sortes de lettres régulières avec emoji alors ce problème est résolu en utilisant utf8mb4_unicode_520_ci:

SELECT 
    WEIGHT_STRING('' COLLATE 'utf8mb4_unicode_520_ci'), 
    WEIGHT_STRING('' COLLATE 'utf8mb4_unicode_520_ci') 

il y aura aussi des poids différents pour ceux emoji 0xfbc3f32e et 0xfbc3f336.

+0

C'est incroyable. Le fait de changer l'encodage en 'utf8mb4_bin' dans les' CREATE TABLE's ci-dessus fait que le reste des requêtes de test fonctionne exactement comme prévu. Merci beaucoup. Tout autre aperçu de ce serait apprécié. – Ryan

+1

Pas étonnant que le collation binaire corrige le problème (c'est ce à quoi cela est destiné) mais je ne comprends pas pourquoi deux emojis entièrement différentes seraient considérées comme des variantes du même personnage. Je doute que ce soit intentionnel. –

+0

@ ÁlvaroGonzález une raison similaire pour laquelle 'Ä',' Á' et 'A' sont les mêmes, même s'ils peuvent avoir une prononciation et une signification différentes. Mon premier était que, ils sont traités comme égaux, parce qu'ils sont tous dans la catégorie des aliments, mais il est plus probable que le «ci» vérifie juste s'ils sont emoji. –