Код:
<?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 $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; } } } ?>
#!/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()); ?>