我正在尝试做一个简单的查询作为预备语句,但没有成功。这是代码:
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:
4.043
$sth->execute('session:foo');
绑定会导致同样的问题$sth->bind_param('session:foo', SQL_VARCHAR);
绑定会导致同样的问题$sth->bind_param(1, 1512407082, SQL_INTEGER);
EDIT3:
我找到了做更多测试的时间,但没有令人满意的结果:
MYSQL_VERSION_ID 50557
,而我的两个原始测试服务器都使用MySQL 5.7 MYSQL_VERSION_ID 50720
和MYSQL_VERSION_ID 50716
$dbh->{mysql_server_prepare} = 1;
一起工作!也许这可以帮助找到这个问题的人,但我现在更愿意找到问题的真正原因答案 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)的解决方案,所以对于其他人来说,这可能会遇到同样的问题:
libmysqlclient-dev
就足够了sudo cpanm --reinstall DBD::mysql
,以便使用现在安装的MySQL 5.6进行构建如果有关于此问题的任何消息,我会在DBD::mysql GitHub提交问题并更新此答案。
另一种解决方案,对我也有用:
让服务器准备你的陈述$dbh->{mysql_server_prepare} = 1;