设为首页收藏本站

LUPA开源社区

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

PHP与MySQL通讯那点事

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

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

在我们的一款WebGame的生产环境中,一次无意的strace抓包时,发现了php与mysql大量通讯的数据。这种情况,在游戏服务器刚启动时,是正常的,但如果是运行一段时间之后,出现大量SELECT的SQL查询,绝对是有问题的,而且,所操作的数据库并不是配置库,那意味着,我们程序员的程序出现了违规的操作。具体结果大约如下:

如上图所示,php持续接收读取进程内描述符为3的响应包数据,描述符为3的为php与mysql建立的TCP通讯链接,这点也可以从313行的SELECT语句来确认。(原始数据丢失了,我模仿了一条。所以是配置库的SQL语句)

这是什么程序,想实现什么逻辑?为何要取这么多数据?

跟着这里的SELECT的sql语句,我定位到了相应的程序段:

  1. /*  
  2. **  业务逻辑的代码  
  3. */  
  4. public function SItem($roleId,$baseId) {  
  5.     //...  
  6.     // ############写出下面这种代码的人都得死.##################  
  7.     $this->dbrRole->select('*');  
  8.     $this->dbrRole->from('role_items');  
  9.     $this->dbrRole->where('role_id',$roleId);  
  10.     $this->dbrRole->where('baseId',$baseId);  
  11.     $result = $this->dbrRole->get()->row(); //看上去,这里好像正常,我们都以为框架会给我们只取一条。  
  12.     //...  

我们从代码上来看,好像明白程序员想根据对应的role_id到role_items表里取一条想符合的数据,所以,他调用了row方法,来取一条。看上去,这里好像正常,我们都以为框架会给我们只取一条。但实际上,框架是如何处理的呢?

我们来看下框架的对应row方法的实现过程。对了,我们是CodeIgniter框架的一个较老的版本。

  1. /*  
  2. **  框架中,DB drive中,row相关方法的代码  
  3. **  
  4. */ 
  5. public function row($n = 0,$type = 'array'){  
  6.     if(!is_numeric($n)){  
  7.         if(! is_array($this->_rowData)){  
  8.             $this->_rowData = $this->rowArray(0);  
  9.         }  
  10.         if(isset($this->_rowData[$n])){  
  11.             return $this->_rowData[$n];  
  12.         }  
  13.         $n = 0;  
  14.     }  
  15.     return ($type == 'object') ? $this->rowObject($n) : $this->rowArray($n);  
  16. }  
  17.  
  18. //继续跟进rowArray方法  
  19. public function rowArray($n = 0){  
  20.     $result = $this->resultArray();  
  21.     if(count($result) == 0){  
  22.         return $result;  
  23.     }  
  24.  
  25.     if($n != $this->_current && isset($result[$n])){  
  26.         $this->_current = $n;  
  27.     }  
  28.  
  29.     return $result[$this->_current];  
  30. }  
  31.  
  32. //继续跟进resultArray方法 ###这个方法是重点###  
  33. public function resultArray(){  
  34.     if(count($this->resultArray) > 0){  
  35.         return $this->resultArray;  
  36.     }  
  37.  
  38.     if(false === $this->resulter || 0 == $this->recordCount()){  
  39.         return array();  
  40.     }  
  41.  
  42.     $this->_dataSeek(0);  
  43.     while($row = $this->_fetchAssoc()){  
  44.         $this->resultArray[] = $row;    //###########这个数组每次都增加_fetchAssoc()结果的内存大小数量#########################  
  45.     }  
  46.     return $this->resultArray;  
  47. }  
  48.  
  49. //继续跟进_fetchAssoc方法  
  50. /*  
  51. ** 对应driver的_fetchAssoc方法的代码  
  52. */ 
  53. protected function _fetchAssoc(){  
  54.     return mysql_fetch_assoc($this->resulter);  
  55. }  

我们可以看到CodeIgniter框架的resultArray方法使用mysql(我们的php调用mysql的api用的是mysql函数,有点绕,后面解释)的mysql_fetch_assoc函数对缓冲区的数据进行遍历转换。将所有缓冲区的数据全部复制给$this->resultArray属性,再判断row方法中所需要的key的结果是否存在,再与返回的。

也就是说,框架层并没有只从mysql server(潜意识上的mysql server)那边取一条给我们调用者,而是取了所有结果,再返回一条。(先别喷,后面解释) 当然,CI这种做法,也不是错。但我觉得有更好的改进方法。

这个问题,我们组的dietoad 发现了这个问题,并给了修复方案。有些同学认为,这是程序员的错,程序员的SELECT语句没有加limit来限制条数。这我绝对赞同,而且,觉得写出这种代码的人都得死。

  1. 业务层:为这种业务需求的SQL语句加上limit限制
  2. 框架层:框架对于这种需求,自动控制,发现这种情况,直接返回1条

对于解决方案1,我写了一个正则,匹配select()方法被调用之后,row()方法被调用之前,中间没有使用limit()方法的所有代码,结果,发现量并不小。后来,我们决定两种方案同时实施,防止第二种出现漏掉的情况。


酷毙

雷人
2

鲜花
1

鸡蛋

漂亮

刚表态过的朋友 (3 人)

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

最新评论

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

返回顶部