Преобразован ли BLOB с использованием текущей / дефолтной кодировки в MySQL?

  1. У меня есть таблица с полем BLOB.
  2. Кодировка таблицы – Latin1.
  3. Я подключаюсь к БД и «SET CHARACTER SET utf8».
  4. Затем я сохраняю двоичные данные в поле.
  5. Затем я извлекаю данные, и это не то, что я сохранил (поврежден).

Код:

<?php $pdo = new \PDO("mysql:host=127.0.0.1;dbname=***", '***', '***'); $pdo->exec('SET CHARACTER SET utf8'); $sql = "INSERT INTO pdo_blob (the_blob) VALUES(:the_blob)"; $insertStm = $pdo->prepare($sql); $blob = (binary) file_get_contents('/home/***/test.pdf'); $insertStm->bindParam(":the_blob", $blob, \PDO::PARAM_LOB); $insertStm->execute(); $selectStm = $pdo->prepare("SELECT the_blob FROM pdo_blob ORDER BY id DESC LIMIT 1"); $selectStm->execute(); $savedBlob = null; $selectStm->bindColumn(1, $savedBlob, \PDO::PARAM_LOB); $selectStm->fetch(); echo 'equal: ' . ((int) ($blob == $savedBlob)); 

Хороший ответ @mvp!

Но когда моим веб-приложением является UTF-8, а кодировка базы данных – latin1 , я должен установить character_set_client и character_set_results .

Когда я использую SET CHARACTER SET utf8 , я получил описанную проблему с BLOB.

Но когда я использую SET NAMES utf8 он работает!

Короткий ответ:

Просто удалите или запишите строку ниже, и она всегда будет работать независимо от того, какая система кодирования базы данных действительно используется ( utf8 , latin1 и т. Д.):

 $pdo->exec('SET CHARACTER SET utf8'); 

Длительный ответ:

Это не ошибка PDO, это ошибка MySQL.

Когда фактическое кодирование базы данных является latin1 , но вы используете:

 SET CHARACTER SET utf8 

(или наоборот: фактический – utf8 , но вы используете latin1 – важная часть заключается в том, что она отличается ), то, насколько я могу судить, MySQL попытается выполнить преобразование набора кодов для всего трафика между клиентом и сервером (даже для BLOB !).

Если вы НЕ используете инструкцию SET CHARACTER SET , то по умолчанию я вижу, что набор символов для скриптов (PHP / PDO или Perl / DBI) по умолчанию является кодировкой базы данных, и в этом случае не происходит никакого неявного преобразования.

Очевидно, что это автоматическое преобразование – это то, что убивает BLOB, которые не хотят, чтобы какое-либо преобразование произошло.

Я тестировал это как на PHP / PDO, так и на Perl / DBI, и проблема легко воспроизводится: оба будут терпеть неудачу, если вы используете базу данных с кодировкой latin1 и используете SET CHARACTER SET utf8 (или наоборот).

Если вы хотите полностью совместиться с UTF8 , вы должны изменить кодировку своей базы данных, используя:

 ALTER DATABASE mydb CHARSET utf8; 

При этом все будет использовать UTF8 , и BLOB также будут работать нормально.

Минимальным файлом, который вызывает эту проблему с коррупцией, является blob.bin с одним байтом 0xFF . В Linux вы можете создать этот тестовый файл с помощью команды printf :

 printf "0xFF" > blob.bin 

Теперь, тестовые скрипты, которые воспроизводят проблему:

Код проверки PHP:

 <?php $dbh = new PDO("mysql:host=127.0.0.1;dbname=test"); # If database encoding is NOT utf8, uncomment to break it: # $dbh->exec("SET CHARACTER SET utf8"); $blob1 = file_get_contents("blob.bin"); $sth = $dbh->prepare( "INSERT INTO pdo_blob (the_blob) VALUES(:the_blob)" ); $sth->bindParam(":the_blob", $blob1, PDO::PARAM_LOB); $sth->execute(); $sth = $dbh->prepare( "SELECT the_blob FROM pdo_blob ORDER BY id DESC LIMIT 1" ); $sth->execute(); $blob2 = null; $sth->bindColumn(1, $blob2, PDO::PARAM_LOB); $sth->fetch(); if ($blob1 == $blob2) { echo "Equal\n"; } else { echo "Not equal\n"; $arr1 = str_split($blob1); $arr2 = str_split($blob2); $i=0; for ($i=0; $i<count($arr1); $i++) { if ($arr1[$i] != $arr2[$i]) { echo "First diff: " . dechex(ord($arr1[$i])) . " != " . dechex(ord($arr2[$i])) . "\n"; break; } } } ?> 

Код проверки Perl:

 #!/usr/bin/perl -w use strict; use DBI qw(:sql_types); my $dbh = DBI->connect("dbi:mysql:host=127.0.0.1;dbname=test"); # If database encoding is NOT utf8, uncomment to break it: # $dbh->do("SET CHARACTER SET utf8"); open FILE, "blob.bin"; binmode FILE; read(FILE, my $blob1, 100000000); close FILE; my $sth = $dbh->prepare( "INSERT INTO pdo_blob (the_blob) VALUES(?)" ); $sth->bind_param(1, $blob1, SQL_BLOB); $sth->execute(); my ($blob2) = $dbh->selectrow_array( "SELECT the_blob FROM pdo_blob ORDER BY id DESC LIMIT 1" ); print ($blob1 eq $blob2 ? "Equal" : "Not equal") , "\n"; 

Изменить: на WAMP-сервере

Он не работал с PDO API. Вы можете использовать base64_encode() перед вставкой и base64_decode() после извлечения. Он раздувает данные на 33% а конверсия – накладные расходы.

Если MySQLi API являются опцией, то вот какой-то код:

 <?php $mysqli = new mysqli('localhost', 'spark', 'spark123', 'test'); $sql = "INSERT INTO blob_tb (bdata) VALUES(?)"; $insertStm = $mysqli->prepare($sql); $blob = NULL; //necessary $insertStm->bind_param('b', $blob); $blob = (binary) (file_get_contents('favicon.ico')); $insertStm->send_long_data(0, $blob); $insertStm->execute(); $insertStm->close(); $selectStm = $mysqli->prepare("SELECT bdata FROM blob_tb LIMIT 1"); $selectStm->execute(); $selectStm->bind_result($savedBlob); $selectStm->fetch(); $selectStm->close(); $mysqli->close(); echo 'equal: ' . ((int) ($blob == $savedBlob)); // var_dump(($blob), strlen($blob)); // var_dump(($savedBlob), strlen($savedBlob)); // var_dump(get_defined_vars()); ?>