web-dev-qa-db-fra.com

Utiliser ou non "SET NAMES"

En lisant "MySQL haute performance" de O'Reilly, je suis tombé sur ce qui suit

Une autre requête courante est SET NAMES UTF8, ce qui est de toute façon la mauvaise façon de faire les choses (cela ne change pas le jeu de caractères de la bibliothèque cliente; cela affecte uniquement le serveur).

Je suis un peu confus, car j'avais l'habitude de mettre "SET NAMES utf8" en haut de chaque script pour faire savoir à la base de données que mes requêtes sont encodées en utf8.

Quelqu'un peut-il commenter la citation ci-dessus ou, pour le dire plus formellement, quelles sont vos suggestions/meilleures pratiques pour vous assurer que mon flux de travail de base de données est compatible avec l'Unicode.

Mes langues cibles sont php et python si cela est pertinent.

60
user187291

mysql_set_charset() serait une option - mais une option limitée à ext/mysql . Pour ext/mysqli c'est mysqli_set_charset et pour PDO::mysql vous devez spécifier un paramètre de connexion.

Étant donné que l'utilisation de cette fonction entraîne un appel à l'API MySQL, elle doit être considérée beaucoup plus rapidement que l'émission d'une requête.

En ce qui concerne les performances, le moyen le plus rapide d'assurer une communication basée sur UTF-8 entre votre script et le serveur MySQL consiste à configurer correctement le serveur MySQL. Comme SET NAMES x est équivalent à

SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;

tandis que SET character_set_connection = x exécute également en interne SET collation_connection = <<default_collation_of_character_set_x>> vous pouvez également définir ces variables de serveur statiquement dans votre my.ini/cnf.

Veuillez être conscient des problèmes possibles avec d'autres applications fonctionnant sur la même instance de serveur MySQL et nécessitant un autre jeu de caractères.

30
Stefan Gehrig

TLDR

// The key is the "charset=utf8" part.
$dsn = 'mysql:Host=localhost;dbname=testdb;charset=utf8';
$dbh = new PDO($dsn, 'user', 'pass');

Cette réponse met l'accent sur la bibliothèque pdo de php car elle est tellement omniprésente.

Petit rappel - mysql est une architecture client-serveur. C'est important car il n'y a pas seulement le serveur mysql où se trouve la base de données, mais il y a aussi le pilote client mysql séparé, qui est la chose qui parle au serveur mysql (ce sont des entités distinctes). Vous pourriez en quelque sorte dire que le client mysql et pdo sont mélangés.

Lorsque vous utilisez set names utf8, Vous émettez une requête SQL standard vers mysql. Alors que la requête sql passe par pdo, puis par la bibliothèque cliente mysql, puis finalement elle atteint le serveur mysql, SEUL le serveur mysql analyse et interprète cette requête sql. Ceci est important car le serveur mysql n'envoie aucun message à pdo ou au client mysql pour lui faire savoir que le jeu de caractères et l'encodage ont changé, et donc le client mysql et pdo ignorent totalement le fait que cela s'est produit.

Il est important de ne pas le faire car la bibliothèque cliente ne peut pas gérer correctement les chaînes si elle ne connaît pas le jeu de caractères actuel. La plupart des opérations courantes fonctionneront correctement sans que le client connaisse le jeu de caractères correct, mais celui qui ne le sera pas est l'échappement de chaîne, comme PDO :: quote . Vous pensez peut-être que vous n'avez pas à vous soucier d'un tel échappement manuel de chaînes primitives parce que vous utilisez des instructions préparées, mais la vérité est la grande majorité des utilisateurs de pdo: mysql utilisent inconsciemment instructions préparées émulées parce que cela a été le paramètre par défaut pour le pilote pdo: mysql depuis très longtemps maintenant. Une instruction préparée émulée n'utilise pas de véritables instructions natives préparées par mysql comme le fournit l'api mysql; à la place, php fait l'équivalent d'appeler PDO::quote() sur toutes vos valeurs, et de str_replacing'ing tous vos espaces réservés avec les valeurs citées pour vous.

Comme vous ne pouvez pas correctement échapper une chaîne à moins de connaître le jeu de caractères que vous utilisez, ces instructions préparées émulées sont vulnérables à l'injection SQL si vous avez changé pour certains jeux de caractères via set names. Quelle que soit la possibilité d'injection SQL, vous pouvez toujours casser vos chaînes si vous utilisez un schéma d'échappement destiné à un jeu de caractères différent.

Pour le pilote pdo mysql, vous pouvez spécifier le jeu de caractères lorsque vous vous connectez, en en le spécifiant dans le DSN . La bibliothèque cliente et le serveur seront tous deux conscients du jeu de caractères si vous faites cela, et donc les choses fonctionneront comme elles le devraient.

// The key is the "charset=utf8" part.
$dsn = 'mysql:Host=localhost;dbname=testdb;charset=utf8';
$dbh = new PDO($dsn, 'user', 'pass');

Mais un échappement incorrect des chaînes n'est pas le seul problème. Par exemple, vous pouvez également rencontrer des problèmes lors de l'utilisation de PDO :: bindColumn car les noms de colonne sont spécifiés sous forme de chaînes, et donc le codage est important. Un exemple peut être un nom de colonne nommé ütube (Notez le tréma), et vous passez de latin à utf8 Via des noms de jeu, puis vous essayez de $stmt->bindColumn('ütube', $var); avec ütube étant une chaîne encodée en utf8 car votre fichier php est encodé en utf8. Cela ne fonctionnera pas, vous devrez encoder la chaîne en variante latin1 ... et maintenant vous avez toutes sortes de fous.

26
goat

Pas sûr de py, mais php a mysql_set_charset maintenant, qui indique que c'est la "façon préférée de changer le jeu de caractères [et] en utilisant mysql_query () pour exécuter SET NAMES n'est pas recommandé." Notez que cette fonction a été introduite pour MySQL 5.0.7, donc elle ne fonctionnera pas avec les versions antérieures.

mysql_set_charset('utf8', $link);

Où $ link est une connexion créée avec mysql_connect

9
typeoneerror