最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

gdb排查PHP数据库长链接问题

XAMPP教程 中文小张 1280浏览 0评论

重构一个老的业务系统,有一个特殊的限制就是,有个资源必须在内网存储使用,但是数据库呢还必须放在外网,因为外网有业务与内网要共享数据,可能有人就会问了,为什么不做sql同步呢,这个还真没法做,内外网都需要操作数据。

所以呢就选了一个方案,程序部署内网,然后链接外网,这种方案也有很大风险,数据库暴露在外网,但是没有办法只能考虑怎么样做的更安全,除去传统的用户名密码,端口号变成随机的,我们还加了客户端证书的验证,算是加了一层保险。

但是有个问题就是这样一来,导致整个相应的就很慢,首先我们排查了sql的索引以及表结构设计的问题,进行了一番改造,响应速度很可以,但是想着是不是还有其他的方式再次优化,就把目光投向了mysql persist connection,我们用的框架也是支持的,就调整了下参数,OK上线测试响应再次提升。

但是调整之后,突然发现有些原来可用的功能,现在纷纷500了,查看日志后发现是sql的问题,而语句根本没有改动,不应该有问题的,但问题实实在在就在,是sql的语法错了。因为integer类型,被插入了null,经过和同事一番排查,发现是因为sqlmode严格模式导致的,所以就排查哪个地方可能修改了这个配置,先去PHP框架里面排查。

但是发现无论是否设置的长链接,这个mode的设置都为非严格模式,可是长链接为什么变成了严格模式呢。

决定要去php-src里面一探究竟,但是探寻这种项目真有如大海捞针一般,但是也是有迹可循的,因为长链接导致的问题,那我就去看哪里是处理长链接的地方就好了,根据上面框架中的逻辑,长链接时候host前面会有添加一个标示“p:”,这个就是一条线索,而且执行是链接函数是mysql_real_connect,我就照着这两个标志去找,经过一番排查,找到了位于ext/mysqli/mysql_api.c下面的mysqli_real_connect。

它调用同目录下mysqli_nonapi.c下面的mysqli_common_connect函数,而这个函数也的确有判断是否host里面存在“p:”这个长链接标示。

然后就接着去分析里面的逻辑,发现半天看源码看的懵懵的,陷入进去拔不出来了,排查的目的是看看mysql的init_comands为什么在长链接下不执行。

顺着这个目标展开调查,首先在php-src正常接收到脚本传递过来的,init_command的设置非严格模式的sql语句数据。而且数据也存入了mysqlnd_data结构下的options下的init_commands,但是存入不代表就会去用,下一步就要看这个命令是否被执行。

set mode执行
执行init_command也就是设置sqlmode的方法
这个时候就要好好去分析mysqli_common_connect了,也就是上图中的方法。这个方法基本逻辑就是从PHP代码当中获取指定连接参数然后根据你的host user 以及port等参数作为key,去查看是否有对应的连接存在如果没有则需要重新创建连接,也就是需要执行mysql的连接函数,这个时候经过debug发现其实在连接创建成功后,的确执行了设置sqlmode的语句。结果也是第一请求创建连接时候的确变成了非严格模式,但是后续的请求由于已经有了连接所以就不会重新创建连接,不设置sqlmode。

根据mysql连接的的原理,这种设置属于session模式下的,基本可以认定如果连接不关闭代表着session也不会过期,因为这是由状态的协议。但是奇怪的是mode之后都是严格模式,让我百思不得其解。

然后我接着一步步调试发现每次就算是长链接,PHP也会重新创建mysqlnd的数据,但是一旦发现连接池里面有东西就选择了丢弃使用新的直接使用池子里面的,我就怀疑是不是因为新的对象里面的options的sqlmode没有通过php代码写入成功,经过一阵子gdb发现写入成功了,而且也没用。而重用连接时候的options 里面的sqlmode为空,但是并不会执行设置sqlmode的语句。

这就让我很疑惑了,完全想不明白为什么会这样,这个问题下班后调试四五次每次都感觉快要解决了,但是总感觉还差点东西。接着在想有没有重新resetsession的mysql库函数呢,而PHP的mysqlnd正好也用上了。果不其然

mysql clear session state

看到了上面的介绍,之后又专门搜索了下对应的函数mysql_reset_connection,这个函数可以重置session的状态。

所以看到这里就出现了曙光如何确定如果这次PHP执行的bt中,确实存在这个函数的调用那就足以说明为什么后续的重用的连接设置的sqlmode无效了,接下来我去找对应的调用,

很显然还是在mysqli_common _connect的长链接逻辑里面,有这个函数的调用。不过PHP做了一个宏的重定义。

根据排查以及调试发现我们并没有设置对应的

MYSQLI_NO_CHANGE_USER_ON_PCONNECT,所以我们每次重用连接的时候就会发生清除所有session变量的情况,这样sqlmode就会变回原来默认的,就解释了为什么第一次设置有效之后的设置都无效。

所以解决的办法就是直接两个方向,第一在每次调用PHP执行sql语句时候调用一下设置sqlmode的语句而不是将它写入init_command里面,第二是设置宏定义重新编译。

转载请注明:XAMPP中文组官网 » gdb排查PHP数据库长链接问题

您必须 登录 才能发表评论!