PHP PDO在SWOOLE模式下关闭数据库连接一些注意点
博客专区 > anoty 的博客 > 博客详情
PHP PDO在SWOOLE模式下关闭数据库连接一些注意点
anoty 发表于1周前
PHP PDO在SWOOLE模式下关闭数据库连接一些注意点
  • 发表于 1周前
  • 阅读 259
  • 收藏 10
  • 点赞 0
  • 评论 12

新睿云服务器60天免费使用,快来体验!>>>   

摘要: 最近在SWOOLE中使用PHP PDO扩展访问数据库的时候,发现了一个很有意思的事情,使用的时候需要注意一下

最近在swoole中使用php pdo扩展访问数据库的时候,发现了一个很有意思的事情。

我测试用的版本是 PHP 7.1.13

官方手册明确到告诉我们,使用PDO是这样关闭数据库连接的,只需要将PDO对象置为null即可,这段代码就是手册里的

<?php
try {
    $dbh = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', '');
    foreach($dbh->query('SELECT * from test') as $row) {
        print_r($row);
    }
    sleep(100);	
    $dbh = null;
} catch (PDOException $e) {
    print "Error!: " . $e->getMessage() . "<br/>";
    die();
}

我实际测下来也的确如此。 这是query执行前的MySQL的PROCESSLIST

mysql> SHOW PROCESSLIST;
+----+------+-----------------+------+---------+------+----------+------------------+
| Id | User | Host            | db   | Command | Time | State    | Info             |
+----+------+-----------------+------+---------+------+----------+------------------+
| 10 | root | localhost:56477 | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
+----+------+-----------------+------+---------+------+----------+------------------+
1 row in set (0.00 sec)

这是query运行后,PDO 对象置为null前的MySQL的PROCESSLIST

mysql> SHOW PROCESSLIST;
+----+------+-----------------+------+---------+------+----------+------------------+
| Id | User | Host            | db   | Command | Time | State    | Info             |
+----+------+-----------------+------+---------+------+----------+------------------+
| 10 | root | localhost:56477 | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
| 14 | root | localhost:56572 | test | Sleep   |    2 |          | NULL             |
+----+------+-----------------+------+---------+------+----------+------------------+
2 rows in set (0.00 sec)

PDO 对象置为null后,数据库连接的的确确关闭了,看来官方手册没错

mysql> SHOW PROCESSLIST;
+----+------+-----------------+------+---------+------+----------+------------------+
| Id | User | Host            | db   | Command | Time | State    | Info             |
+----+------+-----------------+------+---------+------+----------+------------------+
| 10 | root | localhost:56477 | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
+----+------+-----------------+------+---------+------+----------+------------------+
1 row in set (0.00 sec)

但是接下来有意思的事情就要发生了,看这段代码

<?php
$pdo = new PDO(
    'mysql:dbname=test;host=127.0.0.1;port=3306;charset=utf8mb4', 'root', '',
    [
        PDO::ATTR_TIMEOUT => 10,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
);

$s = $pdo->prepare("select * from test WHERE id=:id");
$s->execute(['id' => 1]);
$s->fetchAll();

$pdo = null;
sleep(100);

在PDO对象被置为null之后,MySQL的连接并没有被真的释放

mysql> SHOW PROCESSLIST;
+----+------+-----------------+------+---------+------+----------+------------------+
| Id | User | Host            | db   | Command | Time | State    | Info             |
+----+------+-----------------+------+---------+------+----------+------------------+
| 10 | root | localhost:56477 | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
| 18 | root | localhost:56606 | test | Sleep   |    3 |          | NULL             |
+----+------+-----------------+------+---------+------+----------+------------------+
2 rows in set (0.00 sec)

是不是很诡异?没关系,我们在改下这段代码

<?php
$pdo = new PDO(
    'mysql:dbname=test;host=127.0.0.1;port=3306;charset=utf8mb4', 'root', '',
    [
        PDO::ATTR_TIMEOUT => 10,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
);

$s = $pdo->prepare("select * from test WHERE id=:id");
$s->execute(['id' => 1]);
$s->fetchAll();

$s=null;
$pdo = null;
sleep(100);

在$s也被置为null之后将$pdo置为null,pdo申请的数据库连接就被真真正正的释放了

mysql> SHOW PROCESSLIST;
+----+------+-----------------+------+---------+------+----------+------------------+
| Id | User | Host            | db   | Command | Time | State    | Info             |
+----+------+-----------------+------+---------+------+----------+------------------+
| 10 | root | localhost:56477 | NULL | Query   |    0 | starting | SHOW PROCESSLIST |
+----+------+-----------------+------+---------+------+----------+------------------+
1 row in set (0.00 sec)

这是为什么?看下面这段代码

$pdo = new PDO(
    'mysql:dbname=test;host=127.0.0.1;port=3306;charset=utf8mb4', 'root', '',
    [
        PDO::ATTR_TIMEOUT => 10,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]
);

debug_zval_dump($pdo);

$s = $pdo->prepare("select * from test WHERE id=:id");

debug_zval_dump($pdo);

$s->execute(['id' => 1]);
$s->fetchAll();

$s=null;

debug_zval_dump($pdo);

$pdo = null;
sleep(100);

输出

object(PDO)#1 (0) refcount(2){
}
object(PDO)#1 (0) refcount(3){
}
object(PDO)#1 (0) refcount(2){
}

你会发现,当$pdo对象第一次打印的时候他的引用计数是2(为什么是2,而不是1呢?传送门 http://php.net/manual/zh/function.debug-zval-dump.php )在$pdo->prepare之后引用计数变成了3,也就是说,即使$pdo被置为null,它依然还是会被$s引用,并不会被PHP的GC给回收掉,所以它创建的数据库连接也不会被释放。

在FPM模式下的PHP,这个问题并不会引起什么问题,但是在SWOOLE这种运行环境下,假如你的PDO对象在被一些全局变量(传送门 https://wiki.swoole.com/wiki/page/p-zend_mm.html ) 引用,即使你将PDO对象置为null,你的数据库连接依然不会被释放。

标签: PHP PDO swoole-server
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 11
博文 19
码字总数 13631
评论 (12)
聪聆
Swoole这种垃圾东西 还是算了!:joy:
pz9042
没看懂,到底是swoole的问题还是PHP自己的问题?从描述上来看,是PHP自己的问题啊
卖爷爷的老红薯
unset()直接把变量干掉,可以不?
卖爷爷的老红薯

引用来自“聪聆”的评论

Swoole这种垃圾东西 还是算了!:joy:
写过一次,确实不想再试了。
半桶水_桶哥

引用来自“聪聆”的评论

Swoole这种垃圾东西 还是算了!:joy:

引用来自“卖爷爷的老红薯”的评论

写过一次,确实不想再试了。
感觉像是水平不够的样子。:smirk:
eechen

引用来自“pz9042”的评论

没看懂,到底是swoole的问题还是PHP自己的问题?从描述上来看,是PHP自己的问题啊
既不是Swoole的问题也不是PHP的问题,而是PHP的基础.
还有变量保持对$pdo的引用,$pdo当然不会被释放了.
PHP-FPM下会被"释放"只是一种错觉,因为在PHP-FPM这种短生命周期下,请求结束后就释放了所有资源,而Swoole Server是一种常驻内存的长生命周期的运行模式.不过如果$pdo操作是放在一个函数里,在函数结束时,$pdo执行prepare生成的$s变量会被自动释放,这时$pdo也会自动释放,在全局作用域操作pdo,则要注意楼主提到的问题.

所以说,楼主这篇博文主要是提长生命周期下的PDO使用时的注意事项,而不是说Swoole有bug,有些留评论的喷子请自重,免得秀无知的下限.
eechen
其实个人不建议Swoole下操作完成就释放PDO连接,因为Swoole是常驻内存的服务,PHP里的量支持常驻内存,把PDO连接做成一个static静态变量实现单例,供一个worker使用,不依赖PDO提供的持久连接其实就能实现长连接,关键是要做好断线重连,即每次获取PDO连接时都检测一次,连接可用,返回PDO对象,连接不可用,则重新建立连接:
function app_pdo() {
  static $db = null;
  static $conn = false;
  if (!$conn) {
    connect:
    try {
      $db = new PDO();
    } catch (PDOException $e) {
      // 连接失败,返回false
      $conn = false;
      return false;
    }
    // 连接成功,返回$db对象
    $conn = true;
    return $db;
  } else {
    if (php_sapi_name() === 'cli') {
      // 检查连接状态,连接可用,返回$db对象,连接不可用,则重新建立连接
      @$db->query('SELECT 1');
      $errorInfo = $db->errorInfo();
      $errno = $errorInfo1;
      if ($errno != 0) goto connect;
    }
    return $db;
  }
}
$db = app_pdo();
$db->query($sql)->fetchAll();
eechen

引用来自“聪聆”的评论

Swoole这种垃圾东西 还是算了!:joy:
一个Laravel脑残粉,有什么资格喷Swoole?
优雅你的优雅去吧,呵呵.
卖爷爷的老红薯

引用来自“聪聆”的评论

Swoole这种垃圾东西 还是算了!:joy:

引用来自“eechen”的评论

一个Laravel脑残粉,有什么资格喷Swoole?
优雅你的优雅去吧,呵呵.
swoole,phalcon,laravel,都在用。请不要这样抨击laravel,各有各的好处,各自定位也不同。
eechen

引用来自“卖爷爷的老红薯”的评论

unset()直接把变量干掉,可以不?
写内存常驻的PHP程序时,建议还是了解下PHP的垃圾回收机制.
不是说unset,变量就一定会被释放,我举个例子,你自己测试一下:
echo memory_get_usage() . "\n";
$a = file_get_contents('/path/to/a/file');
echo memory_get_usage() . "\n";
$b = &$a; // 变量$a的引用计数增加
unset($a); // unset并不能释放$a占用的资源(内存)
echo memory_get_usage() . "\n";
unset($b); // unset释放$b后,$a占用的内存才会被真正释放
echo memory_get_usage() . "\n";
eechen

引用来自“聪聆”的评论

Swoole这种垃圾东西 还是算了!:joy:

引用来自“eechen”的评论

一个Laravel脑残粉,有什么资格喷Swoole?
优雅你的优雅去吧,呵呵.

引用来自“卖爷爷的老红薯”的评论

swoole,phalcon,laravel,都在用。请不要这样抨击laravel,各有各的好处,各自定位也不同。
明明是有人无脑喷Swoole是垃圾,而那个人正好是个Laravel粉.
难道只许Laravel粉无脑喷Swoole,不准别人抨击脑残粉么?
JPer
phper没有常驻内存的观念... 所以说swoole不好...
×
anoty
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: