MySQLi 在析构函数中立即关闭

Posted

技术标签:

【中文标题】MySQLi 在析构函数中立即关闭【英文标题】:MySQLi close in destructor fires immediately 【发布时间】:2017-09-03 10:15:13 【问题描述】:

我最近一直在重新设计一个古老的 php 应用程序,它的大部分代码都包含在每个页面的 1000 多行 PHP 文件中。我首先将大部分代码重构为类。我最近一直在研究数据库连接,并开始为它编写一个类。我决定将$mysqli->close() 扔到析构函数中(我正在使用OO 方法)。

不幸的是,我几乎立即就遇到了问题。当页面完成渲染时(或者当没有更多对 DB 对象的引用时)而不是关闭 MySQLi 连接,它只是立即关闭。我通过编写一个简单的测试对此进行了测试:

$db = new DBConnect(); //My abstraction class
$db->getSQL()->query("SELECT 1"); //Query fails. Error message states connection closed.

我的析构函数如下所示:

public function __destruct() 
    $this->mysqli->close();

我的构造函数如下所示:

public function __construct() 
     $this->mysqli=new mysqli(\MainConfig::$database['host'], \MainConfig::$database['user'], \MainConfig::$database['pass'], \MainConfig::$database['db']);
     if($this->mysqli->connect_error) 
          die('Connect error (' . $this->mysqli->connect_errno . ') ' . $this->mysqli->connect_error); //This is not ever fired.
     

我知道这不是关闭连接的其他代码,因为如果我注释掉 $this->mysqli->close() 行,那么代码会按预期工作。在我看来,析构函数 立即 触发,这不是所需的行为。我是否误解了它们的工作原理?

【问题讨论】:

看起来有一些代码丢失导致了这种情况。 PHP 垃圾收集器不会破坏对象,除非不再引用它。 每个DBConnect 实例是否有自己的mysqli 连接对象,或者您是否缓存它们并在不同对象之间共享它们? @b.enoit.be 项目中的所有源文件都复制了这种行为,包括那里的测试代码。我无法发布 1000 行长的源文件! 也发布构造函数。 恕我直言,通常没有必要明确关闭 mysqli 连接。如果 $db 是一个全局变量,它直到脚本结束才会消失,并且连接会自动关闭。 【参考方案1】:

我能够重现您使用代码描述的行为:

<?php

class DestructorTest 
    public function getter() 
        print "DestructorTest::getter() called\n";
        return new CallableTest();
    

    public function __destruct() 
        print "DestructorTest destructor called\n";
    


class CallableTest 
    public function method() 
        print "CallableTest::method() called\n";
    


(new DestructorTest())->getter()->method();

哪个打印:

DestructorTest::getter() called
DestructorTest destructor called
CallableTest::method() called

它的长短是:只要没有对对象的引用,就可以调用析构函数。这甚至可能发生在一行的中间——例如,在这种情况下,在调用 DestructorTest:getter() 之后,DestructorTest 对象不再可访问,因此它被销毁了。

我的建议:

您不需要关闭 MySQLi 句柄。它们已经有一个内部析构函数,当它们被垃圾回收时会关闭它们。

事实上:避免编写一般的析构方法。 (这适用于许多编程语言,而不仅仅是 PHP。)它们通常不会在您期望的时候被调用,并且往往会导致奇怪的、难以调试的行为。

如果你要忽略这一点并编写析构函数,那么你需要确保你不会无意中破坏你在课堂之外“泄露”了引用的东西。就您的代码而言,您已经允许类外部的代码获取对 MySQLi 句柄的引用,但是一旦您的对象消失,您就会销毁该句柄——即使句柄仍在外面使用。

【讨论】:

但是他将对象分配给变量,而不是在临时结果上调用方法。这应该保留指针并防止调用析构函数。 我同意,尽管我开始意识到在这种情况下几乎不需要析构函数。我目前完全禁用了代码工作,我可能就这样离开它。为什么如果我不关闭 MySQLi 连接,代码仍然有效,这对我来说没有意义。这不是说调用了析构函数,但对象仍然存在吗? @Barmar 这是真的。我无法根据问题中包含的代码来解释行为;我提供的代码是最接近重现问题的代码。 使用连接池来回收现有连接也是值得的。

以上是关于MySQLi 在析构函数中立即关闭的主要内容,如果未能解决你的问题,请参考以下文章

在析构函数中终止当前线程

为啥在析构函数中抛出异常时不调用重载删除?

在析构函数中捕获异常

为啥我必须在析构函数中调用 MPI.Finalize() ?

即使内存不是动态分配的,在析构函数中是不是需要`delete ptr;`? [复制]

c ++在析构函数中删除向量类成员内存