Kort svar:
Ta bara bort eller kommentera raden nedan så kommer det alltid att fungera, oavsett vilken databaskodning som verkligen används (utf8
, latin1
, etc):
$pdo->exec('SET CHARACTER SET utf8');
Långt svar:
Detta är inte PDO-bugg, det här är MySQL-bugg.
När faktisk databaskodning är latin1
, men du använder:
SET CHARACTER SET utf8
(eller vice versa:faktisk är utf8
, men du använder latin1
- viktig del är att det är annorlunda ), så kommer MySQL, så vitt jag kan se, att försöka utföra teckenuppsättningskonvertering för all trafik mellan klient och server (även för BLOB
!).
Om du INTE ANVÄNDER SET CHARACTER SET
sats, från vad jag ser för skript (PHP/PDO eller Perl/DBI) är anslutningsteckenuppsättningen som standard inställd på att vara databasteckenuppsättningen, och i så fall sker ingen implicit konvertering.
Uppenbarligen är denna automatiska konvertering det som dödar BLOBs, som inte vill att någon konvertering ska ske.
Jag har testat detta på både PHP/PDO och Perl/DBI, och problemet är lätt att reproducera:båda kommer att misslyckas om databasen används med latin1
koda och använda SET CHARACTER SET utf8
(eller vice versa).
Om du vill vara helt UTF8
kompatibel, bör du ändra kodningen av din databas med:
ALTER DATABASE mydb CHARSET utf8;
Med detta kommer allt att använda UTF8
, och BLOB kommer också att fungera bra.
Den minimala filen som orsakar detta korruptionsproblem är blob.bin
med enkelbyte 0xFF
. På Linux kan du skapa den här testfilen med printf
kommando:
printf "0xFF" > blob.bin
Testa nu skript som reproducerar problemet:
PHP-testkod:
<?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-testkod:
#!/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";