Perl DBI(MySQL)在预准备语句

时间:2017-12-04 10:41:36

标签: 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

使用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);

EDIT3:
我找到了做更多测试的时间,但没有令人满意的结果:

  • 我能够使用较旧的服务器进行测试并且运行正常。 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; 一起工作!也许这可以帮助找到这个问题的人,但我现在更愿意找到问题的真正原因

2 个答案:

答案 0 :(得分:5)

从跟踪日志中可以看到,问号占位符(?)已由一个撇号(')替换为DBD :: mysql。因此,这是纯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

  

不兼容的更改:已实现一个新的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在32bit和64bit x86平台上都与指针大小相同,因此,以上来自DBD :: mysql驱动程序的C代码会导致一个单引号字符。

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

因此,使用MySQL 5.7客户端库编译时,DBD :: MariaDB也将兼容。

答案 1 :(得分:3)

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

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

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

如果有关于此问题的任何消息,我会在DBD::mysql GitHub提交问题并更新此答案。

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