【问题标题】:Perl MySQL utf8mb4 issue / possible bugPerl MySQL utf8mb4 问题/可能的错误
【发布时间】:2018-03-25 10:32:34
【问题描述】:

我在 Debian 8 机器上使用 Perl 5.20.2 和 MySQL 5.5.57。我最近发现 MySQL 的 utf8 表仅限于三字节字符。因此,我无法存储表情符号。 所以,我尝试了应该解决这个问题的 utfmb4 表。我从 mysql 客户端内部将表从 utf8 更改为 utf8mb4:

ALTER DATABASE `mydb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
ALTER TABLE `mydb`.`mytable` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `mydb`.`mytable` CHANGE `object` `object` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

在 mytable 中存储数据似乎可行,至少我可以在 phpMyAdmin 中看到预期的表情符号。但是,当从表中读取时,我收到一个包含 3 个不可打印字符的 4 个字符的结果。以下程序应该打印两次相同的表情符号:

#!/usr/bin/perl

use 5.10.1;
use warnings;
use strict;
use DBI;

binmode(STDOUT, ':utf8');

my $object = "\x{1F600}";
my $hd_db  = DBI->connect('DBI:mysql:mydb:localhost', 'user', 'password');
$hd_db->do('SET NAMES utf8mb4');

# cleanup
my $delete = $hd_db->prepare("DELETE FROM mytable");
$delete->execute;

my $insert = $hd_db->prepare("INSERT INTO mytable (object) VALUES ('" . $object . "')");
$insert->execute;
my $select = $hd_db->prepare("SELECT * FROM mytable");
$select->execute;
my $row    = $select->fetchrow_hashref;

say $object;
say $row->{'object'};

预期输出:

????
????

实际输出:

????
�

对我来说似乎是一个错误。有什么建议可以解决吗?

编辑:从 mysql 客户端中选择数据也会显示预期的表情符号

mysql> SET SESSION CHARACTER_SET_CLIENT = utf8mb4;
mysql> SET SESSION CHARACTER_SET_RESULTS = utf8mb4;
mysql> SELECT * FROM mytable;
+--------+
| object |
+--------+
| ????      |
+--------+

【问题讨论】:

  • 你真的应该使用占位符。
  • 你指的是准备语句吗?我通常会这样做,但这似乎与这里无关
  • 它足够相关,我花时间指出它;-)
  • @simbabque,请解释使用占位符如何解决 Perl 客户端中 utf8mb4 字符集的问题。
  • @Bill 我从来没有说过。只是指出好的风格。我们在力所能及的地方进行教育。 :)

标签: mysql perl utf8mb4


【解决方案1】:

您告诉 MySQL 使用 UTF-8 进行通信,但您还需要告诉 DBD::mysql 解码数据(或自己做)。

你想要

my $dbh = DBI->connect('DBI:mysql:mydb:localhost', 'user', 'password', {
   mysql_enable_utf8mb4 => 1,
})
   or die($DBI::errstr);

相当于

my $dbh  = DBI->connect('DBI:mysql:mydb:localhost', 'user', 'password')
   or die($DBI::errstr);

$dbh->do('SET NAMES utf8mb4')
   or die($dbh->errstr);

$dbh->{mysql_enable_utf8mb4} = 1;

【讨论】:

  • 我接受了这个答案,因为这是 DBI 版本 >= 4.041_01 的方法。 Debian 8 附带 3.0.17。对于选择mysql_enable_utf8 => 1 时该版本的解码工作,请参阅this post
【解决方案2】:

解决方法是让 MySQL 将所有内容都视为字节并在您的应用程序中进行编码。

use Encode qw(encode decode);

my $object = "\x{1F600}";
my $hd_db  = DBI->connect('DBI:mysql:mydb:localhost', 'user', 'password');
$hd_db->do('SET NAMES latin1');

...

my $insert = $hd_db->prepare("INSERT INTO mytable (object) VALUES ('" . 
    encode("UTF-8",$object) . "')"); # or equiv statement with placeholders
$insert->execute;

...

my $select = $hd_db->prepare("SELECT * FROM mytable");
$select->execute;
my $row    = $select->fetchrow_hashref;
say $object;
say decode("UTF-8",$row->{'object'});

【讨论】:

  • 感谢您的建议,但不幸的是,我必须在我的应用程序中重新访问超过 1k 的数据库查询。更糟糕的是,他们必须接受测试..
【解决方案3】:

"\x{1F600}"; 是“Unicode”,而不是“utf8”。它们是相关的,但它们不是相同的编码。

你需要UTF-8(非mysql世界叫它)和utf8mb4(MySQL叫它)。

? 是十六进制 F09F9880(在 utf8mb4 中);如果您通过CHARACTER SET latin1(“Mojobake”)转换,则为😀

请运行SELECT HEX(object) ... 以查看您是否获得了这 4 个十六进制字节或其他内容。然后我们就会知道是关注INSERT还是SELECT

您说的是“实际输出”——但这是在哪里?一个网页?是否为UTF-8 配置?或者是其他东西?如果它是您的命令行窗口,请确保将其设置为 UTF-8。在 Windows 中,这是通过 chcp 65001 完成的。

你提到

mysql> SET SESSION CHARACTER_SET_CLIENT = utf8mb4;
mysql> SET SESSION CHARACTER_SET_RESULTS = utf8mb4;

这只是需要设置的 3 个中的 2 个。最好干脆做

SET NAMES utf8mb4;

【讨论】:

  • 这是控制台输出,它在 Ubuntu 和 W10/Putty 0.7 上都可以开箱即用。 Win7/Putty 0.7 不能开箱即用,虽然我没有尝试过chcping
  • 比较my.cnf。您可能会发现不同的默认值。 MySQL 有哪些版本
猜你喜欢
  • 1970-01-01
  • 2021-05-06
  • 2021-10-29
  • 2019-01-21
  • 2011-04-10
  • 2015-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多