PHP/PDO/MySQL: вставка в MEDIUMBLOB хранит плохие данные

У меня есть простое веб-приложение PHP, которое принимает изображения значков через загрузку файлов и сохраняет их в столбце MEDIUMBLOB.

На моей машине (Windows) плюс два Linux-сервера это прекрасно работает. На третьем сервере Linux вставленное изображение повреждено: нечитабельно после SELECT, а длина данных столбца, о котором сообщает функция length() MySQL, примерно на 40% больше, чем размер загруженного файла.

(Каждый сервер подключается к отдельному экземпляру MySQL.)

Конечно, это заставляет меня думать о проблемах с кодировкой и набором символов. В столбцах BLOB нет связанных кодировок, поэтому, скорее всего, наиболее вероятным виновником является PDO и его интерпретация значения параметра для этого столбца.

  • Я пробовал использовать bindValue с PDO:: PARAM_LOB, без эффекта.
  • Я проверил, что изображения принимаются на сервере правильно (т.е. читать их после отправки без проблем), поэтому это определенно проблема с DB/PDO.
  • Я искал очевидные различия в конфигурации между серверами, но я не являюсь экспертом в настройке PHP, поэтому я мог что-то пропустить.

Код вставки выглядит примерно следующим образом:

$imagedata = file_get_contents($_FILES["icon"]["tmp_name"]);
$stmt = $pdo->prepare('insert into foo (theimage) values (:theimage)');
$stmt->bindValue(':theimage', $imagedata, PDO::PARAM_LOB);
$stmt->execute();

Любая помощь будет действительно оценена.

ОБНОВЛЕНИЕ. Шрифт MySQL по умолчанию на проблемном сервере - utf8; это latin1 на других.

Проблема "решена", добавив PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES latin1 COLLATE latin1_general_ci" в конструктор PDO.

Мне кажется, что ошибка плохой дизайн для меня: почему кодировка соединения влияет на данные для двоичного столбца, особенно когда она была идентифицирована как двоичная для самого PDO с PARAM_LOB?

Обратите внимание, что таблицы БД во всех случаях определяются как latin1: это противоречивые кодировки серверов по умолчанию.

Ответ 1

Мне кажется, что это ошибка: почему кодировка соединения влияет на данные для двоичного столбца, особенно когда она была идентифицирована как двоичная для самого PDO с PARAM_LOB?

Я не думаю, что это должно быть ошибкой. Я могу себе представить, что всякий раз, когда клиент разговаривает с сервером и говорит, что следующая команда находится в UTF-8, а серверу требуется его в латинском-1, тогда запрос может быть повторно закодирован перед разбором и исполнением. Таким образом, это проблема кодирования для транспортировки данных. Поскольку весь запрос до синтаксического анализа будет зависеть от этого повторного кодирования, бинарные данные для столбца BLOB также будут изменены.

Из руководства Mysql:

Какой набор символов должен сервер перевести оператор после его получения?

Для этого сервер использует системные переменные character_set_connection и collation_connection. Он преобразует утверждения, отправленные клиентом от character_set_client к character_set_connection (за исключением строковых литералов, у которых есть интродуктор, такой как _latin1 или _utf8). collation_connection важна для сравнения литеральных строк. Для сравнения строк со значениями столбцов объединение collation_connection не имеет значения, поскольку столбцы имеют собственную сортировку, которая имеет более высокий приоритет сортировки.

Или на обратном пути: данные Latin1 из магазина будут преобразованы в UTF-8, потому что клиент сказал серверу, что он предпочитает UTF-8 для транспортировки.

Идентификатор самого PDO, который вы называете, выглядит как нечто совершенно другое:

PDO:: PARAM_LOB сообщает PDO сопоставлять данные как поток, чтобы вы могли манипулировать им с помощью PHP Streams API. (Ссылка)

Я не эксперт по MySQL, но я бы объяснил это так. Клиенту и серверу необходимо согласовать, какие кодировки они используют, и я предполагаю, что они делают это по какой-то причине.