¿Se ha convertido un BLOB utilizando el juego de caracteres actual / predeterminado en MySQL?

  1. Tengo una tabla con un campo BLOB.
  2. El conjunto de caracteres de la tabla es Latin1.
  3. Me conecto a la base de datos y “SET CHARACTER SET utf8”.
  4. Luego guardo datos binarios en el campo.
  5. Luego recupero los datos, y no es lo que guardé (corrupto).

El código:

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)); 

Buena respuesta @mvp!

Pero cuando mi aplicación web es UTF-8 y la encoding de la base de datos es latin1 , tengo que establecer el character_set_client y el character_set_results .

Cuando uso SET CHARACTER SET utf8 , SET CHARACTER SET utf8 el problema descrito con BLOB.

Pero cuando uso SET NAMES utf8 , ¡funciona!

Respuesta corta:

Simplemente elimine o comente la línea siguiente, y siempre funcionará, sin importar qué encoding de base de datos realmente esté en uso ( utf8 , utf8 , etc.):

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

Respuesta larga:

Esto no es un error de PDO, este es un error de MySQL.

Cuando la encoding real de la base de datos es latin1 , pero usted usa:

 SET CHARACTER SET utf8 

(o viceversa: real es utf8 , pero usa latin1 – la parte importante es que es diferente ), entonces, hasta donde puedo decir, MySQL intentará realizar conversión de conjunto de caracteres para todo el tráfico entre el cliente y el servidor (incluso para BLOB !).

Si NO utiliza la instrucción SET CHARACTER SET , por lo que veo para las secuencias de comandos (PHP / PDO o Perl / DBI), el juego de caracteres de la conexión está configurado de forma predeterminada para el conjunto de caracteres de la base de datos, y en ese caso no tiene lugar una conversión implícita.

Obviamente, esta conversión automática es lo que mata a los BLOB, que no quieren que ocurra ninguna conversión.

He probado esto tanto en PHP / PDO como en Perl / DBI, y el problema es fácilmente reproducible: ambos fallarán si se usa la base de datos con encoding latin1 y se usa SET CHARACTER SET utf8 (o viceversa).

Si quieres ser totalmente compatible con UTF8 , debes cambiar la encoding de tu base de datos usando:

 ALTER DATABASE mydb CHARSET utf8; 

Con esto, todo estará usando UTF8 , y los BLOB también funcionarán bien.

El archivo mínimo que causa este problema de corrupción es blob.bin con un solo byte 0xFF . En Linux, puede crear este archivo de prueba usando el comando printf :

 printf "0xFF" > blob.bin 

Ahora, scripts de prueba que reproducen el problema:

Código de prueba de PHP:

 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 

Código de prueba de 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"; 

Editar: en el servidor WAMP

No funcionó con la API de PDO. Puede usar base64_encode() antes de insert y base64_decode() después de la recuperación. Intensifica los datos en un 33% y la conversión es una sobrecarga.

Si las API de MySQLi son una opción, aquí hay un código:

 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()); ?>