暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

php pdo+proxysql预处理执行SQL时异常分析

ITOpers 2021-06-28
1567

最近发现一个在线问题,一个表DDL 添加一个字段,添加字段完成后,php程序异常报错,具体错误信息如下:

Packets out of order. Expected 1 received 13. Packet size=5

后面所有数据都为线下模拟数据。
 表结构如下:

 CREATE TABLE `sbtest1` (
`id` int(10) unsigned NOT NULL,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`d` varchar(500) COLLATE utf8mb4_bin NOT NULL DEFAULT 'a',
PRIMARY KEY (`id`),
KEY `idx_d` (`d`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

报错一段时间后就恢复了,异常期间有上线操作:添加了字段。并在proxysql的日志中也能看到异常日志:

2020-05-20 15:59:41 MySQL_Session.cpp:2887:handler(): [ERROR] Detected a broken connection during query on (1,10.200.131.4,3306) , FD (Conn:43 , MyDS:43) : 2057, The number of parameters in bound buffers differs from number of columns in resultset
2020-05-20 15:59:43 MySQL_PreparedStatement.cpp:315:update_metadata(): [WARNING] Updating metadata for stmt 1 , user sbtest_rw, query SELECT * FROM sbtest1 WHERE id =?
2020-05-20 15:59:43 MySQL_Session.cpp:2452:handler(): [ERROR] RECEIVED AN UNKNOWN COMMAND: 28 -- PLEASE REPORT A BUG

初步整理信息:

  • 异常时间点对sbtest1做了ddl,增加了一个字段。SQL为alter table sbtest1 add column daa varchar(2) not null default 'd' after k;

  • php执行SQL采用的是预处理

  • proxysql提示有明显报错,有可能是proxysql的bug

  • php使用的pdo访问MySQL

如下是问题复现过程:

  • 首先,确认的是不是proxysql的bug,在issue中并没有找到具体信息,虽然也有人提过issue。于是尝试复现。

通过proxysql执行如下SQL:

sbtest_rw 15:13:  [sbtest]> PREPARE stmt1 FROM 'select * from sbtest1 where id=?';
Query OK, 0 rows affected (0.02 sec)
Statement prepared

sbtest_rw 15:13: [sbtest]> set @a=30;
Query OK, 0 rows affected (0.02 sec)

sbtest_rw 15:13: [sbtest]> EXECUTE stmt1 USING @a;

另一个session同步修改表结构:alter table sbtest1 add column daa varchar(2) not null default 'd' after k;
 再执行时并没有异常:

sbtest_rw 15:13:  [sbtest]> EXECUTE stmt1 USING @a;

通过直接在proxysql手动执行prepare,无法复现,表示可能不是proxysql的问题。还有一种可能是pdo的版本bug。继续复现,准备php以及pdo环境。并编写如下脚本:

$  cat prepare.php
<?php
$dsn = 'mysql:dbname=sbtest;host=10.200.131.3;port=6033';
$user = 'sbtest_rw';
$password = 'sbtest_rw_123';

//链接数据库
try {
$params = [
PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8", //设置编码
PDO::ATTR_EMULATE_PREPARES => false, //使用预处理
];
$db = new PDO($dsn, $user, $password, $params);

} catch (PDOException $e) {
echo $e->getMessage();
exit;
}

//查询多条数据
$sth = $db->prepare('SELECT * FROM sbtest1 WHERE id =?');
$sth->execute([28]);
$res1 = $sth->fetchAll(PDO::FETCH_ASSOC); //有多种方式可以设置返回的值
print_r($res1);
?>

这次果然完整复现了问题,如下是复现步骤:1、先执行脚本:php prepare.php

Array
(
[0] => Array
(
[id] => 28
[k] => 4971
[daa] => d
[da] => d
[dc] => d
[ddd] => d
[dd] => dd
[d] => d
[a] => a
[c] => 40783119738-56118100649-59405854784-01609403334-31427251764-63910168355- 67527844925-29768547496-01835772584-19294175703
[pad] => 24254594429-63006752771-86115602773-26630920715-27676864358
[b] => b
[dk] => d
)

)

2、执行加字段

alter table sbtest1 add column daa varchar(2) not null default 'd' after k;

3、再执行php prepare.php

PHP Warning:  PDOStatement::execute(): MySQL server has gone away in data/dbadir/20200520/prepare.php on line 21
PHP Warning: PDOStatement::execute(): Error reading result set's header in
/data/dbadir/20200520/prepare.php on line 21
Array
(
)

4、继续执行php prepare.php

PHP Warning:  Packets out of order. Expected 1 received 13. Packet size=5 in 
/data/dbadir/20200520/prepare.php on line 22
Array
(
)

后面继续执行就恢复正常了。

复现了问题了就好了,说明是通过pdo才能出现,很明显,这里pdo的ATTR_EMULATE_PREPARES
配置默认设置成了false
。改成true后,测试如上步骤就没有报错了。到这里可以得出,由于pdo的预处理关闭了,导致proxysql处理这种情况时会异常。

总结+扩展测试结论:(如下测试都是在有增加字段的情况下)

  • ATTREMULATEPREPARES为false,通过proxysql请求异常
    (在proxysql中提了个issue确认下这个场景的问题,https://github.com/sysown/proxysql/issues/2812)

  • ATTREMULATEPREPARES为true,通过proxysql请求正常

  • ATTREMULATEPREPARES为false/true直连MySQL正常

  • select * 改为指定列,通过proxysql访问测试正常(ATTREMULATEPREPARES为true和false都正常

后续优化:

  • 避免使用select *,程序中需要什么列必须明确出来。

  • pdo的ATTREMULATEPREPARES配置设置为true(需要测试,在网上有人提到查询结果会把整型转成字符串)

补充:

  • PDO::ATTREMULATEPREPARES 启用或禁用预处理语句的模拟。有些驱动不支持或有限度地支持本地预处理。使用此设置强制PDO总是模拟预处理语句(如果为 TRUE ),或试着使用本地预处理语句(如果为 FALSE)



文章转载自ITOpers,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论