Использовать ли "SET NAMES"

Читая "Высокопроизводительный MySQL" от О'Рейли, я наткнулся на следующее

Другим распространенным мусорным запросом является SET NAMES UTF8, который в любом случае является неправильным способом выполнения действий (он не меняет набор символов клиентской библиотеки; он влияет только на сервер).

Я немного сбит с толку, потому что я использовал "SET NAMES utf8" в верхней части каждого скрипта, чтобы БД знала, что мои запросы кодируются в utf8.

Может ли кто-нибудь прокомментировать приведенную выше цитату или, если выразиться более формально, каковы ваши предложения / лучшие практики для обеспечения того, чтобы мой рабочий процесс базы данных учитывал юникод.

Мои целевые языки - php и python, если это актуально.

3 ответа

Решение

mysql_set_charset() будет вариант - но вариант ограничен ext/mysql, За ext/mysqli это mysqli_set_charset и для PDO::mysql вам нужно указать параметр подключения.

Поскольку использование этой функции приводит к вызову MySQL API, его следует рассматривать намного быстрее, чем отправлять запрос.

Что касается производительности, самый быстрый способ обеспечить связь на основе UTF-8 между вашим сценарием и сервером MySQL - это правильно настроить сервер MySQL. Как SET NAMES x эквивалентно

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

в то время как SET character_set_connection = x внутренне также выполняет SET collation_connection = <<default_collation_of_character_set_x>> Вы также можете установить эти переменные сервера статически в вашем my.ini/cnf,

Обратите внимание на возможные проблемы с другими приложениями, работающими на том же экземпляре сервера MySQL и требующими некоторого другого набора символов.

TLDR

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

В этом ответе делается акцент на библиотеке pdo php, потому что она настолько вездесуща.

Краткое напоминание - mysql - это клиент-серверная архитектура. Это важно, потому что есть не только сервер mysql, где находится фактическая база данных, но также есть отдельный клиентский клиент mysql, который взаимодействует с сервером mysql (они являются отдельными объектами). Вы можете сказать, что MySQL-клиент и pdo смешаны.

Когда вы используете set names utf8, вы выдаете стандартный SQL-запрос к MySQL. В то время как запрос sql проходит через pdo, а затем через клиентскую библиотеку mysql и, наконец, достигает сервера mysql, ТОЛЬКО сервер mysql анализирует и интерпретирует этот запрос sql. Это важно, потому что сервер mysql не отправляет никаких сообщений обратно pdo или клиенту mysql, сообщая ему, что набор символов и кодировка изменились, и поэтому клиент mysql и pdo совершенно не знают, что это произошло.

Важно этого не делать, потому что клиентская библиотека не может правильно обрабатывать строки, если она не знает о текущем наборе символов. Наиболее распространенные операции будут работать правильно, если клиент не знает правильный набор символов, но не будет экранирования строки, например PDO:: quote. Вы можете думать, что вам не нужно беспокоиться о ручном экранировании примитивной строки, потому что вы используете подготовленные операторы, но на самом деле подавляющее большинство пользователей pdo: mysql неосознанно используют эмулированные подготовленные операторы, потому что это была настройка по умолчанию для pdo: mysql водитель очень давно. Эмулируемый подготовленный оператор не использует настоящие нативные подготовленные операторы mysql, как это предусмотрено в mysql api; вместо этого php делает эквивалент вызова PDO::quote() на все ваши значения и str_replacing'ing все ваши заполнители с указанными для вас значениями.

Поскольку вы не можете должным образом экранировать строку, если не знаете используемый набор символов, эти эмулированные подготовленные операторы уязвимы для внедрения SQL, если вы переключились на определенные наборы символов с помощью set names, Независимо от возможности внедрения SQL, вы все равно можете разбить ваши строки, если вы используете экранирующую схему, предназначенную для другого набора символов.

Для драйвера pdo mysql вы можете указать набор символов при подключении, указав его в DSN. Если вы это сделаете, клиентская библиотека и сервер будут знать о наборе символов, и поэтому все будет работать так, как должно.

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

Но неправильное экранирование строки - не единственная проблема. Например, у вас также могут возникнуть проблемы с использованием PDO::bindColumn, потому что имена столбцов указываются в виде строк, и поэтому снова имеет значение кодировка. Примером может быть имя столбца с именем ütube(обратите внимание на умлаут), и вы переходите от latin в utf8 с помощью набора имен, а затем вы пытаетесь $stmt->bindColumn('ütube', $var); с ütube будучи строкой в ​​кодировке utf8, потому что ваш php-файл кодирован в кодировке utf8. Это не сработает, вам нужно будет закодировать строку как вариант latin1... и теперь у вас есть все виды сумасшествия.

Не уверен насчет py, но PHP имеет mysql_set_charset Теперь, в котором говорится, что это "предпочтительный способ изменить кодировку [и], используя mysql_query() для выполнения SET NAMES, не рекомендуется". Обратите внимание, что эта функция была введена для MySQL 5.0.7, поэтому она не будет работать с более ранними версиями.

mysql_set_charset('utf8', $link);

Где $link - это соединение, созданное с mysql_connect

Другие вопросы по тегам