Perl DBI(MySQL)在预准备语句中使用单引号而不是实际参数

deR*_*_Ed 8 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);
Run Code Online (Sandbox Code Playgroud)

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

DBI跟踪的输出显示

  -> 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
Run Code Online (Sandbox Code Playgroud)

所以对我来说,看起来这个陈述并没有得到应有的准备.当我发送没有参数的查询时,它按预期工作.

我在这里想念什么?

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

用Windows perl 5, version 26, subversion 1 (v5.26.1) built for MSWin32-x86-multi-thread-64int
和Ubuntu 测试perl 5, version 22, subversion 1 (v5.22.1) built for x86_64-linux-gnu-thread-multi

Edit1:
for context:我在使用Catalyst和Catalyst :: Plugin :: Session :: Store :: DBIC时注意到了这个问题.这里,id-column是Varchar(72)类型,它包含session-id.

EDIT2:

  • DBD :: mysql版本是 4.043
  • 绑定通过$sth->execute('session:foo');导致相同的问题
  • 绑定通过$sth->bind_param('session:foo', SQL_VARCHAR);导致相同的问题
  • 绑定数字字段确实有效,但只能使用显式类型定义 $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
  • $dbh->{mysql_server_prepare} = 1;它的作品!也许这有助于找到这个问题的人,但我现在宁愿找出问题的真正原因

Pal*_*ali 5

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

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

          *ptr++ = '\'';
          ptr += mysql_real_escape_string(sock, ptr, valbuf, vallen);
          *ptr++ = '\'';
Run Code Online (Sandbox Code Playgroud)

所以问题是,上面的C代码可以在*ptr缓冲区中产生一个撇号吗?答案是肯定的,当mysql_real_escape_string()返回相同值的整数作为指针大小减去1 - 当两个撇号被写入*ptr缓冲区中的相同位置时,模拟递减1的数值运算.

这会发生吗?是的,它可以,因为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

不兼容的更改:新的C API函数mysql_real_escape_string_quote()已被实现为mysql_real_escape_string()的替代,因为后一个函数在启用NO_BACKSLASH_ESCAPES SQL模式时无法正确编码字符.在这种情况下,mysql_real_escape_string()不能转义引号字符,除非加倍它们,并且要正确地执行此操作,它必须知道有关引用上下文的更多信息.mysql_real_escape_string_quote()使用额外的参数来指定引用上下文.有关用法详细信息,请参阅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

返回值:放入to参数的编码字符串的长度,不包括终止空字节,如果发生错误,则返回-1.

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

现在我在下面的pull请求中修复了DBD :: MariaDB驱动程序(DBD :: mysql的fork)的这个问题:https://github.com/gooddata/DBD-MariaDB/pull/77

所以当使用MySQL 5.7客户端库编译时,DBD :: MariaDB也是兼容的.


deR*_*_Ed 3

经过一些测试,我得出的结论是,这似乎是DBD::mysqlMySQL 客户端 5.7(和/或MySQL 服务器 5.5)之间的兼容性问题。

至少,我找到了 Ubuntu 16 (xenial) 的解决方案,所以对于其他人来说,可能会遇到同样的问题:

  • 降级到 MySQL 5.6,如此处所述libmysqlclient-dev对我来说,无需服务器/客户端安装就足够了
  • 重新安装 DBD::MySQL sudo cpanm --reinstall DBD::mysql,以便使用现在安装的 MySQL 5.6 进行构建

我将在DBD::mysql GitHub上提交一个问题,如果有关于此问题的任何新闻,我将更新此答案。


另一种解决方案也对我有用:
让服务器准备您的声明$dbh->{mysql_server_prepare} = 1;