设为首页 加入收藏

TOP

php与mysql通讯那点事(一)
2014-11-24 07:55:32 来源: 作者: 【 】 浏览:4
Tags:php mysql 通讯 那点事
BUG、BUG,似乎末学的学习历程都是因为BUG引起的:

在我们的一款WebGame的生产环境中,一次无意的strace抓包时,发现了php与mysql大量通讯的数据。这种情况,在游戏服务器刚启动时,是正常的,但如果是运行一段时间之后,出现大量SELECT的SQL查询,绝对是有问题的,而且,所操作的数据库并不是配置库,那意味着,我们程序员的程序出现了违规的操作。具体结果大约如下:strace跟踪php进程与mysql通讯的日志
如上图所示,php持续接收读取进程内描述符为3的响应包数据,描述符为3的为php与mysql建立的TCP通讯链接,这点也可以从313行的SELECT语句来确认。(原始数据丢失了,我模仿了一条。所以是配置库的SQL语句)

这是什么程序,想实现什么逻辑?为何要取这么多数据?
跟着这里的SELECT的sql语句,我定位到了相应的程序段:


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

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

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


/*
**  框架中,DB drive中,row相关方法的代码
**
*/
public function row($n = 0,$type = 'array'){
    if(!is_numeric($n)){
        if(! is_array($this->_rowData)){
            $this->_rowData = $this->rowArray(0);
        }
        if(isset($this->_rowData[$n])){
            return $this->_rowData[$n];
        }
        $n = 0;
    }
    return ($type == 'object')   $this->rowObject($n) : $this->rowArray($n);
}

//继续跟进rowArray方法
public function rowArray($n = 0){
    $result = $this->resultArray();
    if(count($result) == 0){
        return $result;
    }

    if($n != $this->_current && isset($result[$n])){
        $this->_current = $n;
    }

    return $result[$this->_current];
}

//继续跟进resultArray方法 ###这个方法是重点###
public function resultArray(){
    if(count($this->resultArray) > 0){
        return $this->resultArray;
    }

    if(false === $this->resulter || 0 == $this->recordCount()){
        return array();
    }

    $this->_dataSeek(0);
    while($row = $this->_fetchAssoc()){
        $this->resultArray[] = $row;    //###########这个数组每次都增加_fetchAssoc()结果的内存大小数量#########################
    }
    return $this->resultArray;
}

//继续跟进_fetchAssoc方法
/*
** 对应driver的_fetchAssoc方法的代码
*/
protected function _fetchAssoc(){
    return mysql_fetch_assoc($this->resulter);
}

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

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

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

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

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

dietoad给出如下改进:


/*
**  //改进为当_rowData不存在时,从_rowData的数量开始取,取小于$n条记录,避免 上面 resultArray方法中从缓冲区取所有数据,复制双倍数据,占用内存的情况
*/
public function row ($n = 0, $type = 'array')
{
    if(isset($this->_rowData[$n]))
    {
        return $this->_rowData[$n];
    }
    if (! is_numeric($n))
    {
        return $this->rowObject($n);
    }

    $ln=count($this->_rowData);
    //继续上次位置
    while($ln++<=$n&&$r=$this->_fetchAssoc())
    {
       $this->_rowData[]=$r;
    }
    //需要几条就读几条
    //防止记录集为空报warning
    return isset($this->_rowData[$n]) $this->_rowData
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Windows Server2008下MYSQL外网无.. 下一篇sqlite加载CSV文件

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·Python爬虫教程(从 (2025-12-26 16:49:14)
·【全269集】B站最详 (2025-12-26 16:49:11)
·Python爬虫详解:原 (2025-12-26 16:49:09)
·Spring Boot Java: (2025-12-26 16:20:19)
·Spring BootでHello (2025-12-26 16:20:15)