【问题标题】:Work arround the perl DBD::mysql UTF-8 bug解决 perl DBD::mysql UTF-8 错误
【发布时间】:2019-02-09 14:56:01
【问题描述】:

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

我们可以正确检索所有数据,db 是 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 驱动程序可能不会遇到这个错误?

【问题讨论】:

  • “$dsn”是否包含“mysql_enable_utf8=1”?我不确定你指向的错误是你的问题,因为那是关于 DBD::mysql 没有将'latin1'变量转换为'utf8';但是 $searchExpression 应该已经是 utf-8 编码,因为 'use utf8;'在顶部。另外,您确定正确检索了 utf-8 数据吗?尝试使用不仅包含西方/拉丁1字符(ä、ü等)而且还包含ą、ă、ш、я的字段。
  • 我确实在连接中指定了 mysql_enable_utf8=1,但我现在也将它添加到了 $dns,没有任何区别。我现在还添加了一行上面提到的字符,它们被正确检索。好像只有perl->mysql这个方向有问题。 (我不从 perl 插入/更新数据,只查询它或作为查询过滤器参数)
  • 'warn "utf8 ", utf8::is_utf8($searchExpression) 是什么意思? “开\n”:“关\n”;'就在 $sth->execute 行之前?
  • @mosvy 它显示:utf8 on
  • 我刚刚尝试过一个较旧的 debian wheezy (libdbd-mysql-perl_4.021-1+deb7u3, libdbi-perl_1.622-1+deb7u1) 但无法重现。对不起。我能想到的唯一一件事是该错误在其他地方,并且 $searchExpression 在传递给 $sth->execute; 之前已经被双重编码为​​ utf8;但你可能已经检查过了。

标签: mysql perl utf-8 dbi utf8mb4


【解决方案1】:

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

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

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

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

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

剩下的问题是提交数据时该怎么做。这很简单,在提交文本时,请确保它是 unicode(这意味着它是 not 以 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 中是 not。您可以使用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 进行了不兼容的更改:要获取 pre-unicode perls 中的字符 sina 字符串,您将调用一个名为 SvPV 的函数,它只返回一个指向字符的指针,所有这些都可以是当时存储在一个字节中,即二进制

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

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

【讨论】:

  • 真是一团糟...... :(但感谢您的完整回答
【解决方案2】:

原来, 该字符串(通过 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 的提示

【讨论】:

    猜你喜欢
    • 2017-12-04
    • 1970-01-01
    • 2015-09-11
    • 1970-01-01
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    相关资源
    最近更新 更多