Jawaban Singkat:
Cukup hapus atau komentari baris di bawah ini, dan itu akan selalu berfungsi, tidak peduli penyandian basis data mana yang benar-benar digunakan (utf8
, latin1
, dll):
$pdo->exec('SET CHARACTER SET utf8');
Jawaban Panjang:
Ini bukan bug PDO, ini bug MySQL.
Ketika penyandian basis data sebenarnya adalah latin1
, tetapi Anda menggunakan:
SET CHARACTER SET utf8
(atau sebaliknya:sebenarnya adalah utf8
, tetapi Anda menggunakan latin1
- bagian yang penting adalah berbeda ), kemudian, sejauh yang saya tahu, MySQL akan mencoba melakukan konversi charset untuk semua lalu lintas antara klien dan server (bahkan untuk BLOB
!).
Jika Anda TIDAK menggunakan SET CHARACTER SET
pernyataan, dari apa yang saya lihat untuk skrip (PHP/PDO atau Perl/DBI) charset koneksi secara default diatur menjadi charset database, dan dalam hal ini tidak ada konversi implisit yang terjadi.
Jelas, konversi otomatis inilah yang membunuh Gumpalan, yang tidak menginginkan konversi apa pun terjadi.
Saya telah menguji ini pada PHP/PDO dan Perl/DBI, dan masalahnya mudah direproduksi:keduanya akan gagal jika menggunakan database dengan latin1
encoding dan menggunakan SET CHARACTER SET utf8
(atau sebaliknya).
Jika Anda ingin menjadi UTF8
sepenuhnya kompatibel, Anda harus mengubah penyandian database Anda menggunakan:
ALTER DATABASE mydb CHARSET utf8;
Dengan ini, semuanya akan menggunakan UTF8
, dan BLOB juga akan berfungsi dengan baik.
File minimal yang menyebabkan masalah korupsi ini adalah blob.bin
dengan satu byte 0xFF
. Di Linux, Anda dapat membuat file pengujian ini menggunakan printf
perintah:
printf "0xFF" > blob.bin
Sekarang, uji skrip yang mereproduksi masalah:
Kode tes 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;
}
}
}
?>
Kode uji 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";