【问题标题】:error Prepared statement needs to be re-prepared [duplicate]错误准备好的语句需要重新准备[重复]
【发布时间】:2021-06-24 22:15:44
【问题描述】:

我正在为一个爱好 PHP 网站 (PHP 7.3.27-1~deb10u1 (fpm-fcgi)) 运行 MariaDB 服务器 (10.3.27-MariaDB-0+deb10u1 Raspbian 10)。

昨天我决定是时候开始每天备份数据库并为此目的在 cron 作业中添加脚本。在今晚运行 cron 作业后,网站给了我这个错误:

Fatal error: Uncaught mysqli_sql_exception: Prepared statement needs to be re-prepared in [a].php:180 
Stack trace: 
#0 [a].php(180): mysqli_stmt->execute() 
#1 [b].php(64): getTargetID(Object(mysqli), 'ha', 2020) 
#2 [c].php(3): require('....') 
#3 {main} thrown in funcs.php on line 180

函数 getTargetID() 基本上是在执行一个从视图返回 ID 的查询。在 TablePlus 中运行完全相同的查询(针对相同的数据库)工作正常。

如果我重新启动 MariaDB 服务器,PHP 代码将再次按预期执行,直到再次运行备份脚本。

备份脚本基本上是这样的:

mysqldump -u ${USER} -h${HOST} -p${PASSWORD} --all-databases |gzip > ${SQLFILE}$

编辑:mysqldump 命令被放置在一个 shell 脚本 (db_backup.sh) 中,并在午夜通过一个 cron 作业执行。

脚本在备份机器上执行,该机器有一个具有以下权限的数据库用户:

  • 选择
  • 触发器
  • 显示视图
  • 锁定表

编辑2:

导致错误的函数包含以下代码:

function getTargetID(&$mysqli, $league, $year) {
  $sql_query = <<<SQL
  SELECT team_id
  FROM standings

  WHERE league = ? and season = ?
  ORDER BY points DESC, goals_for DESC, team_name
  LIMIT 1;
SQL;

  $stmt = $mysqli->prepare($sql_query);
  $stmt->bind_param("si", $league, $year); 
  $stmt->execute();
  $result = $stmt->get_result();

  if ($result->num_rows > 0) {
    $rows = $result->fetch_all(MYSQLI_ASSOC);
    $team_id = $rows[0]["team_id"];
    $result->free();
    return $team_id;
  }
  else { echo "<!-- Error 107 -->"; }
  $stmt->close();
}

Standings 是一种“输出”足球联赛排名表的视图。

我有点不知如何解决这个问题,而且到目前为止我无法通过 Google 找到任何解决方案。是否应该在转储命令中添加一些内容?

【问题讨论】:

  • Mysqldump 是可执行文件,不是 sql 语句。你不能用 mysqli 执行它。
  • 您能更详细地描述一下您是如何运行mysqldump 命令的吗?正如 Shadow 所说,这不是您可以通过 MySQL 连接执行的操作。这是一个 shell 命令,因为它是一个二进制可执行文件,根本不是 MySQL 命令。
  • mysqldump 命令放置在 shell 脚本 (db_backup.sh) 中,并在午夜通过 cron 作业执行。 (添加了一个编辑)
  • 这不是直接与 mysqldump 相关的问题,而是与您的 php 代码所做的事情有关。我想mysqldump 做了一些事情来吸收你的 rpi 服务器上运行的mysqld 中的 RAM:反弹它可以修复或隐藏问题。有关更多讨论,请参阅this。请edit您的问题告诉我们更多关于您的php程序的信息。
  • 我已在编辑中添加了相关函数的代码。我有点不确定要添加哪些信息以增加足够的清晰度。

标签: php mysql mysqli mariadb prepared-statement


【解决方案1】:

看看这个....

    $stmt = $mysqli->prepare($sql_query);
    $stmt->bind_param("si", $league, $year);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows > 0) {
      /* do some stulff
      $rows = $result->fetch_all(MYSQLI_ASSOC);
      $team_id = $rows[0]["team_id"];
      $result->free();
      return $team_id;                   /* return here */
    }
    else { echo "<!-- Error 107 -->"; }
    $stmt->close();                      /*  close here */

当您的查询成功时,您无需先关闭准备好的查询对象即可从函数返回。是否有可能导致某种准备好的查询泄漏?

尝试将 php 代码更改为类似这样的内容....

    $stmt = $mysqli->prepare($sql_query);
    $stmt->bind_param("si", $league, $year);
    $stmt->execute();
    $result = $stmt->get_result();
    if ($result->num_rows > 0) {
      /* do some stulff
      $rows = $result->fetch_all(MYSQLI_ASSOC);
      $team_id = $rows[0]["team_id"];
      $result->free();
      $stmt->close();                    /* close first */ 
      return $team_id;                   /* return here */
    }
    else { echo "<!-- Error 107 -->"; }
    $stmt->close();

在开始测试之前,您最好先sudo shutdown -r now 您的机器。这样一来,我们就可以确保从没有隐藏在缓存中的准备好的查询开始。

并且需要一两个备份周期来测试。

如果这确实解决了您的问题,那么这个特殊的 mysqli 边缘情况会引发不正确(不够细致)的错误消息。

【讨论】:

  • 函数结束时每条语句都关闭
  • 我太草率了,谢谢指出。但是,它并不能解决问题。我按照建议重新启动了数据库服务器,然后运行了备份 shell 命令并再次运行了 php 代码。同样的错误。如上所述,将函数重写为不使用视图是可行的。