最近发现一个在线问题,一个表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)




