全局还是单例用于数据库连接?


81

在PHP中使用单例而不是全局的数据库连接有什么好处?我觉得使用单例而不是全局会使代码不必要地变得复杂。

全局编码

$conn = new PDO(...);

function getSomething()
{
    global $conn;
    .
    .
    .
}

用Singleton编码

class DB_Instance
{
    private static $db;

    public static function getDBO()
    {
        if (!self::$db)
            self::$db = new PDO(...);

        return self::$db;
    }
}

function getSomething()
{
    $conn = DB_Instance::getDBO();
    .
    .
    .
}

如果除了全局或单例之外,还有一种更好的初始化数据库连接的方法,请提及它并说明它比全局或单例具有的优势。


如果您打算在自定义会话处理程序中使用PDO,则应注意一些特殊之处:stackoverflow.com/questions/2595860/pdo-prepare-silently-fails
Wabbitseason 2010年

Answers:


105

我知道这很旧,但是Dr8k的答案几乎就在那里。

当您考虑编写一段代码时,假设它会改变。这并不意味着您假设它会在将来的某个时刻进行各种更改,而是会进行某种形式的更改。

将其设定为目标可以减轻将来进行更改的痛苦:全球性组织很危险,因为很难在一个地方进行管理。如果将来我想让该数据库连接上下文知道怎么办?如果我希望它每使用5次就关闭并重新打开一次,该怎么办?如果我决定为了扩展我的应用程序而要使用10个连接池,该怎么办?还是可配置数量的连接?

一个单身的工厂为您提供了灵活性。我以很少的额外复杂性进行设置,并且获得的不仅仅是访问同一连接。我将能够以一种简单的方式更改以后如何将连接传递给我。

请注意,我说的是singleton工厂,而不是单例。单例与全局之间几乎没有什么区别。因此,没有理由建立单例连接:为什么您会花时间设置可以创建常规全局变量的时间呢?

工厂能为您带来什么是获得连接的原因,并且是一个单独的位置来决定您将获得的连接。

class ConnectionFactory
{
    private static $factory;
    private $db;

    public static function getFactory()
    {
        if (!self::$factory)
            self::$factory = new ConnectionFactory(...);
        return self::$factory;
    }

    public function getConnection() {
        if (!$this->db)
            $this->db = new PDO(...);
        return $this->db;
    }
}

function getSomething()
{
    $conn = ConnectionFactory::getFactory()->getConnection();
    .
    .
    .
}

然后,在6个月内,当您的应用程序非常有名,并且变得笨拙而断线,并且您决定需要的连接不止一个时,您要做的就是在getConnection()方法中实现一些池化。或者,如果您决定要实现SQL日志记录的包装器,则可以传递PDO子类。或者,如果您决定要在每次调用时都建立一个新的连接,则可以这样做。它是灵活的,而不是僵化的。

16行代码,包括花括号,可以节省您数小时的时间,也可以节省数小时的重构工作。

请注意,我不考虑这种“功能蠕变”,因为在第一轮中我没有进行任何功能实现。它是边界线“ Future Creep”,但是在某些时候,“为今天的明天编码”始终是一件坏事的想法对我来说并不成立。


3
不确定,但是我认为您的意思是:public function getConnection(){if(!$ this-> db)$ this-> db = new PDO(...); 返回$ this-> db; }
Dycey

谢谢!通过使用return self::$factory->getConnection();代替会失去使用此方法的任何好处return self::$factory;吗?
尼科·伯恩斯

3
我想在我正在做的项目中使用此代码。我可以引用此页面吗?如果是,此文本使用什么许可证?是CC-BY,BSD还是其他?我目前声称这是“未知-相信公共领域”,但我想在上面注明正确的许可条件。
JonTheNiceGuy 2011年

2
我认为我们应该在getConnection()方法中使“ $ db”“ $ this-> db”,否则“私有$ db”变量“不使用”,在任何地方都没有正式引用。
developer10

嗨,您的解决方案看起来很酷且可扩展。请让我知道这里需要实现什么self :: $ factory = new ConnectionFactory(...);
阿南达

16

我不确定我能否回答您的特定问题,但想建议如果对于基于Web的系统,全局/单例连接对象可能不是最好的主意。DBMS通常设计为以有效方式管理大量唯一连接。如果您使用的是全局连接对象,那么您需要做几件事:

  1. 强制页面顺序执行所有数据库连接,并终止异步页面加载的任何尝试。

  2. 可能在数据库元素上保持打开锁的时间超过了必要的时间,从而降低了整体数据库的性能。

  3. 最大化数据库可以支持的并发连接总数,并阻止新用户访问资源。

我相信还有其他潜在后果。请记住,此方法将尝试为每个访问该站点的用户维持数据库连接。如果您只有一个或两个用户,则不成问题。如果这是一个公共网站,并且您想要流量,那么可伸缩性将成为问题。

[编辑]

在较大规模的情况下,每次碰到数据集时都创建新连接可能是不好的。但是,答案不是创建全局连接并将其重新用于所有内容。答案是连接池。

使用连接池,可以维护许多不同的连接。当应用程序需要连接时,将检索池中的第一个可用连接,然后在完成其工作后将其返回到池中。如果请求连接而没有连接,则将发生以下两种情况之一:a)如果未达到允许的最大连接数,则打开新连接,或b)强制应用程序等待连接可用。

注意:在.Net语言中,默认情况下,连接池由ADO.Net对象处理(连接字符串设置所有必需的信息)。

感谢Crad对此发表评论。


我想使用单例可以给我带来尽可能晚地初始化连接的优势,而如果使用全局,则可能会在脚本开始时进行初始化。我对么?
伊姆兰

正确,如果您走这条路,单身人士将提供这一优势。
Dr8k

实际上,这一切都取决于规模。在大规模的Web部署中,大量的DB连接是有害的。这就是为什么存在PostgreSQL等pgBouncer之类的应用程序,而Java进行资源池的原因。
加文·罗伊,

是的,但是我认为适当的连接池与仅使用全局连接对象不同。连接池仍然使用多个连接,它只是限制了最大数量,并随着时间的推移重新使用它们以减轻设置开销。
Dr8k

5
请注意,在PHP的情况下,“ global”不会使变量在PHP页面中成为全局变量。这只是意味着可以从函数内部对其进行访问。
Ates Goral,

7

创建singleton方法是为了确保任何类只有一个实例。但是,由于人们将它用作捷径全球化的一种方式,因此它被称为惰性和/或错误编程。

因此,我将忽略global和Singleton,因为它们并不是真正的OOP。

您正在寻找的是依赖注入

您可以在http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection上查看与依赖项注入相关的基于PHP的易于阅读的信息(带有示例)。


3

两种模式都达到相同的最终效果,为您的数据库调用提供了一个单一的访问点。

在特定实现方面,单例具有一个小的优点,即只有在至少其他一种方法请求它之前,才启动数据库连接。实际上,在我编写的大多数应用程序中,这并没有多大区别,但是如果您有一些页面/执行路径根本不进行任何数据库调用,则这是潜在的优势,因为这些页面不会曾经请求连接到数据库。

另一个小的区别是,全局实现可能会无意间踩踏了应用程序中的其他变量名。您不太可能会意外地声明另一个全局$ db引用,尽管您可能会意外地覆盖它(例如,当您打算编写if($ db == null)时会写if($ db = null)。单例对象可以防止这种情况。


2

如果您不打算使用持久性连接,并且在某些情况下不这样做,那么我发现从概念上讲,单例比面向对象设计中的全局更可口。

在真正的OO体系结构中,单例比每次创建对象的新实例都有效。


2

在给定的示例中,我认为没有理由使用单例。根据经验,如果我只考虑允许一个对象的单个实例,如果语言允许,我更喜欢使用全局变量


1

通常,我会使用单例进行数据库连接...您不想在每次需要与数据库进行交互时都创建新的连接...这可能会损害网络的性能和带宽...为什么创建一个新的,当有一个可用的...我的2美分...

温迪


通过在全局范围内初始化连接,我将每页初始化一次连接,并在需要与数据库交互的函数中使用该全局变量。
伊姆兰

使用单例进行数据库连接与在每次与DBMS进行交互时不重新创建连接不同。单身就是这样;给定类的一个实例,并且唯一一个实例可以全局存在。您可能需要立即连接到不同的数据库。
罗布

我指的是一个单例类,用于管理与数据库的连接。每当您想与dbms交互时,我都看不到创建新连接对象的意义。当然,如果您需要一次连接到其他数据库,则可能需要创建另一个连接对象。
RWendi

0

这很简单。切勿使用全局或单例。


4
当涉及到Singleton模式时,请始终说“永不”
1800信息

3
如果您需要多个日志提供者怎么办?谁说我无法登录文件和控制台?
1800信息

2
因此,您需要将一个反模式(Singleton)与另一个(神对象)组合在一起
1800信息

3
是。通过允许全局类对象,每种主要OOP语言的设计人员也是如此。如果您是一神论者,并且您想要一个对象来代表上帝,那么您正在寻找的是一个单例上帝对象。还有其他不正确的地方。
史蒂夫·杰索普

4
如果我是月度主义者,并且想创建一个God对象,则可以指定使用MonotheistAbstractFactory Pattern创建我的God对象吗?通过指定PolytheistAbstractfactory,这将使多神论用户也可以使用我的程序。
1800信息

0

作为建议,单例全局均有效,并且可以在同一系统,项目,插件,产品等中加入……就我而言,我生产用于Web(插件)的数字产品。

我在主类中仅使用单例,并且原则上使用它。我几乎不使用它,因为我知道主类不会再次实例化它

<?php // file0.php

final class Main_Class
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
    public final function __clone()
    {
        throw new LogicException("Cloning timer is prohibited");
    }
    public final function __sleep()
    {
        throw new LogicException("Serializing timer is prohibited");
    }
    public final function __wakeup()
    {
        throw new LogicException("UnSerializing timer is prohibited");
    }
}

几乎所有二级类的全局用法,例如:

<?php // file1.php
global $YUZO;
$YUZO = new YUZO; // YUZO is name class

在运行时,我可以使用Global在同一实例中调用它们的方法和属性,因为我不需要主产品类的另一个实例。

<?php // file2.php
global $YUZO;
$YUZO->method1()->run();
$YUZO->method2( 'parameter' )->html()->print();

我对全局变量的理解是使用相同的实例来使产品正常工作,因为对于相同类的实例,我不需要工厂,通常该实例工厂用于大型系统或非常罕见的用途。

In conclusion:,如果您已经非常了解反模式SingletonGlobal,则必须使用这两个选项之一或将它们混合使用,但是如果我建议不要滥用,因为有很多程序员非常例外且忠于编程的OOP,将它用于在执行时间内大量使用的主类和辅助类。(它为您节省了大量CPU)。😉

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.