在PHP应用开发过程中,数据库操作几乎是所有业务场景的必备环节,而数据库连接的创建和销毁会占用系统资源,当并发请求量上升时,频繁创建连接句柄会导致服务器负载升高,甚至引发数据库连接数耗尽的问题,使用单例模式开发数据库类可以有效解决这类问题。
什么是PHP单例模式
单例模式是一种创建型设计模式,核心目的是确保一个类在整个应用生命周期中只有一个实例,并且提供全局的访问点。在PHP中实现单例模式需要满足三个条件:
- 构造函数私有化,禁止外部直接通过new关键字实例化对象
- 定义一个静态的私有成员变量保存类的唯一实例
- 提供一个公共的静态方法,用于返回类的唯一实例,同时在该方法中判断实例是否已经存在,不存在则创建
非单例模式数据库类的问题
如果不使用单例模式开发数据库类,每次需要进行数据库操作时都会创建新的连接实例,以常规的MySQLi数据库类为例,非单例模式的实现如下:
<?php
class NormalMysql {
private $conn;
public function __construct($host, $user, $pass, $db) {
$this->conn = new mysqli($host, $user, $pass, $db);
if ($this->conn->connect_error) {
die("连接失败: " . $this->conn->connect_error);
}
echo "创建新的数据库连接句柄" . PHP_EOL;
}
public function query($sql) {
return $this->conn->query($sql);
}
public function close() {
$this->conn->close();
}
}
// 多次调用创建多个实例
$db1 = new NormalMysql("127.0.0.1", "root", "123456", "test");
$db2 = new NormalMysql("127.0.0.1", "root", "123456", "test");
$db3 = new NormalMysql("127.0.0.1", "root", "123456", "test");
?>
上述代码中每次实例化NormalMysql类都会创建新的数据库连接,假设一个页面中有3次数据库操作,就会创建3个连接句柄,在高并发场景下,1000个并发请求就会创建1000个数据库连接,很容易超过数据库的最大连接数限制,同时每次创建连接都需要进行TCP握手、权限验证等操作,会额外增加CPU和内存的开销。
单例模式数据库类的实现
使用单例模式开发数据库类,可以保证整个应用运行中只会有一个数据库连接实例,避免重复创建连接的开销,实现代码如下:
<?php
class SingletonMysql {
private static $instance = null;
private $conn;
// 私有构造函数,禁止外部实例化
private function __construct($host, $user, $pass, $db) {
$this->conn = new mysqli($host, $user, $pass, $db);
if ($this->conn->connect_error) {
die("连接失败: " . $this->conn->connect_error);
}
echo "创建唯一的数据库连接句柄" . PHP_EOL;
}
// 禁止克隆对象
private function __clone() {}
// 禁止反序列化创建对象
private function __wakeup() {}
// 公共静态方法获取唯一实例
public static function getInstance($host, $user, $pass, $db) {
if (self::$instance === null) {
self::$instance = new self($host, $user, $pass, $db);
}
return self::$instance;
}
public function query($sql) {
return $this->conn->query($sql);
}
public function close() {
if (self::$instance !== null) {
$this->conn->close();
self::$instance = null;
}
}
}
// 多次调用获取同一个实例
$db1 = SingletonMysql::getInstance("127.0.0.1", "root", "123456", "test");
$db2 = SingletonMysql::getInstance("127.0.0.1", "root", "123456", "test");
$db3 = SingletonMysql::getInstance("127.0.0.1", "root", "123456", "test");
?>
上述代码中,SingletonMysql类的构造函数被私有化,外部无法直接new实例化,只能通过getInstance静态方法获取实例,方法内部会判断$instance是否已经存在,只有第一次调用时才会创建连接,后续调用都会返回已有的实例,因此无论调用多少次getInstance,都只会创建一个数据库连接句柄。
两种模式的资源消耗对比
我们可以通过简单的测试对比两种模式的资源消耗差异:
| 对比项 | 非单例模式数据库类 | 单例模式数据库类 |
|---|---|---|
| 10次数据库操作创建的连接数 | 10个 | 1个 |
| 1000并发请求的连接数 | 1000个 | 1个(PHP-FPM模式下每个进程1个) |
| 连接创建额外开销 | 每次操作都有TCP握手、权限验证开销 | 仅第一次操作有额外开销 |
| 内存占用 | 随连接数线性增长 | 固定占用一个连接的内存 |
单例模式数据库类的注意事项
虽然单例模式可以减少数据库连接开销,但使用时也需要注意以下问题:
- PHP的运行模式如果是CLI模式,单例实例会存在于整个脚本生命周期,不需要额外处理;如果是PHP-FPM模式,每个进程是独立的,单例实例仅存在于当前进程中,不同进程的实例不共享,这是正常情况,不会造成问题。
- 如果需要连接不同的数据库,单例模式的基础实现无法满足,可以扩展为带参数的单例,根据连接参数生成不同的实例键,保存多个不同连接的实例。
- 单例模式虽然减少了连接开销,但长时间不操作数据库可能会导致连接超时,需要在查询前判断连接是否有效,无效则重新建立连接。
总结
使用PHP单例模式开发数据库类的核心价值在于减少数据库连接句柄的重复创建,降低系统的资源消耗,避免数据库连接数耗尽的问题,尤其在高并发场景下效果更加明显。开发者在实际项目中可以根据业务需求,结合单例模式的实现规则,开发出高效稳定的数据库操作类,提升应用的整体性能。