文档章节

PHP PDO在SWOOLE模式下关闭数据库连接一些注意点

anoty
 anoty
发布于 02/11 20:30
字数 936
阅读 945
收藏 12

最近在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、GO相关踩坑实践技巧请关注我的公众号:PHP架构师

© 著作权归作者所有

共有 人打赏支持
anoty
粉丝 20
博文 34
码字总数 22429
作品 0
浦东
加载中

评论(14)

eechen
eechen

引用来自“聪聆”的评论

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

引用来自“eechen”的评论

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

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

swoole,phalcon,laravel,都在用。请不要这样抨击laravel,各有各的好处,各自定位也不同。

引用来自“eechen”的评论

明明是有人无脑喷Swoole是垃圾,而那个人正好是个Laravel粉.
难道只许Laravel粉无脑喷Swoole,不准别人抨击脑残粉么?

引用来自“曲空”的评论

首先我从不粉任何东西,再者 sWOole垃圾是事实,不能因为你这个传销人员的各种场合洗白就能否定它的垃圾。
果然是喷子中的垃圾,一点干货都喷不出来,还有脸哔哔.
曲空
曲空

引用来自“聪聆”的评论

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

引用来自“eechen”的评论

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

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

swoole,phalcon,laravel,都在用。请不要这样抨击laravel,各有各的好处,各自定位也不同。

引用来自“eechen”的评论

明明是有人无脑喷Swoole是垃圾,而那个人正好是个Laravel粉.
难道只许Laravel粉无脑喷Swoole,不准别人抨击脑残粉么?
首先我从不粉任何东西,再者 sWOole垃圾是事实,不能因为你这个传销人员的各种场合洗白就能否定它的垃圾。
JPer
JPer
phper没有常驻内存的观念... 所以说swoole不好...
eechen
eechen

引用来自“聪聆”的评论

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

引用来自“eechen”的评论

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

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

swoole,phalcon,laravel,都在用。请不要这样抨击laravel,各有各的好处,各自定位也不同。
明明是有人无脑喷Swoole是垃圾,而那个人正好是个Laravel粉.
难道只许Laravel粉无脑喷Swoole,不准别人抨击脑残粉么?
eechen
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";
卖爷爷的老红薯
卖爷爷的老红薯

引用来自“聪聆”的评论

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

引用来自“eechen”的评论

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

引用来自“聪聆”的评论

Swoole这种垃圾东西 还是算了!:joy:
一个Laravel脑残粉,有什么资格喷Swoole?
优雅你的优雅去吧,呵呵.
eechen
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
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,有些留评论的喷子请自重,免得秀无知的下限.
半桶水_桶哥
半桶水_桶哥

引用来自“聪聆”的评论

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

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

写过一次,确实不想再试了。
感觉像是水平不够的样子。:smirk:
swoole实现Timer定时器、心跳检测及Task进阶实例:mysql连接池

Table of Contents 1.Timer定时器 2.心跳检测 3.Task进阶:MySQL连接池 环境说明: 系统:Ubuntu14.04 (安装教程包括CentOS6.5) PHP版本:PHP-5.5.10 swoole版本:1.7.7-stable 1.Timer定时...

太阳黑子
2016/10/28
127
0
swoole项目思维转换 -- mysql server gone away

mysql做为php的黄金搭档和互联网上应用最广泛的数据库,免不了天天与之打交道,不少朋友在熟悉swoole的使用之后,也趟平了不少坑,准备实战了,终于上线了,正愉快的体验swoole带来的巨大改进...

杨太化
2015/10/15
182
0
秒杀 计数器 直播--php 实现数据库连接池、直播平台

yaf项目快速开发(兼容php7): yaf project rapid development, integration of the db action class support chain operation, support separate read and write, pdo, mysqli, mongo, up......

qieangel
2015/09/07
11.5K
3
EasySwoole框架与Egg.js框架查询Mysql 性能比较

鉴于最近前端er 因为好不容易有了一个node ,终于可以脱离浏览器独立运行得高潮点,那么现在就来测试下node 与swoole 的真实情况 首先swoole 有众多框架,基本都大同小异,这里就选一个比较简...

福嘞娃
05/18
0
0
MySQL数据库PDO教程

翻译烂到家了,看不顺眼轻喷。。。 1.为什么要使用PDO?         mysql*函数已经过时,相当一段时间以来,mysql函数在其他SQL数据库编程接口方面已经有所差别;它不支持预处理,存储...

旺仔的小馒头
2016/07/01
27
0

没有更多内容

加载失败,请刷新页面

加载更多

[Python进阶] Python命令行参数

Python 获得命令行参数的方法 需要模块:sys 参数个数:len(sys.argv) 脚本名: sys.argv[0] 参数1: sys.argv[1] 参数2: sys.argv[2] 解析命令行参数 Python提供了一个getopt模块,可用于解...

Eappo_Geng
10分钟前
0
0
add docker api url to jenkins

add docker api url to jenkins add jenkins to dockergroup gpasswd -a $USER docker gpasswd -a jenkins docker https://stackoverflow.com/questions/37178824/how-do-i-find-the-docker-......

kewei_zhang
14分钟前
0
0
Scala入门篇

1、定义变量 var 可变 val 不可变,相当于Java中的final Unit相当于Java中的void,以()表示 scala> val a = println("ddd") ddd a: Unit = () 2,声明数组 scala> val arr = Array(1,2,3,4,5) ......

算法之名
15分钟前
6
0
利用redis统计分布式集群中接口缓存命中情况

接口使用了缓存,想看看缓存命中率,到底提升了多少了?固想到做个统计方法,单机情况下使用 AtomicImteger,考虑到分布式集群中多台服务器调用,所以考虑使用redis进行统计 原来的想法很简单用分布...

计算机的小二青年
15分钟前
0
0
前端加密JS库--CryptoJS 使用指南

有时候项目涉及到的敏感数据比较多,为了信息安全,我们常常需要对一些数据进行接口加密处理,如编码、将明文转化为暗文、加密比对、AES + BASE64 算法加密等。 Base64 编码 为什么要编...

舒龙虎
17分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部