Si utilizar “SET NOMBRES”

Al leer “MySQL de alto rendimiento” de O’Reilly, me he topado con lo siguiente:

Otra consulta común de basura es SET NAMES UTF8, que es la forma incorrecta de hacer las cosas de todos modos (no cambia el conjunto de caracteres de la biblioteca del cliente; afecta solo al servidor).

Estoy un poco confundido, porque solía poner “SET NAMES utf8” en la parte superior de cada script para que la DB sepa que mis consultas están codificadas en utf8.

¿Puede alguien comentar la cita anterior o, para decirlo de manera más formal, cuáles son sus sugerencias / mejores prácticas para garantizar que el flujo de trabajo de mi base de datos sea compatible con Unicode?

Mis idiomas de destino son PHP y Python si esto es relevante.

mysql_set_charset() sería una opción, pero una opción limitada a la ext/mysql . Para ext/mysqli es mysqli_set_charset y para PDO ::mysql debe especificar un parámetro de conexión.

Como el uso de esta función da como resultado una llamada a la API de MySQL, debe considerarse mucho más rápido que emitir una consulta.

Con respecto al rendimiento, la forma más rápida de garantizar una comunicación basada en UTF-8 entre su script y el servidor MySQL es configurar el servidor MySQL correctamente. Como SET NAMES x es equivalente a

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

mientras que SET character_set_connection = x también ejecuta internamente SET collation_connection = <> también puede configurar estas variables de servidor en su my.ini/cnf .

Tenga en cuenta los posibles problemas con otras aplicaciones que se ejecutan en la misma instancia del servidor MySQL y que requieren algún otro conjunto de caracteres.

TLDR

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

Esta respuesta tiene un énfasis en la biblioteca de pdo de php porque es muy ubicua.

Un breve recordatorio: mysql es una architecture cliente-servidor. Esto es importante porque no solo está el servidor mysql donde se encuentra la base de datos, sino también el controlador de cliente mysql separado, que es lo que se comunica con el servidor mysql (son entidades independientes). Se podría decir que el cliente mysql y el pdo están mezclados.

Cuando utiliza set names utf8 , emite una consulta estándar de SQL a mysql. Mientras que la consulta sql pasa a través de pdo, y luego a través de la biblioteca cliente mysql, y finalmente llega al servidor mysql, SOLO el servidor mysql analiza e interpreta esa consulta sql. Esto es importante porque el servidor mysql no envía ningún mensaje a pdo o el cliente mysql le informa que el conjunto de caracteres y la encoding ha cambiado, por lo que tanto mysql client como pdo ignoran por completo el hecho de que sucedió.

Es importante no hacer esto porque la biblioteca del cliente no puede manejar cadenas si no conoce el conjunto de caracteres actual. La mayoría de las operaciones comunes funcionarán correctamente sin que el cliente sepa el conjunto de caracteres correcto, pero uno que no es el escape de cadenas, como PDO :: quote . Puede pensar que no necesita preocuparse por el hecho de que se escape la cadena manual primitiva porque utiliza declaraciones preparadas, pero la verdad es que la gran mayoría de los usuarios de pdo: mysql, sin saberlo, usan declaraciones preparadas emuladas porque ha sido la configuración predeterminada para pdo: mysql Conductor desde hace mucho tiempo ahora. Una statement preparada emulada no usa declaraciones preparadas de mysql nativas reales como las proporciona la API mysql; en su lugar, php hace el equivalente a llamar a PDO::quote() en todos sus valores, y al reemplazar todos sus marcadores de posición con los valores citados para usted.

Dado que no puede escapar de una cadena correctamente a menos que sepa el conjunto de caracteres que está utilizando, estas declaraciones preparadas emuladas son vulnerables a la inyección de SQL si ha cambiado a ciertos conjuntos de caracteres a través de set names . Independientemente de la posibilidad de inyección de SQL, aún puede romper sus cadenas si utiliza un esquema de escape destinado a un conjunto de caracteres diferente.

Para el controlador pdo mysql, puede especificar el conjunto de caracteres al conectarse, especificándolo en el DSN . La biblioteca cliente y el servidor conocerán el conjunto de caracteres si haces esto, y así las cosas funcionarán como deberían.

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

Pero el escape inadecuado de cadenas no es el único problema. Por ejemplo, también puede tener problemas con el uso de PDO :: bindColumn porque los nombres de las columnas se especifican como cadenas y, por lo tanto, la encoding también importa. Un ejemplo podría ser un nombre de columna llamado ütube (tenga en cuenta la diéresis), y cambie de latin a utf8 través de los nombres de los conjuntos, y luego intente con $stmt->bindColumn('ütube', $var); siendo ütube una cadena codificada en utf8 porque su archivo php está codificado en utf8. No funcionará, deberías codificar la cadena como una variante de latin1 … y ahora tienes todo tipo de locos.

No estoy seguro acerca de py, pero php tiene mysql_set_charset ahora, lo que indica que esta es la “manera preferida de cambiar el conjunto de caracteres [y] usar mysql_query () para ejecutar SET NAMES no se recomienda”. Tenga en cuenta que esta función se introdujo para MySQL 5.0.7, por lo que no funcionará con versiones anteriores.

 mysql_set_charset('utf8', $link); 

Donde $ link es una conexión creada con mysql_connect