解决Perl DBD :: mysql UTF-8错误

时间:2018-09-04 14:29:31

标签: mysql perl utf-8 dbi utf8mb4

我们有一个用perl编写的软件,可以从mysql数据库中检索数据。 为此,我们使用DBD :: mysql接口

我们可以正确检索所有数据,数据库为UTF8MB4,而perl应用程序使用UTF-8。

检索sql结果的代码是:

use utf8;
use encoding 'utf8';

...
my $dsn = "DBI:mysql:database=mydatabase;mysql_enable_utf8=1";
my $dbh = DBI->connect($dsn, $userid, $password, { mysql_enable_utf8 => 1 } ) or die $DBI::errstr;

...

my $sth = $dbh->prepare("SELECT addressid, 
                            company, firstname, lastname, 
                            address, zip, city, country,
                            phone, mobile, home,
                            speeddial_phone, speeddial_mobile, speeddial_home,
                            fax, email
                        FROM address
                        WHERE (firstname like ? or lastname like ? or company like ?)
                        LIMIT $sizeLimit
                        ");
$sth->execute(  $searchExpression, $searchExpression, $searchExpression) or die $DBI::errstr;

只要$ searchExpression包含普通字符,它就可以正常工作。 但是,一旦我们使用非ASCII特殊字符(例如éöäü和类似字符)进行查询,我们就不会返回空结果集。

根据这篇文章,这是由于版本4.041_01之前dbd :: mysql驱动程序中的错误

http://blogs.perl.org/users/mike_b/2016/12/dbdmysql-all-your-utf-8-bugs-are-belong-to-us.html

我测试了不同的东西,但无济于事。

我确实在mysql服务器上打开了请求日志记录,在那里我看到带有特殊字符的参数以错误的编码输入。

以下是mysql日志的输出到文件:

Time                 Id Command    Argument
180905  9:17:06   403 Connect   inno-ldap-db@localhost on phonebook_innovaphone
                  403 Query     SELECT addressid,
                                company, firstname, lastname,
                                address, zip, city, country,
                                phone, mobile, home,
                                speeddial_phone, speeddial_mobile, speeddial_home,
                                fax, email
                            FROM address
                            WHERE companyid='1' and (firstname like 'andré%' or lastname like 'andré%' or company like 'andré%' )
                            LIMIT 25
                  403 Quit

由于我们当前无法升级系统(它是debian 7,仅包括4.021-1 + deb7u3之类的较旧软件包),因此我需要解决该问题。

或者使用某种魔术来预编码/解码参数,还是odbc驱动程序可能不会遇到此错误?

2 个答案:

答案 0 :(得分:0)

原来, 该字符串(通过Net :: LDAP :: Server接收)已经以某种utf8编码,然后mysql驱动程序再次对其进行了编码。

通过添加以下代码解决了问题

use Encode qw( decode );
my $decoded = eval { decode('UTF-8', $encoded, Encode::FB_CROAK) }

此帖子中的代码:The proper way of encoding detection in perl

感谢有关对mosvy进行双重编码的提示

答案 1 :(得分:0)

再详细说明一下:DBD::mysql 无法知道服务器对绑定参数期望什么编码。此外,它具有与许多 CPAN 模块相同的 unicode 错误:它忽略 perl 使用的存储编码,只查看内部位和字节,因此 Perl 中的相同字符串将正确输出,有时错误,这是根很多人的困惑:结果没有意义,因为字符串的内部编码不是通常在 perl 级别公开的东西。

处理这个问题的可以说是正确的方法是要求用户将每个参数标记作为二进制(对于 blob 列)和其他东西 - 这就是 DBD::MariaDb 所做的 - 它假设所有内容都是文本,除非您覆盖它,因此,您不会遇到同样的问题,代价是必须为二进制值做额外的工作。

您也可以在 DBD::mysql 中确定性地处理此问题,方法如下:

连接时,启用 mysql_enable_utf8mb4 标志/属性。你不应该稍后再做,但问题中的顺序也应该有效。您还应该为您的数据库/表/文本列使用 utf8mb4 字符集。

这应该可靠地处理从数据库读取的数据,因为 mysql/mariadb 将正确地将文本标记为文本(包括其编码)和二进制标记为二进制。

剩下的问题是提交数据时要做什么。这很容易,在提交文本时,请确保它是 unicode 格式(这意味着它不是 utf-8 编码,因为 Perl 可以直接表示 unicode 字符)。然后你可以升级(不改变perl中字符串的含义,或者encode将字符串编码为utf8(改变perl中的含义):

# when you have a unicode text string
utf8::upgrade $text_column; # do this
utf8::encode $text_column; # OR that

其中任何一个都将确保 Perl 字符串的内部表示与 utf-8 兼容,这是数据库所期望的。第一个可能更可取,因为如果数据库驱动程序被修复(或当您切换到 DBD::MariaDb 时),它会继续工作

如果您的数据已经编码 utf-8,另一种方法是降级:

# when you have an utf-8 encoded binary string
utf8::downgrade $text_column;

对于 blob/binary 列,您需要确保 Perl 的内部表示不是在 utf-8 中。您可以通过使用 utf8::downgrade:

来确保这一点
# when you have BLOB data
utf8::downgrade $blob_column;

这也不会改变 Perl 中字符串的含义,但是由于 DBD::mysql 不关心 Perl 的想法,它会做正确的事情并以二进制形式提交数据。

不幸的是,这不是未来兼容版本 - 如果您切换到另一个未损坏的数据库驱动程序,您可能必须以不同的方式处理 BLOB 数据。一种方法是将参数标记为 SQL_BLOB 进行降级,这应该适用于正确的驱动程序。

现在介绍一些历史。

在我个人看来,Perl 和 CPAN 模块中的混淆源于一些不同的事情。首先,当第一次在 perl 中实现 unicode 时,人们意识到他们最初为 Perl 设想的 unicode 模型不能很好地工作,并想要改变它。不幸的是,记录 Perl 的 Camel 书已经更新,所以他们没有更改它,以保持与本书兼容,并进行了一些半心半意的修复。结果,perl 不再与本书完全兼容,也不再与正确的模型完全兼容。这种情况在过去几年有所改善,但花费了很长时间,并留下了许多受害者。

其次,许多手册页,例如perluniintro,是完全错误的,并且使错误的假设永久存在,即 perl 知道其字符串的编码,或者所谓的“utf8-flag”具有某种意义与 unicode 和/或 utf-8 的字符串有关。两者都不对。

最后,XS API 发生了不兼容的更改:要在 unicode 之前的 perls 中获取字符 sina 字符串,您将调用一个名为 SvPV 的函数,该函数仅返回指向字符的指针,所有这些那个时候可以用一个字节存储,即binary

新版本的 perl 不是保持这种方式,而是继续返回字符串的内部字节表示,因此尚未更新的模块将获得原始数据,而没有机会进行解释正确,因为 SvPV 没有返回足够的信息来解码数据。

因此,一夜之间,所有处理字符串数据的 Perl 模块都获得了 Perl Unicode Bug(tm),但并非所有模块都已修复。现在修复它们可能会破坏以某种方式解决问题的程序。