Mysqli:调用通过预准备语句使用游标的存储过程会死掉

时间:2016-01-18 13:35:21

标签: php stored-procedures mysqli

我正在尝试解决看起来很模糊的问题,我认为这可能是mysqlnd驱动程序或mysql中的错误,或者可能是PHP端。我希望有人能够确认这是一个错误,还是我做错了什么(可能更有可能!)。我在Ubuntu 15.10上使用php 5.6.11-1ubuntu3和MariaDB / mysql版本10.0.20-MariaDB-0ubuntu0.15.04.1。

我发布的代码是概念证明,并不是我遇到过的原始代码 - 它仅用于说明目的,尽管它确实运行。

请注意,这是/一般调用存储过程并获取结果的问题。我知道存储过程将返回多个需要处理的结果集,并且这是处理的。问题特别是当存储过程使用游标时。

我创建了以下POC php代码,通过mysqli中的预准备语句调用存储过程:

ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
ini_set("error_reporting", E_ALL);
ini_set("log_errors", true);
error_reporting("E_ALL");

/* Trying to find a solution to this bug:
 * mysqli used to connect
 * call stored proc via a prepared stmt.  Proc contains a cursor.
 * Connection is lost during processing, possibly at the point cursor is opened.
 */

function dbg()
{
    foreach( func_get_args() as $a )
    {
        if( !is_string($a) ) $a = var_export($a, true);
        echo "\n<br/>\nDBG: " . $a;
        ob_flush();
        flush();
    }
}

function sdown()
{
    dbg("SHUT DOWN");
}

function err($num, $err)
{
    dbg("EH: $err");
}

register_shutdown_function("sdown");
set_error_handler(err);

try
{
    test();
}
catch( Exception $e )
{
    dbg("Exception caught: " . $r->getMessage());
}
function test()
{
    dbg("Connecting...");
    $db = new mysqli("localhost", "testuser", "testtesttest", "testdb");
    dbg("Connected");
    if( $db->connect_error )
        throw new Exception("Failed connecting to DB server");
    dbg("set charset");
    $db->set_charset("utf8-bin");

    $stmt = $db->stmt_init();
    dbg("init");
    $result = $stmt->prepare("call curTest(4,5)");
    dbg("prepared");
    $stmt->execute();
    dbg("executed");
    $stmt->store_result();
    $stmt->bind_result($a, $b);
    dbg("bound");
    do {
        dbg("About to fetch...");
      $result = $stmt->fetch();
      dbg("fetched");
      dbg("RES:", $result);
      if( $result == false )
      {
        dbg(" ** ERROR: " . $db->error);
        exit;
      }
      dbg("Got vars:", $a, $b);
      $result->free();
    } while ($stmt->more_results() && $stmt->next_result());

}

调用的存储过程如下所示:

create procedure curTest (AuthTok char(32), LogId char(11))
begin

  declare vBC varchar(10);
  declare vBID, done int unsigned;
  declare cur1 cursor for select Id, BookingCode from Bookings;
  declare continue handler for not found set done=true;

  drop table if exists tBC;
  create temporary table tBC (bid int unsigned, bc varchar(10));

  insert into tBC (bid, bc) values (42, "foo");

  open cur1;

   set done = false;
   read_loop: loop
     fetch cur1 into vBID, vBC;
     if done then
       leave read_loop;
     end if;
     insert into tBC (bid, bc) values (vBID, vBC);
   end loop;

  close cur1;

  select * from tBC;
  drop table tBC;

end

运行程序时,此输出显示在屏幕上。注意EH行告诉我在与服务器通信时协议出现严重问题。此时我实际上失去了连接,这在POC代码中没有显示。还要注意,它是第一个出错的“fetch”;这不是“多个结果集”的问题。

DBG: Connecting...
DBG: Connected
DBG: set charset
DBG: init
DBG: prepared
DBG: executed
DBG: bound
DBG: About to fetch...
DBG: EH: Packets out of order. Expected 1 received 5. Packet size=15
DBG: fetched
DBG: RES:
DBG: false
DBG: ** ERROR:
DBG: SHUT DOWN

如果我从mysql客户端运行它,我会收到预期的结果:

MariaDB [hb]> call curTest(1,2);
+------+----------+
| bid  | bc       |
+------+----------+
|   42 | foo      |
|    1 | PCRFXDVX |
+------+----------+
2 rows in set (0.07 sec)

Query OK, 0 rows affected (0.09 sec)

如果编辑存储过程只打开和关闭游标而没有游标循环,我仍然会收到相同的错误。

但如果编辑它以便光标根本不打开或关闭,那么它可以正常工作:

create procedure curTest (AuthTok char(32), LogId char(11))
begin

  declare vBC varchar(10);
  declare vBID, done int unsigned;
  declare cur1 cursor for select Id, BookingCode from Bookings;
  declare continue handler for not found set done=true;

  drop table if exists tBC;
  create temporary table tBC (bid int unsigned, bc varchar(10));

  insert into tBC (bid, bc) values (42, "foo");

--   open cur1;
-- 
--    set done = false;
--    read_loop: loop
--      fetch cur1 into vBID, vBC;
--      if done then
--        leave read_loop;
--      end if;
--      insert into tBC (bid, bc) values (vBID, vBC);
--    end loop;
-- 
--   close cur1;

  select * from tBC;
  drop table tBC;

end

这给了我一个很好的干净运行:

DBG: Connecting...
DBG: Connected
DBG: set charset
DBG: init
DBG: prepared
DBG: executed
DBG: bound
DBG: About to fetch...
DBG: fetched
DBG: RES:
DBG: true
DBG: Got vars:
DBG: 42
DBG: foo
DBG: SHUT DOWN

我为此搜索了很多内容并继续寻找死胡同。我能找到的最接近的匹配是人们与PDO相似并通过使用“模拟准备”PDO设置来修复它。感觉是有司机问题。 mysqli似乎没有相同的设置。

所以我想知道 -

  1. 这里有没有人经历过这个?

  2. 我是否缺少一个设置或步骤可以使这项工作?

  3. 非常感谢!

    谢谢,

    -OLi

0 个答案:

没有答案