PHP单例数据库连接模式

12

我一直在使用PHP和MySQL进行一个小项目的开发。在这方面我有很多关于连接管理的最佳实践的阅读。

我也实现了(遵循一些找到的帖子)一个单例类来管理MySQL连接。

require_once 'config.inc.php';
class DbConn {

    private static $instance;
    private $dbConn;

    private function __construct() {}

    /**
     *
     * @return DbConn
     */
    private static function getInstance(){
        if (self::$instance == null){
            $className = __CLASS__;
            self::$instance = new $className;
        }

        return self::$instance;
    }

    /**
     *
     * @return DbConn
     */
    private static function initConnection(){
        $db = self::getInstance();
        $connConf = getConfigData();
        $db->dbConn = new mysqli($connConf['db_host'], $connConf['db_user'], $connConf['db_pwd'],$connConf['db_name']);
        $db->dbConn->set_charset('utf8');
        return $db;
    }

    /**
     * @return mysqli
     */
    public static function getDbConn() {
        try {
            $db = self::initConnection();
            return $db->dbConn;
        } catch (Exception $ex) {
            echo "I was unable to open a connection to the database. " . $ex->getMessage();
            return null;
        }
    }
}

但是...如果我的网站同时有大约10,000个访问者,并且我每次都调用我的单例对象,那么我应该期望会出现性能问题吗?我的意思是,我是否应该使用连接池而不是单例?


5
那么矛盾在哪里呢?你可以设置连接为持久化并使用单例。这种模型被用来确保单个请求不会连接两次数据库,但是两个不同的客户端将运行两个不同的脚本实例,因此将使用两个不同的连接。 - mishu
1
@mishu 当然。我真是太蠢了。谢谢! - LucasM
1
@mishu:谢谢你的解释。我们中的许多人没有考虑过这个问题。 - kta
1个回答

15
在PHP中使用单例被认为是一种不好的实践。根据我的经验,它们最令人困扰的问题是单元测试。当测试单例时,很难确保两个测试是独立的。
我会将“只有一个实例应该存在”的约束责任委托给创建Db对象的代码。
此外,对我来说,似乎有一种误解,即单例在PHP中与其他语言的工作方式不同:例如,如果有10,000个并发请求,则每个请求都在单独的PHP进程或线程中运行,这意味着它们都有自己的“单例”实例,在常见的Web后端场景中,除了单个请求之外,没有共享此对象的情况。
在PHP中没有“连接池”,但您可以使用mysqli持久连接来连接mysql。可以通过在创建mysqli时在主机名前面添加“p:”来实现。这可能会有所帮助,但请小心处理(表示首先阅读文档)。

然而,仅限于理论,PHP中的单例模式必须意识到某人可能会使用clone。这意味着在你的情况下,有可能会这样做:

$db = DB::getInstance();
$db2 = clone $db; 

为了避免这种情况,您可以按照以下方式实现__clone()方法:
public function __clone() {
    throw new Exception("Can't clone a singleton");
}

非常感谢。除此之外,您还有什么建议吗?我读了关于依赖注入的文章。干杯! - LucasM
这与翻译无关。我只需执行$db = new Db(),然后确保所有需要它的代码层级都可以访问此实例。Symfony2正在使用他们的“服务”概念。在我看来,这是有意义且良好的设计方法...(从未想过有一天我会这样说关于Symfony :) 但我确实认为这是一个不错的实现) ... 请查看:http://symfony.com/doc/current/book/service_container.html - hek2mgl
明白了!再次感谢。干杯 - LucasM
15
仅仅因为要避免单例而避免单例通常被认为是不好的做法。每种设计模式都有正确使用的时机和场合,只要不过度或不足地使用它们即可。 - Gilles Lesire
@Nathan 当代码以将db对象作为参数的方式编写时,这绝对是一个优点。但如果您已经有了这个,为什么不直接传递类似于 your_function($app->get_db_instance(reconnect=false)) 的东西,而不是使用单例模式?这可以在 $app 的(正常)生命周期内始终返回相同的实例,但返回值(db实例)并没有被字面上实现为单例模式。 - hek2mgl
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接