mysql - Perl DBI(MySQL)在准备好的语句中放置单引号而不是实际参数

标签 mysql perl prepared-statement dbi

我正在尝试将一个简单的查询作为准备好的语句进行,但没有成功。这是代码:

package sqltest;
use DBI;

DBI->trace(2);

my $dbh = DBI->connect('dbi:mysql:database=test;host=***;port=3306','the_username', '****');
my $prep = 'SELECT me.id, me.session_data, me.expires FROM sys_session me WHERE me.id = ?';
$dbh->{RaiseError} = 1;
my $sth = $dbh->prepare($prep);
$sth->bind_param(1, 'session:06b6d2138df949524092eefc066ee5ab3598bf96');
$sth->execute;
DBI::dump_results($sth);

MySQL 服务器响应一个语法错误 near '''

DBI-trace 的输出显示

  -> bind_param for DBD::mysql::st (DBI::st=HASH(0x21e35cc)~0x21e34f4 1 'session:06b6d2138df949524092eefc066ee5ab3598bf96') thr#3ccdb4
 Called: dbd_bind_ph
  <- bind_param= ( 1 ) [1 items] at perl_test_dbi_params.pl line 10
[...]
>parse_params statement SELECT me.id, me.session_data, me.expires FROM sys_session me WHERE me.id = ?
Binding parameters: SELECT me.id, me.session_data, me.expires FROM sys_session me WHERE me.id = '
[...]
DBD::mysql::st execute failed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1

所以对我来说,声明似乎没有按应有的方式准备。 当我发送不带参数的查询时,它会按预期工作。

我在这里错过了什么?

DBI版本为DBI 1.637-ithread,MySQL版本为5.5.57-0+deb8u1

使用为 MSWin32-x86-multi-thread-64int 构建的 Windows perl 5,版本 26,subversion 1 (v5.26.1) 进行测试
和 Ubuntu perl 5, version 22, subversion 1 (v5.22.1) built for x86_64-linux-gnu-thread-multi

编辑1:
对于上下文:我在将 Catalyst 与 Catalyst::Plugin::Session::Store::DBIC 一起使用时注意到了这个问题.在这里,id 列是一个 Varchar(72) 类型,它包含一个 session ID。

编辑2:

  • DBD::mysql 版本是 4.043
  • 通过 $sth->execute('session:foo'); 绑定(bind)导致同样的问题
  • 通过 $sth->bind_param('session:foo', SQL_VARCHAR); 绑定(bind)导致同样的问题
  • 绑定(bind)数字字段确实有效,但仅适用于显式类型定义 $sth->bind_param(1, 1512407082, SQL_INTEGER);

编辑3:
我抽出时间进行了更多测试,但并没有得到令人满意的结果:

  • 我能够使用较旧的服务器进行测试并且它有效。 DBI 和 DBD::mysql 的版本相同,但我发现服务器使用 MySQL 5.5 客户端,在 DBI-trace 中报告为 MYSQL_VERSION_ID 50557,而我的原始测试服务器都使用 MySQL 5.7 MYSQL_VERSION_ID 50720MYSQL_VERSION_ID 50716
  • with $dbh->{mysql_server_prepare} = 1;它起作用了! 也许这对找到这个问题的人有帮助,但我现在宁愿找到真正的原因问题

最佳答案

从您的跟踪日志中可以看出,问号占位符 (?) 被 DBD::mysql 替换为 one 撇号 (')。所以它是纯粹的 DBD::mysql 错误。乍一看,它根本没有意义……因为占位符被放入两个撇号中的参数所取代。

执行此占位符替换的相关代码可在此处找到:https://metacpan.org/source/MICHIELB/DBD-mysql-4.043/dbdimp.c#L784-786

          *ptr++ = '\'';
          ptr += mysql_real_escape_string(sock, ptr, valbuf, vallen);
          *ptr++ = '\'';

所以问题是,上面的 C 代码可以在 *ptr 缓冲区中只生成一个撇号吗?答案是肯定的,当 mysql_real_escape_string() 返回与指针大小相同的整数减一时——模拟减一的数字操作,当两个撇号都写入 *ptr 缓冲区中的相同位置时。

这会发生吗?是的,它可以,因为 Oracle 在 MySQL 5.7.6 客户端库中更改了 mysql_real_escape_string() C 函数的 API:

https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html#mysqld-5-7-6-feature

Incompatible Change: A new C API function, mysql_real_escape_string_quote(), has been implemented as a replacement for mysql_real_escape_string() because the latter function can fail to properly encode characters when the NO_BACKSLASH_ESCAPES SQL mode is enabled. In this case, mysql_real_escape_string() cannot escape quote characters except by doubling them, and to do this properly, it must know more information about the quoting context than is available. mysql_real_escape_string_quote() takes an extra argument for specifying the quoting context. For usage details, see mysql_real_escape_string_quote().

MySQL 5.7.6 版本的 mysql_real_escape_string() 文档说:

https://dev.mysql.com/doc/refman/5.7/en/mysql-real-escape-string.html

Return Values: The length of the encoded string that is placed into the to argument, not including the terminating null byte, or -1 if an error occurs.

因此,如果您在 MySQL 服务器上启用 NO_BACKSLASH_ESCAPES SQL 模式,则 MySQL 5.7.6 客户端中的 mysql_real_escape_string() 无法工作并返回错误,因此 -1 转换为 unsigned long。 unsigned long 在 32 位和 64 位 x86 平台上都与指针大小相同,因此上面来自 DBD::mysql 驱动程序的 C 代码导致一个撇号字符。

现在我为 DBD::MariaDB 解决了这个问题以下拉取请求中的驱动程序(DBD::mysql 的分支):https://github.com/gooddata/DBD-MariaDB/pull/77

因此 DBD::MariaDB 在与 MySQL 5.7 客户端库一起编译时也将兼容。

关于mysql - Perl DBI(MySQL)在准备好的语句中放置单引号而不是实际参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47631478/

相关文章:

php - 使用 SQL 拉取去年最后一个月的数据

perl - 如何将函数传递给 Perl sub?

perl - 在 Strawberry Perl 上更新 OpenSSL

sql - 使用函数语法的准备语句

java - 使用java比较和更新数据时如何解决巨大的性能问题?

java - 没有为参数X指定值。如何避免?

mysql - PHP 和 MariaDB : Establish a secure mySQL-Connection

MySQL bigint、decimal(18,0) 或 char(10) 为主

mysql - rails : Insert a lot of HTML code through migration to the database

perl - 如何临时更改 Perl 使用的 tmp 目录?