使用 perl 从 MySql 获取 utf8mb4 字符串的长度

getting length of utf8mb4 string with perl from MySql

我写了一个小的 perl 函数,它接受一个字符串并检查它的长度,而不是 spaces。基本代码如下所示:

sub foo
{
   use utf8;
   my @wordsArray = split(/ /, $_[0]));
   my $result = length(join('', @wordsArray));
   return $result;
}

当我为这个函数提供一个包含特殊字符(例如希伯来字母)的字符串时,它似乎工作得很好。 当我使用来自 MySql 列且字符集为 utf8mb4 的值时,问题就开始了:在这种情况下,正在计算的值高于上一个示例中的值。

我能猜到为什么会出现这样的行为:特殊字符在table中以4字节的方式写入,因此每个字母在utf8编码中计算为两个字符。

有谁知道如何解决上述问题,这样我就可以从 DB table 定义为 utf8mb4 的字符串中获取正确数量的字符?

编辑:

关于上述代码的更多信息:

用作函数参数的 DB 列的类型为 VARCHAR(1000),排序规则为 utf8mb4_unicode_ci。 我正在通过配置如下的 MySql 连接获取行:

$mySql = DBI->connect(
  "DBI:mysql:$db_info{'database'}:$db_info{'hostname'};mysql_multi_statements=1;",
  "$db_info{'user'}",
  "$db_info{'password'}",
  {'RaiseError' => 1,'AutoCommit' => 0});
...
$mySql->do("set names utf8mb4");

示例数据值是“שלום עולם”(在希伯来语中表示 "Hello World")。

1)当调用foo($request->{VALUE});(其中VALUE是来自DB的列数据)时,结果是16(其中每个希伯来字符被算作两个字符,它们之间的一个space是无视)。在这种情况下,转储器是:

$VAR1 = "71747575 72757475";

2) 调用时foo("שלום עולם");:

看来我需要在开始使用它之前找到一种将接收到的值转换为 UTF8 的方法。

MySQL 调用 utf8 的是 UTF-8 的有限子集,每个字符只允许三个字节,并且涵盖最多 0xFFFF 的代码点。即使 utf8mb4 也没有涵盖完整的 UTF-8 范围,它支持最长 6 个字节的编码字符

结果是来自 utf8utf8mb4 列的任何数据在 Perl 中只是一个 UTF-8 字符串,两种数据库编码之间应该没有区别

我猜你还没有为你的 DBI 句柄启用 UTF-8,所以一切都被视为只是一个字节序列。您应该在进行 connect 调用时启用 mysql_enable_utf8,然后看起来应该类似于

my $dbh = DBI->connect($dsn, $user, $password, { mysql_enable_utf8 => 1 });

根据附加数据,我可以看出您从数据库中检索的字符串确实是 שלום עולם UTF-8 编码

但是,如果我解码它,那么首先我从你的 foo 子程序和我自己的子程序中得到一个非 space 字符数 8,而不是 9;而且你应该从数据库中返回 characters,而不是 bytes

我怀疑您可能首先将编码字符串写入数据库。这是一个创建 MySQL table 的短程序,向其中写入两条记录(一个字符串和一个编码字符串)并检索它写入的内容。你会看到,唯一有所不同的是 mysql_enable_utf8 的设置。无论是否对原始字符串进行编码,以及是否使用 SET NAMES utf8mb4

,行为都是相同的

进一步的实验表明 mysql_enable_utf8 SET NAMES utf8mb4 将使 DBI 达到 正确写入数据,但后者对读取

没有影响

我建议您的解决方案应该是在读取或写入时仅使用 mysql_enable_utf8

您还应该 use utf8 仅在所有程序的顶部。错过这个意味着你不能在你的代码中使用任何非 ASCII 字符

use utf8;
use strict;
use warnings;

use DBI;
use open qw/ :std :encoding(utf-8) /;

STDOUT->autoflush;

my $VAR1 = "71747575 72757475";

my $dbh = DBI->connect(
    qw/ DBI:mysql:database=temp admin admin /, {
        RaiseError => 1,
        PrintError => 0,
        mysql_enable_utf8 => 1,
    }
) or die DBI::errstr;

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

$dbh->do('DROP TABLE IF EXISTS temp');
$dbh->do('CREATE TABLE temp (value VARCHAR(64) CHARACTER SET utf8mb4)');

my $insert = $dbh->prepare('INSERT INTO temp (value) VALUES (?)');
$insert->execute('שלום עולם');
$insert->execute($VAR1);

my $values = $dbh->selectcol_arrayref('SELECT value FROM temp');
printf "string: %s  foo: %d\n", $_, foo($_) for @$values;

sub foo2 {
  $_[0] =~ tr/ //c;
}

sub foo {
  length join '', split / /, $_[0];
}

输出 mysql_enable_utf8 => 1

string: שלום עולם  foo: 8
string: שלום עולם  foo: 8

输出 mysql_enable_utf8 => 0

string: ש××× ×¢×××  foo: 16
string: ש××× ×¢×××  foo: 16