单例类和mysqli事务

6

我有一个单例类 DataBase 用于共享数据库连接:

class DataBase
{
  private static $mysqli;

  final private function __construct() {}

  public static function getInstance()
  {
    if (!is_object(self::$mysqli)) self::$mysqli = new mysqli($H,$U,$P,$B);
    return self::$mysqli;
  }

  private function __destruct()
  {
    if (self::$mysqli) self::$mysqli->close();
  }

  private function __clone() {} //no cloning in singleton
}

我在许多其他类中使用这个单例对象,并且在每个类的构造函数中从单例中检索数据库连接,例如:

class Customer
{
  public $db;

  function __construct()
  {
    $this->db=DataBase::getInstance();
  }

  public function create()
  {
    $this->db->query('INSERT INTO customers SET ... WHERE id=100'); //e.g.
  }

}

并且

class Product
{
  public $db;

  function __construct()
  {
    $this->db=DataBase::getInstance();
  }

  public function create()
  {
    $this->db->query('INSERT INTO products SET ... WHERE id=200'); //e.g.
  }
}

由于我所有的类都共享单例类提供的相同连接,所以我的问题是,我能否从一个类开始数据库事务,并从另一个类回滚/提交它。我的意思是,下面的代码是否正确地创建了一个数据库事务,用于在一个数据库事务中创建客户和产品(类模拟后面的数据库记录):

由于我所有的类都共享单例类提供的同一连接,因此我的问题是,我能否从一个类开始数据库事务,然后从另一个类回滚/提交它。我的意思是,下面的代码是否正确地创建了一个数据库事务,用于在一个数据库事务中创建客户和产品(类模拟后面的数据库记录):

$Customer=new Customer();
$Customer->db->begin_transaction();
...
$Customer->create();
...
$Product=new Product();
$Product->create();
...
$Product->db->commit();

如果所有用户都共享来自单例类的相同连接,那么此代码将如何在多用户环境中运作?


2
事务是按连接计算的。如果您的所有对象实例都使用相同的连接,那么您肯定可以在一个对象中“开始”并在另一个对象中“回滚”。问题是您是否应该这样做。除非您的对象是根据其数据库连接的基础完全更改而编写的,否则您将遇到麻烦。 - Marc B
假设这段代码与开始/提交事务的代码位于同一个PHP页面中。我在想,如果两个用户同时启动此页面,会发生什么。是否有一个用户会在事务中干扰另一个用户。 - sbrbot
你可能想要对工作单元模式进行一些研究。 - Jasper N. Brouwer
2个回答

5

正如@Marc B所提到的,事务是基于连接的,因此通常情况下您可以在两个不同的对象中启动和提交事务。

但是,您应该确保您没有使用持久连接(关于PHP和mysqli的连接处理,请阅读更多http://php.net/manual/en/mysqli.quickstart.connections.php。持久连接在脚本之间进行汇集,并可能使您的应用程序面临事务干扰的风险。

另外,请记住某些语句会导致隐式提交(如http://php.net/manual/en/mysqli.quickstart.transactions.php所述)。 您还应验证,在脚本执行过程中,您与数据库的连接不会超时(在某些情况下可能会发生)。我建议从检查MySQL的wait_timeout http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html#sysvar_wait_timeout设置开始。

我认为你应该认真考虑你的应用程序设计。我坚信,DB对象应该仅在Model对象中可用,并且只能从其中调用,而不是在代码中显式调用。

如果您决定考虑事务,请记住在您的DataBase类中放置适当的方法(例如,在对象销毁时添加autocommit(TRUE)和rollback(),如果已启动事务,则更好地确保安全。

我强烈建议您选择(或更改)为InnoDB(对表锁定和事务有很好的支持) - 在The InnoDB Transaction Model and Locking中阅读更多相关信息。此外,关于事务的大量有用信息在这里的文档中:Mysql transactions and locking


在php中,持久化的数据库连接不能保持数据库会话处于活动状态。http://php.net/manual/en/features.persistent-connections.php 然而,锁定确实会导致问题,现在它的工作方式有点奇怪。 - Barkermn01
感谢@Kleskowy的建议和进一步阅读链接,您的一些建议很好但是已经广为人知,比如使用InnoDB(当然),添加autocommit()(我使用begin_transaction()和autocommit(FALSE)一样),建议只在Model对象中使用与DB相关的功能(我的Customer和Product都是Model对象,并且只有他们使用DB),并且利用对象销毁方法进行事务回滚(以防万一,因为目前我只有在捕获异常时才回滚)。 - sbrbot
非常棒的帖子@Kleskowy。那你是MVC模式的支持者吗? - b_dubb
@b_dubb 是的,MVC或滚蛋;) 没有意义的代码,对象使构建和调试任何应用程序变得更加容易和清晰。 - Kleskowy

1
是的,如果用户从web服务器apache、nginx等访问您的php,则您的代码在多用户环境中可以正常工作。php将创建一个新的连接资源(您可以在mysql的进程列表中看到它们)。您的单例DB类只是保证在单个请求中,无论包含的php或类需要连接,它们都将使用该连接,因此,如果您不使用一些pctnl fork流程,逻辑上,您的所有代码将按顺序运行,这将确保所有提交和回滚都能正常工作。
当然,如果您想执行一些事务查询,则您的表应该是InnoDB,myisam不支持事务。

因此,在多用户环境中,PHP在Apache2服务器上保证了用户之间的代码隔离。如果我理解正确,PHP将为每个用户(会话)创建一个单例类!?因此,连接仅在用户范围内重用,而不是在不同用户之间重用! - sbrbot
是的,每次HTTP请求时PHP都会创建一个新的连接到您的数据库,但是您的单例类将为每个请求提供一个连接,只要您在所有包含的类(如PHPES等)中使用它。 - Santa's helper

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