设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

PHP与MySQL通讯那点事

2013-4-28 14:09| 发布者: 红黑魂| 查看: 3870| 评论: 0|来自: 51CTO

摘要: 在我们的一款WebGame的生产环境中,一次无意的strace抓包时,发现了php与mysql大量通讯的数据。这种情况,在游戏服务器刚启动时,是正常的,但如果是运行一段时间之后,出现大量SELECT的SQL查询,绝对是有问题的,而 ...

dietoad给出如下改进:

  1. /*  
  2. **  //改进为当_rowData不存在时,从_rowData的数量开始取,取小于$n条记录,避免 上面 resultArray方法中从缓冲区取所有数据,复制双倍数据,占用内存的情况  
  3. */ 
  4. public function row ($n = 0, $type = 'array')  
  5. {  
  6.     if(isset($this->_rowData[$n]))  
  7.     {  
  8.         return $this->_rowData[$n];  
  9.     }  
  10.     if (! is_numeric($n))  
  11.     {  
  12.         return $this->rowObject($n);  
  13.     }  
  14.  
  15.     $ln=count($this->_rowData);  
  16.     //继续上次位置  
  17.     while($ln++<=$n&&$r=$this->_fetchAssoc())  
  18.     {  
  19.        $this->_rowData[]=$r;  
  20.     }  
  21.     //需要几条就读几条  
  22.     //防止记录集为空报warning  
  23.     return isset($this->_rowData[$n])?$this->_rowData[$n]:array();  
  24. }  

在今年的4月末,鄙人写过另一篇关于CodeIgniter框架的设计缺陷问题,给我们游戏项目带来较大的影响,后来提交到github issues,并没得到回复,想了想,虽然官方的2.1.3版本中,也存在这个小问题。不过我觉得,这就不提交了,或许,我们的做法也符合他们的设计初衷。不过,我们还是在我们的项目中改进了。

如此改进之后,我们使用php的memory_get_usage()函数观察前后两个row()方法的结果时,果然发现内存使用情况有较大改善(改善幅度取决于SELECT的返回数据量)。

似乎,到这里就应该结束了,问题就这么被发现,被解决了。

但,我总觉得少了些什么呢?当我再次strace抓包时,发现仍然存在大量的数据通讯,就像文章开头的那副截图一模一样。然而,这又是什么原因呢?

我顺手写了个内存占用的测试代码如下:

  1. $db = mysql_connect('192.168.xx.xx','xxxx','xxxx');  
  2. $sql = 'SELECT * from items';  
  3. mysql_select_db('jv01',$db);  
  4. echo 'SELECT_DB: ',convert(memory_get_usage()),"\n";     //619.26 kb  
  5.  
  6. $r = mysql_query($sql,$db);  
  7. echo 'QUERY_SQL: ',convert(memory_get_usage()),"\n";    //619.98 kb  ###什么?查询完之后,内存大小居然只增加了不到1k?我那个表可是几十M的数据啊  
  8.  
  9. //sleep(50);  // hold住进程,别销毁,留着看当前进程的内存分配1  
  10. $arr = array();  
  11. while ($rs = mysql_fetch_assoc($r))  
  12. {  
  13.     $arr[]=$rs;  
  14. }  
  15. echo 'FETCH_RS: ',convert(memory_get_usage()),"\n";    //27.11 mb  ###什么?刚刚不是只增加了1k吗?这里的遍历的结果集怎么突增几十M啊?尼玛这到底是什么情况?  
  16.  
  17. unset($arr);  
  18. echo 'UNSET: ',convert(memory_get_usage()),"\n";    //620.12 kb  #### $arr z占了 几十M  
  19.  
  20. mysql_free_result($r);  
  21. echo 'FREE_R: ',convert(memory_get_usage()),"\n";    //620 kb    ### 结果集居然只有0.12 k?这不扯淡么? 莫非。。。莫非缓冲区的数据php统计不到?莫非不是调用zend 内存申请函数来申请内存的?  
  22.  
  23.  
  24. //sleep(50);  // hold住进程,别销毁,留着看当前进程的内存分配2  
  25. function convert($size)  
  26. {  
  27.  $unit=array('b','kb','mb','gb','tb','pb');  
  28.  return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];  
  29. }  
  30. /*  
  31. //返回结果如下:  
  32. SELECT_DB: 619.26 kb  
  33. QUERY_SQL: 619.98 kb  
  34. FETCH_RS: 27.11 mb  
  35. UNSET: 620.12 kb  
  36. FREE_R: 620 kb  
  37. */ 

看到结果时,我不禁XX一紧,什么?这你妈什么情况?查询完之后,内存大小居然只增加了不到1k?我那个表可是几十M的数据啊?遍历结果集之后,怎么突增几十M啊?尼玛这到底是什么情况?strace返回的大量数据到底存在哪的?算不算php进程申请的?

后来,我再次执行如上程序,再定时用free、/proc/PID/maps 之类系统工具,查看系统的内存使用情况,确认了当前进程的内存占用确实存在。那么可能的情况就是memory_get_usage()函数并没有获取到 mysql_query之后的内存占用情况。由于比较怀疑,末学跟进了memory_get_usage()函数的源码,该函数直接交给 zend_memory_usage函数处理。

  1. //这个是php的memory_get_usage()函数的 相关代码,见Zend_alloc.c  line:2640  
  2. ZEND_API size_t zend_memory_usage(int real_usage TSRMLS_DC)  
  3. {  
  4.     if (real_usage) {  
  5.         return AG(mm_heap)->real_size;  
  6.     } else {  
  7.         size_t usage = AG(mm_heap)->size;  
  8. #if ZEND_MM_CACHE  
  9.         usage -= AG(mm_heap)->cached;  
  10. #endif 
  11.         return usage;  
  12.     }  
  13. }  
  14.  
  15.  
  16.  
  17.  
  18. //这个是Zend内存分配函数的代码  
  19. //Zend_alloc.c  line:2418  
  20. ZEND_API void *_emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)  
  21. {  
  22.     TSRMLS_FETCH();  
  23.  
  24.     if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {  
  25.         return AG(mm_heap)->_malloc(size);  
  26.     }  
  27.     return _zend_mm_alloc_int(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);  
  28. }  

php的内存管理 (中文地址:php-zend的内存管理中文版)这块,对于末学来说,太复杂了,只是稍微看懂直接 返回了mm_heap结构体的real_size/size的值。(两篇都是鸟哥写的,中文的地址也就是鸟哥博客最近一直打不开,抽风得厉害)

那mysql_query的结果集,存在哪的呢?如何申请内存的,莫非不是调用zend的_emalloc内存分配函数的?这得先明确mysql客户端类库问题,也就是我们使用哪个类库?libmysql还是mysqlnd,通过查看编译参数,发现(我的虚拟机)是libmysql,编译参数是这样的

  1. ./configure'  '--prefix=/services/php_5.3.19' '--with-config-file-path=/services/php_5.3.19/etc' '--with-pdo-mysql=/usr/bin/mysql_config' '--with-mysql=/usr/bin/mysql_config' '--with-mysqli=/usr/bin/mysql_config' '--enable-bcmath' '--enable-fpm  
  2.  
  3. //生产服务器如下:  
  4. ./configure'  '--prefix=/services/php' '--with-config-file-path=/services/php/etc' '--with-pdo-mysql=mysqlnd' '--with-mysql=mysqlnd' '--with-mysqli=mysqlnd' '--enable-bcmath' '--enable-fpm 

有点乱:

mysql、mysqli、pdo-mysql、libmysql、mysqlnd 好多名词,有点乱,没关系,一张图让你清晰起来:

mysql、mysqli、pdo-mysql、libmysql、mysqlnd之间关系

mysqlnd跟libmysql一样,都是直接与mysql server通讯的驱动类库。 而php程序员使用的mysql、mysqli、pdo-mysql是面向程序员调用的API接口。


酷毙

雷人
2

鲜花
1

鸡蛋

漂亮

刚表态过的朋友 (3 人)

  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部