【问题标题】:PDO: MySQL server has gone awayPDO:MySQL 服务器已消失
【发布时间】:2011-01-14 23:39:33
【问题描述】:

我有一个脚本,每晚都会做大量的跑腿工作。

它使用在循环中执行的 PDO 准备语句。

前几个运行良好,但后来我发现它们都因错误而失败: “MySQL 服务器已消失”。

我们运行 MySQL 5.0.77。

PHP 版本 5.2.12

网站的其余部分运行良好。

【问题讨论】:

  • 呃,我们目前不记录 mysql,因为我们发生了太多事情
  • 好吧,在这种情况下很难解决问题,因为它是 mysql 的问题,不一定是 PDO/PHP。我会打开它运行一夜之间运行的样本批次,然后查看日志:-)

标签: php mysql pdo


【解决方案1】:

MySQL 手册的B.5.2.9. MySQL server has gone away 部分列出了导致此错误的可能原因。

也许您正处于其中一种情况? -- 特别是考虑到你正在运行一个长时间的操作,关于wait_timeout 的点可能很有趣......

【讨论】:

  • 我认为你是对的。我的等待超时是 90 秒,我的代码在执行之前可能需要一段时间。有没有办法通过 PHP 动态更改 1 个脚本?
  • 或通过 php PDO “ping” mysql 的方法?
  • 从文档来看,最简单的方法似乎只是重新连接;;即,如果您收到该错误,重新实例化您的 PDO 类可能会起作用
  • 你可以做 $pdo->query("SET wait_timeout=1200;");为您的长时间运行的脚本设置特定的超时限制
  • $pdo->exec("SET @@session.wait_timeout = {$waitTimeout}");
【解决方案2】:

尝试在您的 pod 实例上使用 PDO::setAttribute(PDO::ATTR_EMULATE_PREPARES, true)。不知道它会有所帮助,但我得到的只是没有日志数据。

【讨论】:

    【解决方案3】:

    您的连接可能已被终止(例如,由 wait_timeout 或另一个发出 KILL 命令的线程)、服务器已崩溃或您以某种方式违反了 mysql 协议。

    后者很可能是 PDO 中的错误,如果您使用服务器端准备好的语句或多结果(提示:不要),则极有可能发生这种情况

    需要调查服务器崩溃;查看服务器日志。

    如果您仍然不知道发生了什么,请使用网络数据包转储器(例如 tcpdump)转储连接的内容。

    您也可以启用通用查询日志 - 但在生产中要非常小心。

    【讨论】:

      【解决方案4】:

      很可能您向服务器发送的数据包长度超过了允许的最大数据包长度。

      当您尝试插入超出服务器最大数据包大小的BLOB 时,即使在本地服务器上,您也会在客户端看到以下错误消息:

      MySQL 服务器已消失

      以及服务器日志中的以下错误消息:(如果启用了错误日志记录)

      错误 1153 收到的数据包大于 'max_allowed_pa​​cket' 字节

      要解决此问题,您需要确定要插入的最大BLOB 的大小,并相应地在my.ini 中设置max_allowed_packet,例如:

      [mysqld]
      ...
      max_allowed_packet = 200M
      ...
      

      【讨论】:

      • 谢谢。设置 max_allowed_pa​​cket = 16M 解决了我的问题
      • +1 这也修复了我遇到的一个错误(说服务器已经消失)。
      • 我在my.cnf * 中执行了此操作,而不是您提到的 my.ini,但是是的。
      【解决方案5】:

      我遇到了完全相同的问题。 我通过对 PDO 对象执行 unset 而不是将其设置为 NULL 来解决此问题。

      例如:

      function connectdb($dsn,$username,$password,$driver_options) {
          try {
              $dbh = new PDO($dsn,$username,$password,$driver_options);
              return $dbh;
          }
          catch(PDOException $e)
          {
              print "DB Error: ".$e->getMessage()."<br />";
              die();
          }
          
      }
      
      function closedb(&$dbh) {
          unset($dbh);             // use this line instead of $dbh = NULL;
      }
      

      此外,强烈建议您取消设置所有 PDO 对象。这包括包含准备好的语句的变量。

      【讨论】:

      • 你不就是取消设置closedb里面的局部变量吗?即,该功能什么也不做。 $dbh = null 也不会做任何事情,除非您在函数签名中添加 &amp;
      • 感谢@mpen 指出这一点,我现在添加了缺少的 & 以通过引用传递
      【解决方案6】:
      $pdo = new PDO(
          $dsn,
          $config['username'],
          $config['password'],
          array(
              PDO::ATTR_PERSISTENT => true,
              PDO::ATTR_ERRMODE    => PDO::ERRMODE_EXCEPTION
          )
      );
      

      试试这个。它可能有效

      【讨论】:

        【解决方案7】:

        我遇到了同样的问题,如果超时,托管服务器管理会终止连接。

        由于我主要使用了查询,因此我编写了一个代码,我们可以包含以下类并将类名替换为“ConnectionManagerPDO”,而不是使用 PDO 类。我刚刚包装了 PDO 类。

        final class ConnectionManagerPDO
        {
        
            private $dsn;
            private $username;
            private $passwd;
            private $options;
            private $db;
            private $shouldReconnect;
        
            const RETRY_ATTEMPTS = 3;
        
            public function __construct($dsn, $username, $passwd, $options = array())
            {
                $this->dsn = $dsn;
                $this->username = $username;
                $this->passwd = $passwd;
                $this->options = $options;
                $this->shouldReconnect = true;
                try {
                    $this->connect();
                } catch (PDOException $e) {
                    throw $e;
                }
            }
        
            /**
             * @param $method
             * @param $args
             * @return mixed
             * @throws Exception
             * @throws PDOException
             */
            public function __call($method, $args)
            {
                $has_gone_away = false;
                $retry_attempt = 0;
                try_again:
                try {
        
                    if (is_callable(array($this->db, $method))) {
        
                        return call_user_func_array(array($this->db, $method), $args);
                    } else {
        
                        trigger_error("Call to undefined method '{$method}'");
                        /*
                         * or
                         *
                         * throw new Exception("Call to undefined method.");
                         *
                         */
                    }
                } catch (\PDOException $e) {
        
                    $exception_message = $e->getMessage();
        
                    if (
                        ($this->shouldReconnect)
                        && strpos($exception_message, 'server has gone away') !== false
                        && $retry_attempt <= self::RETRY_ATTEMPTS
                    ) {
                        $has_gone_away = true;
                    } else {
                        /*
                         * What are you going to do with it... Throw it back.. FIRE IN THE HOLE
                         */
                        throw $e;
                    }
                }
        
                if ($has_gone_away) {
                    $retry_attempt++;
                    $this->reconnect();
                    goto try_again;
                }
            }
        
        
            /**
             * Connects to DB
             */
            private function connect()
            {
                $this->db = new PDO($this->dsn, $this->username, $this->passwd, $this->options);
                /*
                 * I am manually setting to catch error as exception so that the connection lost can be handled.
                 */
                $this->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
            }
        
            /**
             * Reconnects to DB
             */
            private function reconnect()
            {
                $this->db = null;
                $this->connect();
            }
        }
        

        然后就可以像在 PDO 中一样开始使用上面的类了。

        try {
            $db = new ConnectionManagerPDO("mysql:host=localhost;dbname=dummy_test", "root", "");
            $query = $db->query("select * from test");
            $query->setFetchMode(PDO::FETCH_ASSOC);
        }
        catch(PDOException $e){
            /*
                handle the exception throw in ConnectionManagerPDO
            */
        }
        

        【讨论】:

        • 自动重新连接对我不起作用。所以我删除了 __call 函数并将重新连接函数公开。我在 try/catch 块中进行查询时调用它。
        • 只有在 pdo 返回异常“服务器已离开”时才会重新连接。你能告诉我它发生了什么异常吗?
        • 我又试了一次:例外是“MySQL 服务器已消失”,它确实重新连接,没关系。但是应该在异常发生时执行的查询丢失了 - 所以我必须尝试/捕获查询才能在重新连接后再次执行它。
        • 有趣!很高兴你能够实现它。
        • Down - 因为使用 goto。非常糟糕的做法
        【解决方案8】:

        Nathan H,下面是用于 pdo 重新连接的 php 类 + 代码使用示例。 Screenshot 已附上。

        <?php
        
        # set errors reporting level
        error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);
        
        # set pdo connection
        include('db.connection.pdo.php');
        
        /* # this is "db.connection.pdo.php" content
        define('DB_HOST', 'localhost');
        define('DB_NAME', '');
        define('DB_USER', '');
        define('DB_PWD', '');
        define('DB_PREFIX', '');
        define('DB_SHOW_ERRORS', 1);
        
        # connect to db
        try {
            $dbh = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PWD);
            $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
            $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        catch(PDOException $e) {
            # echo $e->getMessage()."<br />";
            # exit;
            exit("Site is temporary unavailable."); #
        }
        */
        
        $reconnection = new PDOReconnection($dbh);
        
        $reconnection->getTimeout();
        
        echo $dbh->query('select 1')->fetchColumn();
        echo PHP_EOL;
        echo 'sleep 10 seconds..'.PHP_EOL;
        
        sleep(10);
        
        $dbh = $reconnection->checkConnection();
        echo $dbh->query('select 1')->fetchColumn();
        echo PHP_EOL;
        echo 'sleep 35 seconds..'.PHP_EOL;
        
        sleep(35);
        
        $dbh = $reconnection->checkConnection();
        echo $dbh->query('select 1')->fetchColumn();
        echo PHP_EOL;
        echo 'sleep 55 seconds..'.PHP_EOL;
        
        sleep(55);
        
        $dbh = $reconnection->checkConnection();
        echo $dbh->query('select 1')->fetchColumn();
        echo PHP_EOL;
        
        echo 'sleep 300 seconds..'.PHP_EOL;
        sleep(300);
        $dbh = $reconnection->checkConnection();
        echo $dbh->query('select 1')->fetchColumn();
        echo PHP_EOL;
        
        # *************************************************************************************************
        # Class for PDO reconnection
        class PDOReconnection
        {
            private $dbh;
        
            # constructor
            public function __construct($dbh)
            {
                $this->dbh = $dbh;
            }
        
            # *************************************************************************************************
        
            # get mysql variable "wait_timeout" value
            public function getTimeout()
            {
                $timeout = $this->dbh->query('show variables like "wait_timeout"')->fetch(); # print_r($timeout);
                echo '========================'.PHP_EOL.'mysql variable "wait_timeout": '.$timeout['Value'].' seconds.'.PHP_EOL.'========================'.PHP_EOL;
            }
        
            # *************************************************************************************************
        
            # check mysql connection
            public function checkConnection()
            {
                try {
                    $this->dbh->query('select 1')->fetchColumn();
                    echo 'old connection works..'.PHP_EOL.'========================'.PHP_EOL;
                } catch (PDOException $Exception) {
                    # echo 'there is no connection.'.PHP_EOL;
                    $this->dbh = $this->reconnect();
                    echo 'connection was lost, reconnect..'.PHP_EOL.'========================'.PHP_EOL;
                }
        
                return $this->dbh;
            }
        
            # *************************************************************************************************
        
            # reconnect to mysql
            public function reconnect()
            {
                $dbh = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USER, DB_PWD);
                $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
                $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE); 
                $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);    
                return $dbh;
            }
        }
        # /Class for PDO reconnection
        # *************************************************************************************************
        

        【讨论】:

          【解决方案9】:

          今天早上在 Laravel 中更改我的数据库属性后,我遇到了同样的错误。我已经注释掉了旧设置并粘贴了新设置。问题是缺少DB_CONNECTION 变量的新设置:

          DB_CONNECTION=pgsql
          

          显然您需要添加您正在使用的任何连接类型:sqlite、mysql、...

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-05-25
            • 2011-06-07
            • 2010-12-20
            • 2013-06-13
            • 2017-01-21
            • 2015-01-15
            相关资源
            最近更新 更多