如何在 mysqli::real_connect() 之后重用 mysqli_stmt

问题描述 投票:0回答:1

我有一些脚本在数据库上运行很长时间(有时几个小时),我想让它们尽可能强大。

为此我创建了一个

class extended_mysqli extends mysqli {
除其他附加功能外,我还重写了
query
并添加了重试循环和重新连接 (mysqli::real_connect()),以防与数据库服务器的连接丢失。

这与 mysqli::query() 配合得很好

我现在正在训练以获得与准备好的语句相同的功能。 我实现了一个子类

class extended_mysqli_stmt extends mysqli_stmt {
extended_mysqli
中,我重写方法
prepare()
以返回我的新类
extended_mysqli_stmt
extended_mysqli_stmt
中,我重写了execute()并添加了mysqli::real_connect(),以防与数据库服务器的连接丢失。

但是,即使 mysqli::real_connect() 成功并且 masli::query() 也正常工作,我无法重用此后的语句,它将始终返回 MYSQL 错误 2006 (MySQL 服务器已消失)。

我尝试使用 mysqli_stmt::reset(),使用 mysqli_stmt::free_result(),但似乎没有帮助。

这是我的一些代码:

<?php    
class extended_mysqli extends mysqli {

        var $host;
        var $user;
        var $password;
        var $database;

        function __construct($host, $user, $password, $database) {
            $this->host=$host;
            $this->user=$user;
            $this->password=$password;
            $this->database=$database;

            $success=false;
            $retrys = 5;
            $sleep = 3; // seconds

            while (!$success and ($retrys > 0)) {
                parent::__construct($this->host, $this->user, $this->password, $this->database);
                if ($this->connect_errno == 0) {
                    $success = true;
                    }
                else {
                    $retrys--;
                    sleep($sleep);
                    }
                }
            if (!$success) {
                self::report_error('extended_mysqli', $this->connect_errno, $this->connect_error, E_USER_ERROR);
                }
            }

        public function reconnect() {
            
            $success = false;
            $retrys = 5;
            $sleep = 3; // seconds
            while (!$success and ($retrys > 0)) {
                $this->real_connect($this->host, $this->user, $this->password, $this->database);
                if ($this->connect_errno == 0) {
                    $success = true;
                    }
                else {
                    $retrys--;
                    sleep($sleep);
                    }
                }
            if (!$success) {
                self::report_error('extended_mysqli', $this->connect_errno, $this->connect_error, E_USER_ERROR);
                }
            }

        #[\ReturnTypeWillChange]
        function query(string $query, ?int $resultmode = MYSQLI_STORE_RESULT, $ignore_error = false) {
            if (is_null($resultmode)) {  // fixup legacy NULL value
                $resultmode = MYSQLI_STORE_RESULT;
                trigger_error_backtrace("\$resultmode is NULL", E_USER_DEPRECATED);
                }
            $bt = debug_backtrace();
            while (
                !isset($bt[0]['file']) ||
                in_array(basename($bt[0]['file']), [basename(__FILE__), 'class_base.php'])
                ) {
                array_shift($bt);
                }
            foreach (array_reverse($bt) as $caller) {
                if (isset($caller['file'])) {
                    $query = "# QUERY IN {$caller['file']}#{$caller['line']}\n{$query}";
                    }
                }

            // $ignore_error darf entweder ein boolean, ein integer oder ein Array von integers sein
            if (is_int($ignore_error)) {
                $ignore_error = [$ignore_error];
                }

            $success = false;
            $retrys = 10;
            $sleep = 5; // seconds
            $retry_errors = [    // bei diesen Fehlern wird das Query-Kommando noch mal versucht
                1053,   // Server shutdown in progress
                1205,   // Lock wait timeout exceeded; try restarting transaction
                2006,   // MySQL server has gone away
                ];
            $reconnect_errors = [    // bei diesen Fehlern wird das Query-Kommando noch mal versucht
                1053,   // Server shutdown in progress
                2006,   // MySQL server has gone away
                ];
            do {
                $res = parent::query($query, $resultmode);
                $success = !in_array($this->errno, $retry_errors);
                if (!$success) {
                    $retrys--;
                    trigger_error(__FILE__.'#'.__LINE__
                        . " Retry after Database error ({$this->errno}: {$this->error}). {$retrys} Retries left", E_USER_NOTICE);
                    if ($this->errno == 1205) {
                        trigger_error(preg_replace("/(\r|\n)\s*/", ' ', $query));
                        }
                    if (in_array($this->errno, $reconnect_errors)) {
                        sleep($sleep);
                        $this->reconnect();
                        }
                    }
                } while (!$success and ($retrys > 0));
            $this->handle_result($res, $ignore_error);
            return $res;
            }

        #[\ReturnTypeWillChange]
        function prepare(string $query, $ignore_error = false) {
            $bt = debug_backtrace();
            while(in_array(basename($bt[0]['file']), [basename(__FILE__), 'class_base.php'])) {
                array_shift($bt);
                }
            foreach (array_reverse($bt) as $caller) {
                if (isset($caller['file'])) {
                    $query = "# PREPARE IN {$caller['file']}#{$caller['line']}\n{$query}";
                    }
                }

            $stmt = new extended_mysqli_stmt($this, $query);
            $this->handle_result($stmt, $ignore_error);
            $stmt->handle_result($ignore_error);
            return $stmt;
            }

        public static function report_error($class, $errno, $error, $error_type = E_USER_ERROR) {
            $error_msg = "Error {$class}: ({$errno}) {$error}";
            $backtrace_error_str = get_error_backtrace($error_msg, __FILE__) . "\n";
            if (display_db_errors) {
                echo nl2br($backtrace_error_str);
                }
            trigger_error($backtrace_error_str, $error_type);
            $f = fopen(__DIR__ . '/var/log/sql_errors.log', 'a');
            fputs($f, '[' . date('d-M-Y H:i:s') . '] ' . $backtrace_error_str);
            fclose($f);
            }

        private function handle_result($res, $ignore_error) {
            if (!$res and
                   (($ignore_error === false)
                       or (is_array($ignore_error) and !in_array($this->errno, $ignore_error))
                       )
                   ) {
                self::report_error('extended_mysqli', $this->errno, $this->error);
                }
            }

        }


    class extended_mysqli_stmt extends mysqli_stmt {

        public function execute(?array $params = null): bool {
            global $db;

            $success = false;
            $retrys = 10;
            $sleep = 5; // seconds
            $retry_errors = [    // bei diesen Fehlern wird der Execute noch mal versucht
                1053,   // Server shutdown in progress
                1205,   // Lock wait timeout exceeded; try restarting transaction
                2006,   // MySQL server has gone away
                ];
            $reconnect_errors = [    // bei diesen Fehlern wird zuvor 5sec gewartet und die Verbindung zum DB-Server neu aufgebaut
                1053,   // Server shutdown in progress
                2006,   // MySQL server has gone away
                ];
            do {
                //$res = parent::execute($params);   
                // führt zu
                // Fatal error: Uncaught ArgumentCountError: mysqli_stmt::execute() expects exactly 0 arguments, 1 given
                // ?????
                
                $res = parent::execute();
                $success = !in_array($this->errno, $retry_errors);
                if (!$success) {
                    $retrys--;
                    trigger_error(__FILE__ . '#' . __LINE__
                        . " Retry after Database error ({$this->errno}: {$this->error}). {$retrys} Retries left", E_USER_NOTICE);
                    if (in_array($this->errno, $reconnect_errors)) {
                        sleep($sleep);
                        $db->reconnect();
                        }
                    }
                } while (!$success and ($retrys > 0));
            if (!$res) {
                extended_mysqli::report_error('extended_mysqli_stmt', $this->errno, $this->error);
                }
            return $res;
            }

        public function handle_result($ignore_error) {
            if ($this->errno and
                   (($ignore_error === false)
                       or (is_array($ignore_error) and !in_array($this->errno, $ignore_error))
                       )
                   ) {
                       extended_mysqli::report_error('extended_mysqli_stmt', $this->errno, $this->error);
                }
            }

        }

php mysqli prepared-statement
1个回答
1
投票

解决方案: 覆盖 mysqli_stmt::__construct 和 mysqli_stmt::bind_param 并将查询和参数存储到属性

public function __construct(mysqli $mysql, ?string $query = null) {
    parent::__construct($mysql, $query);
    $this->last_query = $query;
}        

public function bind_param(string $types, mixed &...$vars): bool {
    $this->bind_param_types = $types;
    $this->bind_param_vars = $vars;
    return parent::bind_param($types, ...$vars);
}

在extend_mysqli_stmt::execute()中重新连接mysqli-Object后添加以下代码:

$db->reconnect();
$this->close();
parent::__construct($db, $this->last_query);
parent::bind_param($this->bind_param_types, ...$this->bind_param_vars);

我觉得为现有对象实例调用父构造函数有点不安。我没有找到任何其他有效的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.