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:
4.043$sth->execute('session:foo');导致相同的问题$sth->bind_param('session:foo', SQL_VARCHAR);导致相同的问题$sth->bind_param(1, 1512407082, SQL_INTEGER);编辑3:
我找到了做更多测试的时间,但没有令人满意的结果:
MYSQL_VERSION_ID 50557,而我的两个原始测试服务器都使用MySQL 5.7 MYSQL_VERSION_ID 50720和MYSQL_VERSION_ID 50716$dbh->{mysql_server_prepare} = 1;它的作品!也许这有助于找到这个问题的人,但我现在宁愿找出问题的真正原因从您的跟踪日志可以看出,问号占位符(?)被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也是兼容的.
经过一些测试,我得出的结论是,这似乎是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;