首页
下载
文档
社区
视频
捐赠
源代码
赞助商
AOT 编译器
AI 助理
商业产品
PHP AOT 原生编译器
Swoole-Compiler 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
发表新帖
onClose回调中连接池MysqliProxy查询结果与预期不符且抛出MysqliException异常
### Swoole 4.6.6,php 8.0.3,Ubuntu 20.04 为了与现有框架融合,我对单个连接做了简易封装,出错涉及部分如下 ```php class Mysqli implements ConnectionInterface{ private $mysqli; // \Mysqli或其代理的实例 private \Swoole\Database\MysqliPool $pool; public function __construct($mysqli=NULL,\Swoole\Database\MysqliPool &$pool=NULL) { $this->mysqli=$mysqli; if(!is_null($pool)){ $this->pool=$pool; echo '借到连接了',PHP_EOL; } } public function select(string $sql,array $where=[],string $fetch='object') { $sql='SELECT '.$sql.self::splicingWhere($where,$types,$params); try { $stmt=$this->mysqli->prepare($sql); if(!empty($types)) $stmt->bind_param($types,...$params); $stmt->execute(); $res=$stmt->get_result(); $stmt->close(); } catch (\Throwable $th) { echo date('Y-m-d H:i:s'),substr(explode(' ',microtime())[0],1),':结果异常',$sql,PHP_EOL,$th->getCode(),':',$th->getMessage(),PHP_EOL; var_dump($types,$params); return $fetch=='all'?[]:NULL; } if(is_bool($res)){ echo date('Y-m-d H:i:s'),substr(explode(' ',microtime())[0],1),':布尔结果',$sql,PHP_EOL; var_dump($res,$types,$params); return $fetch=='all'?[]:NULL; } $props=[ 'count'=>'field_count', //总列数 'rows'=>'num_rows', //总行数 'len'=>'lengths', //当前行每一列的内容长度 ]; if(isset($props[$fetch])) $ret=$res->{$props[$fetch]}; elseif($fetch==='all'){ $ret=[]; for($i=0;$i<$res->num_rows;$i++){ if($i>0) $res->data_seek($i); $ret[]=$res->fetch_object(); } }elseif(in_array($fetch,['array','object'])) $ret=$res->{'fetch_'.$fetch}(); else throw new \ForbiddenException('数据查询结果解析出错!'); $res->free(); return $ret; } private static function checkType($v) { if(is_integer($v)) return 'i'; if(is_numeric($v)) return 'd'; if(is_bool($v)) return 'b'; return 's'; } //拼接where语句 private static function splicingWhere(array $where,?string &$types,?array &$params) { if(!isset($types,$params)){ $types=''; $params=[]; } $fields=[]; foreach($where AS $k => $v){ if(strpos($k,'`')===FALSE) $k='`'.$k.'`'; $fields[]=$k.' = ?'; $params[]=$v; $types.=self::checkType($v); } return ' '.implode(' AND ',$fields); } public function __destruct() { if(isset($this->pool)){ echo '连接已归还',PHP_EOL; $this->pool->put($this->mysqli); } } } ``` 在onClose事件中,需要更新用户连接的结束时间,删除fd相关数据等操作,出错部分如下 ```php $server->on('close',function($ws,int $fd)use(&$DBPool){ echo date('Y-m-d H:i:s'),substr(explode(' ',microtime())[0],1),':',$fd,'断开了',PHP_EOL; $db=new \App\DB\Mysqli($DBPool->get(),$DBPool); $connect=$db->select('`log_id` FROM `'.TB_SOCKET_ONLINE.'` WHERE',['fd'=>$fd]); $db->delete(TB_SOCKET_ONLINE,['fd'=>$fd]); if(is_null($connect)) return; ...... }); ``` ### 变量$connect赋值的这一行偶发性出错 因此如第一段代码所示,我在查询的位置做了try/catch,发现出错时总是先发生$stmt->get_result()返回布尔值false,紧接着下一次查询就会抛出MysqliException,位置是MysqliProxy.php的第61行,我从git上下载了源文件,找到对应方法如下 ```php public function __call(string $name, array $arguments) { for ($n = 3; $n--;) { $ret = @$this->__object->{$name}(...$arguments); if ($ret === false) { /* non-IO method */ if (!preg_match(static::IO_METHOD_REGEX, $name)) { break; } /* no more chances or non-IO failures */ if (!in_array($this->__object->errno, static::IO_ERRORS, true) || ($n === 0)) { throw new MysqliException($this->__object->error, $this->__object->errno); //这个位置抛出的异常 } $this->reconnect(); continue; } if (strcasecmp($name, 'prepare') === 0) { $ret = new MysqliStatementProxy($ret, $arguments[0], $this); } elseif (strcasecmp($name, 'stmt_init') === 0) { $ret = new MysqliStatementProxy($ret, null, $this); } break; } /* @noinspection PhpUndefinedVariableInspection */ return $ret; } ``` 使人困惑的是,抛出的$this->__object->error是0,$this->__object->errno是空 日志内容如下: ```php 2021-04-27 11:07:12.06089200:548断开了 2021-04-27 11:07:12.06105500:504断开了 2021-04-27 11:07:12.06132400:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(548) } 2021-04-27 11:07:12.06169200:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(504) } ``` 另外,封装的这个select方法在onOpen、onMessage、onRequest方法中都有应用,而且从没有出错过,就是在onClose方法中,具体概率还不确定,一般前500次以内就会出现。 ### 所以,请问这是我逻辑涉及的问题,还是触发了某个底层的bug?
发布于5年前 · 7 次浏览 · 来自
提问
White
### Swoole 4.6.6,php 8.0.3,Ubuntu 20.04 为了与现有框架融合,我对单个连接做了简易封装,出错涉及部分如下 ```php class Mysqli implements ConnectionInterface{ private $mysqli; // \Mysqli或其代理的实例 private \Swoole\Database\MysqliPool $pool; public function __construct($mysqli=NULL,\Swoole\Database\MysqliPool &$pool=NULL) { $this->mysqli=$mysqli; if(!is_null($pool)){ $this->pool=$pool; echo '借到连接了',PHP_EOL; } } public function select(string $sql,array $where=[],string $fetch='object') { $sql='SELECT '.$sql.self::splicingWhere($where,$types,$params); try { $stmt=$this->mysqli->prepare($sql); if(!empty($types)) $stmt->bind_param($types,...$params); $stmt->execute(); $res=$stmt->get_result(); $stmt->close(); } catch (\Throwable $th) { echo date('Y-m-d H:i:s'),substr(explode(' ',microtime())[0],1),':结果异常',$sql,PHP_EOL,$th->getCode(),':',$th->getMessage(),PHP_EOL; var_dump($types,$params); return $fetch=='all'?[]:NULL; } if(is_bool($res)){ echo date('Y-m-d H:i:s'),substr(explode(' ',microtime())[0],1),':布尔结果',$sql,PHP_EOL; var_dump($res,$types,$params); return $fetch=='all'?[]:NULL; } $props=[ 'count'=>'field_count', //总列数 'rows'=>'num_rows', //总行数 'len'=>'lengths', //当前行每一列的内容长度 ]; if(isset($props[$fetch])) $ret=$res->{$props[$fetch]}; elseif($fetch==='all'){ $ret=[]; for($i=0;$i<$res->num_rows;$i++){ if($i>0) $res->data_seek($i); $ret[]=$res->fetch_object(); } }elseif(in_array($fetch,['array','object'])) $ret=$res->{'fetch_'.$fetch}(); else throw new \ForbiddenException('数据查询结果解析出错!'); $res->free(); return $ret; } private static function checkType($v) { if(is_integer($v)) return 'i'; if(is_numeric($v)) return 'd'; if(is_bool($v)) return 'b'; return 's'; } //拼接where语句 private static function splicingWhere(array $where,?string &$types,?array &$params) { if(!isset($types,$params)){ $types=''; $params=[]; } $fields=[]; foreach($where AS $k => $v){ if(strpos($k,'`')===FALSE) $k='`'.$k.'`'; $fields[]=$k.' = ?'; $params[]=$v; $types.=self::checkType($v); } return ' '.implode(' AND ',$fields); } public function __destruct() { if(isset($this->pool)){ echo '连接已归还',PHP_EOL; $this->pool->put($this->mysqli); } } } ``` 在onClose事件中,需要更新用户连接的结束时间,删除fd相关数据等操作,出错部分如下 ```php $server->on('close',function($ws,int $fd)use(&$DBPool){ echo date('Y-m-d H:i:s'),substr(explode(' ',microtime())[0],1),':',$fd,'断开了',PHP_EOL; $db=new \App\DB\Mysqli($DBPool->get(),$DBPool); $connect=$db->select('`log_id` FROM `'.TB_SOCKET_ONLINE.'` WHERE',['fd'=>$fd]); $db->delete(TB_SOCKET_ONLINE,['fd'=>$fd]); if(is_null($connect)) return; ...... }); ``` ### 变量$connect赋值的这一行偶发性出错 因此如第一段代码所示,我在查询的位置做了try/catch,发现出错时总是先发生$stmt->get_result()返回布尔值false,紧接着下一次查询就会抛出MysqliException,位置是MysqliProxy.php的第61行,我从git上下载了源文件,找到对应方法如下 ```php public function __call(string $name, array $arguments) { for ($n = 3; $n--;) { $ret = @$this->__object->{$name}(...$arguments); if ($ret === false) { /* non-IO method */ if (!preg_match(static::IO_METHOD_REGEX, $name)) { break; } /* no more chances or non-IO failures */ if (!in_array($this->__object->errno, static::IO_ERRORS, true) || ($n === 0)) { throw new MysqliException($this->__object->error, $this->__object->errno); //这个位置抛出的异常 } $this->reconnect(); continue; } if (strcasecmp($name, 'prepare') === 0) { $ret = new MysqliStatementProxy($ret, $arguments[0], $this); } elseif (strcasecmp($name, 'stmt_init') === 0) { $ret = new MysqliStatementProxy($ret, null, $this); } break; } /* @noinspection PhpUndefinedVariableInspection */ return $ret; } ``` 使人困惑的是,抛出的$this->__object->error是0,$this->__object->errno是空 日志内容如下: ```php 2021-04-27 11:07:12.06089200:548断开了 2021-04-27 11:07:12.06105500:504断开了 2021-04-27 11:07:12.06132400:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(548) } 2021-04-27 11:07:12.06169200:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(504) } ``` 另外,封装的这个select方法在onOpen、onMessage、onRequest方法中都有应用,而且从没有出错过,就是在onClose方法中,具体概率还不确定,一般前500次以内就会出现。 ### 所以,请问这是我逻辑涉及的问题,还是触发了某个底层的bug?
赞
0
收藏
提问
分享
讨论
建议
公告
开发框架
CodeGalaxy
登录
后参与评论
评论
2021-04-27
White
补充,代码启动的开头部分如下: ```php $DBPool=NULL; \Swoole\Coroutine\run(function()use(&$DBPool){ $DBPool=new \Swoole\Database\MysqliPool((new \Swoole\Database\MysqliConfig) ->withHost('127.0.0.1')->withPort(3306) ->withDbName('库名') ->withCharset('utf8mb4') //???未生效?? ->withUsername('账号')->withPassword('密码') ,DB_POOL_SIZE); \Swoole\Coroutine::create(function()use($DBPool){ for($i=0;$i<DB_POOL_SIZE;$i++) $list[]=$DBPool->get(); $list[0]->real_query('DELETE FROM `'.TB_SOCKET_ONLINE.'`'); foreach($list AS $db){ $db->set_charset('utf8mb4'); $DBPool->put($db); } }); }); ``` ###迷惑的是,初始化的时候withCharset('utf8mb4')竟然不生效??导致存入表的一个utf8mb4_general_ci的字段中汉字全部乱码 并且日志中偶尔还会出现[2021-04-27 11:57:05 #149994.1] WARNING wait(:222): EPOLLIN handle failed. fd=xx, Error: Resource temporarily unavailable[11]
赞
0
回复
2021-04-27
鲁飞
回复
White
发github或者gist
赞
0
回复
2021-04-27
鲁飞
直接提供能复现问题的打包代码吧
赞
0
回复
2021-04-27
White
回复
鲁飞
https://gist.github.com/warmbook/30308bb54a808225d8e396fac8fef63e ###以上代码刚刚试过,浏览器同时开了12个标签连服务器,跑了39秒开始异常 ```php 2021-04-27 13:17:14.02235000:1连上了 借到连接了 连接已归还 2021-04-27 13:17:14.17065200:2连上了 借到连接了 连接已归还 2021-04-27 13:17:14.25505100:3连上了 借到连接了 连接已归还 2021-04-27 13:17:14.31725800:4连上了 借到连接了 连接已归还 2021-04-27 13:17:14.37752300:5连上了 借到连接了 连接已归还 2021-04-27 13:17:14.45636000:6连上了 借到连接了 连接已归还 2021-04-27 13:17:14.52829300:7连上了 借到连接了 连接已归还 2021-04-27 13:17:14.60347500:8连上了 借到连接了 连接已归还 2021-04-27 13:17:14.67017200:9连上了 借到连接了 连接已归还 2021-04-27 13:17:14.76496100:10连上了 借到连接了 连接已归还 2021-04-27 13:17:14.82076500:11连上了 借到连接了 连接已归还 2021-04-27 13:17:14.88020800:12连上了 借到连接了 连接已归还 2021-04-27 13:17:53.67400500:1断开了 借到连接了 2021-04-27 13:17:53.67428600:5断开了 借到连接了 2021-04-27 13:17:53.67440100:9断开了 借到连接了 2021-04-27 13:17:53.67461500:2断开了 借到连接了 2021-04-27 13:17:53.67478000:6断开了 借到连接了 2021-04-27 13:17:53.67551200:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(2) } 2021-04-27 13:17:53.67641600:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(1) } 2021-04-27 13:17:53.67715500:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(6) } 连接已归还 PHP Warning: Wrong COM_STMT_PREPARE response size. Received 1 in @swoole-src/library/core/Database/MysqliProxy.php on line 53 2021-04-27 13:17:53.67778800:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(5) } 2021-04-27 13:17:53.67911100:10断开了 借到连接了 2021-04-27 13:17:53.67972700:3断开了 借到连接了 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 2021-04-27 13:17:53.68155200:7断开了 借到连接了 连接已归还 2021-04-27 13:17:53.68206900:11断开了 借到连接了 2021-04-27 13:17:53.68269000:4断开了 借到连接了 连接已归还 连接已归还 连接已归还 2021-04-27 13:17:53.68433000:8断开了 借到连接了 2021-04-27 13:17:53.68520700:12断开了 借到连接了 连接已归还 连接已归还 2021-04-27 13:17:53.74811100:13连上了 借到连接了 连接已归还 2021-04-27 13:17:53.82346000:14连上了 借到连接了 连接已归还 2021-04-27 13:17:53.88949700:15连上了 借到连接了 连接已归还 2021-04-27 13:17:53.93651000:16连上了 借到连接了 连接已归还 2021-04-27 13:17:53.99023700:17连上了 借到连接了 连接已归还 2021-04-27 13:17:54.05569400:18连上了 借到连接了 连接已归还 2021-04-27 13:17:54.10877000:19连上了 借到连接了 连接已归还 2021-04-27 13:17:54.16110100:20连上了 借到连接了 连接已归还 2021-04-27 13:17:54.21979300:21连上了 借到连接了 连接已归还 2021-04-27 13:17:54.28680700:22连上了 借到连接了 连接已归还 2021-04-27 13:17:54.34853500:23连上了 借到连接了 连接已归还 2021-04-27 13:17:54.40115700:24连上了 借到连接了 连接已归还 2021-04-27 13:18:33.67441800:13断开了 借到连接了 2021-04-27 13:18:33.67465300:17断开了 借到连接了 2021-04-27 13:18:33.67474800:21断开了 借到连接了 2021-04-27 13:18:33.67495000:14断开了 借到连接了 2021-04-27 13:18:33.67508500:18断开了 借到连接了 2021-04-27 13:18:33.67517700:22断开了 借到连接了 2021-04-27 13:18:33.67572500:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(14) } 2021-04-27 13:18:33.67594800:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(18) } 2021-04-27 13:18:33.67684900:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(13) } PHP Warning: Wrong COM_STMT_PREPARE response size. Received 1 in @swoole-src/library/core/Database/MysqliProxy.php on line 53 2021-04-27 13:18:33.67704800:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(17) } PHP Warning: Wrong COM_STMT_PREPARE response size. Received 1 in @swoole-src/library/core/Database/MysqliProxy.php on line 53 2021-04-27 13:18:33.67716500:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(21) } 2021-04-27 13:18:33.67734300:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(22) } 2021-04-27 13:18:33.67849300:15断开了 借到连接了 2021-04-27 13:18:33.67872700:19断开了 借到连接了 2021-04-27 13:18:33.67934400:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(19) } 2021-04-27 13:18:33.68064300:23断开了 借到连接了 2021-04-27 13:18:33.68074400:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(23) } 2021-04-27 13:18:33.68169600:16断开了 借到连接了 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 2021-04-27 13:18:33.68287700:20断开了 借到连接了 2021-04-27 13:18:33.68305800:24断开了 借到连接了 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 2021-04-27 13:18:33.74659100:25连上了 借到连接了 连接已归还 2021-04-27 13:18:33.81211200:26连上了 借到连接了 连接已归还 2021-04-27 13:18:33.87916800:27连上了 借到连接了 连接已归还 2021-04-27 13:18:33.93391300:28连上了 借到连接了 连接已归还 2021-04-27 13:18:33.97904500:29连上了 借到连接了 连接已归还 2021-04-27 13:18:34.03062500:30连上了 借到连接了 连接已归还 2021-04-27 13:18:34.09232900:31连上了 借到连接了 连接已归还 2021-04-27 13:18:34.14209700:32连上了 借到连接了 连接已归还 2021-04-27 13:18:34.19879800:33连上了 借到连接了 连接已归还 2021-04-27 13:18:34.28097600:34连上了 借到连接了 连接已归还 2021-04-27 13:18:34.34621300:35连上了 借到连接了 连接已归还 2021-04-27 13:18:34.40156500:36连上了 借到连接了 连接已归还 2021-04-27 13:19:13.67478000:25断开了 借到连接了 2021-04-27 13:19:13.67500500:29断开了 借到连接了 2021-04-27 13:19:13.67510100:33断开了 借到连接了 2021-04-27 13:19:13.67529000:26断开了 借到连接了 2021-04-27 13:19:13.67540900:30断开了 借到连接了 2021-04-27 13:19:13.67550100:34断开了 借到连接了 2021-04-27 13:19:13.67609600:27断开了 借到连接了 2021-04-27 13:19:13.67632100:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(30) } 2021-04-27 13:19:13.67722300:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(29) } 2021-04-27 13:19:13.67748500:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(33) } 2021-04-27 13:19:13.67834900:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(34) } 2021-04-27 13:19:13.67867200:31断开了 借到连接了 2021-04-27 13:19:13.67949100:布尔结果SELECT `log_id` FROM `socket_online` WHERE `fd` = ? bool(false) string(1) "i" array(1) { [0]=> &int(31) } 2021-04-27 13:19:13.67990700:35断开了 借到连接了 连接已归还 连接已归还 2021-04-27 13:19:13.68145800:28断开了 借到连接了 2021-04-27 13:19:13.68154600:32断开了 借到连接了 2021-04-27 13:19:13.68173100:36断开了 借到连接了 2021-04-27 13:19:13.68297200:结果异常SELECT `log_id` FROM `socket_online` WHERE `fd` = ? 0: string(1) "i" array(1) { [0]=> int(28) } 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 连接已归还 2021-04-27 13:19:13.76246900:37连上了 借到连接了 连接已归还 2021-04-27 13:19:13.81702400:38连上了 借到连接了 连接已归还 2021-04-27 13:19:13.87938900:39连上了 借到连接了 连接已归还 2021-04-27 13:19:13.96158700:40连上了 借到连接了 连接已归还 2021-04-27 13:19:14.02427300:41连上了 借到连接了 连接已归还 2021-04-27 13:19:14.08349000:42连上了 借到连接了 连接已归还 2021-04-27 13:19:14.14476300:43连上了 借到连接了 连接已归还 2021-04-27 13:19:14.20217700:44连上了 借到连接了 连接已归还 2021-04-27 13:19:14.25786600:45连上了 借到连接了 连接已归还 2021-04-27 13:19:14.31878300:46连上了 借到连接了 连接已归还 2021-04-27 13:19:14.37313100:47连上了 借到连接了 连接已归还 2021-04-27 13:19:14.43209300:48连上了 借到连接了 连接已归还 ``` ###麻烦了/(ㄒoㄒ)/~~
赞
0
回复
2021-04-28
White
回复
鲁飞
请问大佬,是我代码的问题吗?
赞
0
回复
2021-04-30
鲁飞
回复
White
抛出异常以后还把连接放回去了
赞
0
回复
2021-04-30
White
回复
鲁飞
这个不妥的话可以放个NULL回去,问题是异常是什么原因导致的呢?
赞
0
回复
2021-07-07
White
回复
鲁飞
你好,我把抛出异常放回连接的做法改了,增加了一个属性用来判断是否发生了异常,如果发生异常则放回NULL。  然并卵,遍历并pin连接池的时候依然会抛出异常: Uncaught Swoole\Database\MysqliException: Malformed packet in @swoole-src/library/core/Database/MysqliProxy.php:61
赞
0
回复