閉じる

MySQL 5.0で prepared statement と bindを使った時の不具合

バージョンは 5.0.x*1 で、5.1.x*2 では大丈夫な模様。

bind変数が一つの時は問題ないのだけど、二つ以上あった場合にクライアント→サーバへの転送が正常に行われないっぽい。
純粋にクライアントサイドの問題らしく、同じサーバに Linuxからアクセスすると NGで Windowsからアクセスすると OKなんて状態。

prepared statement と bindを使っていたソフトを Windowsから Linuxに移植した際、明らかに更新されているはずの update文を実行した時に mysql_stmt_affected_rows()が 0を返すことに気がついたのが事の発端。
bind変数が正常に認識されず、update対象が 0になっていたわけだ。

検証コードは以下の通り。

2943-0.cpp
#include <string.h>
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#pragma comment(lib, "libmysql.lib")
#endif
#include <mysql.h>

void bindtest(MYSQL *mysql,const char *sql)
{
MYSQL_STMT *stmt=mysql_stmt_init(mysql);
if(stmt){
if(mysql_stmt_prepare(stmt,sql,strlen(sql))==0){
long long value1;
long long value2;
long long value3;
MYSQL_BIND param[3];
memset(param,0,sizeof(param));
param[0].buffer_type=MYSQL_TYPE_LONGLONG;
param[0].buffer=&value1;
param[1].buffer_type=MYSQL_TYPE_LONGLONG;
param[1].buffer=&value2;
param[2].buffer_type=MYSQL_TYPE_LONGLONG;
param[2].buffer=&value3;
if(mysql_stmt_bind_param(stmt,param)==0){
printf("bind ok\n");
value1=1;
value2=2;
value3=3;
if(mysql_stmt_execute(stmt)!=0){
printf("execute error(%s)\n",mysql_error(mysql));
}else if(mysql_stmt_affected_rows(stmt)!=1){
printf("affetch error\n");
}else{
printf("execute ok\n");
}
}
}else{
printf("prepare error\n");
}
mysql_stmt_close(stmt);
}
}

int main()
{
MYSQL *mysql=mysql_init(NULL);
if(mysql){
if(mysql_real_connect(mysql,"localhost","root","XXXX","test",0,NULL,0)!=NULL){
printf("connect\n");
mysql_query(mysql,"drop table bindtest");
mysql_query(mysql,"create table bindtest (no int auto_increment primary key,value1 int,value2 int,value3 int)");
// const char *sql="update bindtest set value1 = ? where no = ?";
bindtest(mysql,"insert into bindtest set value1 = ? , value2 = 2 , value3= 3 ");
bindtest(mysql,"insert into bindtest set value1 = ? , value2 = ? , value3= 3 ");
bindtest(mysql,"insert into bindtest set value1 = ? , value2 = ? , value3= ? ");
}
mysql_close(mysql);
}
return 0;
}

5.0では以下のような結果になる。

mysql> select * from bindtest;
+----+------------+--------+--------+
| no | value1     | value2 | value3 |
+----+------------+--------+--------+
|  1 |          1 |      2 |      3 |
|  2 | 2147483647 |      0 |      3 |
|  3 | 2147483647 |      0 |      0 |
+----+------------+--------+--------+
3 rows in set (0.01 sec)

5.1では期待通り以下のような結果になる。

mysql> select * from bindtest;
+----+--------+--------+--------+
| no | value1 | value2 | value3 |
+----+--------+--------+--------+
|  1 |      1 |      2 |      3 |
|  2 |      1 |      2 |      3 |
|  3 |      1 |      2 |      3 |
+----+--------+--------+--------+
3 rows in set (0.01 sec)

どちらもサーバは 5.0.87が動いていて、クライアントで使うライブラリ(libmysqlclient)だけ差し替えてる。

ちなみに long longを longに変えると 5.0では以下のような結果になり、5.1では期待通りの結果に。

mysql> select * from bindtest;
+----+--------+--------+--------+
| no | value1 | value2 | value3 |
+----+--------+--------+--------+
|  1 |      1 |      2 |      3 |
|  2 | 131072 |      0 |      3 |
|  3 |      2 |      3 |      0 |
+----+--------+--------+--------+
3 rows in set (0.00 sec)

1,2,3とセットしているところを 4,5,6に変えると後ろ二つは executeがエラー*3 になったり、2147483647は 0x7FFFFFFFだし、131072は 0x20000なので、調べてみれば構造体のアライメントがどうこうという単純な事な気もする。

クライアント、サーバ共に更新してしまうのが正論なのだけど、そう簡単な話でもないのでクライアントライブラリだけ 5.1.60の物に差し替えて誤魔化すとする。


*1 5.0.87と 5.0.91で確認。

*2 5.1.60で確認。

*3 Incorrect arguments to mysql_stmt_execute

コメントを残す

メールアドレスが公開されることはありません。必須項目には印がついています *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)