我有一些脚本在数据库上运行很长时间(有时几个小时),我想让它们尽可能强大。
为此我创建了一个
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);
}
}
}
解决方案: 覆盖 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);
我觉得为现有对象实例调用父构造函数有点不安。我没有找到任何其他有效的解决方案。