Xtrabackup备份导致从库宕机重启,诱因是?

最近,在写一个备份效验平台,在利用xtrabackup进行物理备的过程中,踩到一些问题点(比如stream模式下,DB写入数据过大同时未指定tmpdir,xtrabackup_logfile文件会放在/tmp目录,进而导致跟目录写满;比如flush tables with read lock情况,从库复制线程有update,导致 ftwrl卡住,最终导致内存突降问题),今天我们聊聊,ftwrl,导致DB宕机重启的问题,文章过几天补充发布

Xtrabackup备份为什么导致内存突降耗尽,进而导致备份失败??

最近,在写一个备份平台,正好遇到一些问题,就整体记录一下,先分享标题,正文过段时间再写,各位小伙伴也可以先评论,相信大家都或多或少遇到此类问题

Xtrabackup物理备如何进行单表恢复??

在平时的工作中,经常会遇到各种奇葩需求,比如测试团队提个需求,我需要恢复一个数据库用来压测某个接口,该接口只需要某个实例的某个库某几张表(当然,这个表比较大,小表直接dump就可以),当然,实际遇到的需求多样,那我们怎么从物理全备里快速提取我们需要的表呢??

MySQL线上大表如何无害删除??

在我们日常的DB维护中,经常会遇到大表删除问题,大表如果在数据层进行drop操作,将会对磁盘造成很大的IO冲击,进而导致线上业务有问题

这里我们采用针对数据文件建立硬链,欺骗mysql,进行删表操作,然后,再在系统层进行删除

1.新建临时表

mysql> create table event_message_tmp like event_message;

2.新旧表切换

mysql> rename table event_message to event_message_old,event_message_tmp to event_message;

3.系统层面建立硬链接

cd /data/mysql/data/***

# ln event_message_old.ibd event_message_old.ibd.hdlk

# ll event_message_old*

4.DB层面,删除老表

mysql> drop table event_message_old;

注意 表太大,直接rm 必然会对数据库服务器的IO性能造成压力

脚本如下:

#!/bin/bash

TRUNCATE=/usr/bin/truncate

for i in `seq 218 -5 5`;

do

sleep 1

echo “$TRUNCATE -s ${i}G /data/mysql/data/mysql56_data3306/abc/event_message.ibd.hdlk”

$TRUNCATE -s ${i}G /data/mysql/data/mysql56_data3306/abc/event_message.ibd.hdlk

done

FLUSH TABLE WITH READ LOCK详解

FLUSH TABLES WITH READ LOCK简称(FTWRL),该命令主要用于备份工具获取一致性备份(数据与binlog位点匹配)。由于FTWRL总共需要持有两把全局的MDL锁,并且还需要关闭所有表对象,因此这个命令的杀伤性很大,执行命令时容易导致库hang住。如果是主库,则业务无法正常访问;如果是备库,则会导致SQL线程卡住,主备延迟。本文将详细介绍FTWRL到底做了什么操作,每个操作的对库的影响,以及操作背后的原因。

FTWRL做了什么操作?

FTWRL主要包括3个步骤:

1.上全局读锁(lock_global_read_lock)
2.清理表缓存(close_cached_tables)
3.上全局COMMIT锁(make_global_read_lock_block_commit)

FTWRL每个操作的影响

上全局读锁会导致所有更新操作都会被堵塞;关闭表过程中,如果有大查询导致关闭表等待,那么所有访问这个表的查询和更新都需要等待;上全局COMMIT锁时,会堵塞活跃事务提交。由于FTWRL主要被备份工具使用,后面会详细解释每个步骤的作用,以及存在的必要性。FTWRL中的第1和第3步都是通过MDL锁实现,关于MDL的实现,我之前总结了MDL锁的文章,这里主要介绍清理表缓存的流程。

清理表缓存

每个表在内存中都有一个table_cache,不同表的cache对象通过hash链表维护。
访问cache对象通过LOCK_open互斥量保护,每个会话打开的表时,引用计数share->ref_count++,
关闭表时,都会去对引用计数share->ref_count–。
若发现是share对象的最后一个引用(share->ref_count==0),并且share有old_version,
则将table_def_cache从hash链表中摘除,调用free_table_share进行处理。关键函数close table流程如下:

1.关闭所有未使用的表对象
2.更新全局字典的版本号
3.对于在使用的表对象,逐一检查,若表还在使用中,调用MDL_wait::timed_wait进行等待
4.将等待对象关联到table_cache对象中
5.继续遍历使用的表对象
6.直到所有表都不再使用,则关闭成功。

清理表缓存函数调用

mysql_execute_command->reload_acl_and_cache->close_cached_tables
->TABLE_SHARE::wait_for_old_version->MDL_wait::timed_wait->
inline_mysql_cond_timedwait

会话操作表流程

1.打开表操作,若发现还有old_version,则进行等待
2.share->ref_count++
3.操作完毕,检查share->ref_count–是否为0
4.若为0,并且检查发现有新版本号,则认为cache对象需要重载
5.将cache对象摘除,调用MDL_wait::set_status唤醒所有等待的线程。

关闭表对象函数调用

dispatch_command->mysql_parse->mysql_execute_command->
close_thread_tables->close_open_tables->close_thread_table->
intern_close_table->closefrm->release_table_share->my_hash_delete->
table_def_free_entry->free_table_share

关闭表导致业务库堵住的典型场景

假设有3个会话,会话A执行大查询,访问t表;然后一个备份会话B正处于关闭表阶段,需要关闭表t;随后会话C也请求访问t表。三个会话按照这个顺序执行,我们会发现备份会话B和会话C访问t表的线程都处于“waiting for table flush”状态。这就是关闭表引起的,这个问题很严重,因为此时普通的select查询也被堵住了。下面简单解释下原因:

1.会话A打开表t,执行中……
2.备份会话B需要清理表t的cache,更新版本号(refresh_version++)
3.会话B发现表t存在旧版本(version != refresh_version),表示还有会话正在访问表t,
等待,加入share对象的等待队列
4.后续会话C同样发现存在旧版本(version != refresh_version),
等待,加入share对象的等待队列
……
5. 大查询执行完毕,调用free_table_share,唤醒所有等待线程。

free_table_share //逐一唤醒所有等待的线程。
{
while ((ticket= it++))
ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED);
}

第4步与第5步之间,所有的访问该表的会话都处于“waiting for table flush”状态,唯有大查询结束后,等待状态才能解除。

主备切换场景

在生产环境中,为了容灾一般mysql服务都由主备库组成,当主库出现问题时,可以切换到备库运行,保证服务的高可用。在这个过程中有一点很重要,避免双写。因为导致切换的场景有很多,可能是因为主库压力过大hang住了,也有可能是主库触发mysql bug重启了等。当我们将备库写开启时,如果老主库活着,一定要先将其设置为read_only状态。“set global read_only=1”这个命令实际上也和FTWRL类似,也需要上两把MDL,只是不需要清理表缓存而已。如果老主库上还有大的更新事务,将导致set global read_only hang住,设置失败。因此切换程序在设计时,要考虑这一点。

关键函数:fix_read_only

1.lock_global_read_lock(),避免新的更新事务,阻止更新操作
2.make_global_read_lock_block_commit,避免活跃的事务提交

FTWRL与备份

Mysql的备份方式,主要包括两类,逻辑备份和物理备份,逻辑备份的典型代表是mysqldump,物理备份的典型代表是extrabackup。根据备份是否需要停止服务,可以将备份分为冷备和热备。冷备要求服务器关闭,这个在生产环境中基本不现实,而且也与FTWRL无关,这里主要讨论热备。Mysql的架构支持插件式存储引擎,通常我们以是否支持事务划分,典型的代表就是myisam和innodb,这两个存储引擎分别是早期和现在mysql表的默认存储引擎。我们的讨论也主要围绕这两种引擎展开。对于innodb存储引擎而言,在使用mysqldump获取一致性备份时,我们经常会使用两个参数,–single-transaction和–master-data,前者保证innodb表的数据一致性,后者保证获取与数据备份匹配的一致性位点,主要用于搭建复制。现在使用mysql主备集群基本是标配,所以也是必需的。对于myisam,就需要通过–lock-all-tables参数和–master-data来达到同样的目的。我们在来回顾下FTWRL的3个步骤:

1. 上全局读锁
2. 清理表缓存
3. 上全局COMMIT锁

第一步的作用是堵塞更新,备份时,我们期望获取此时数据库的一致状态,不希望有更多的更新操作进来。对于innodb引擎而言,其自身的MVCC机制,可以保证读到老版本数据,因此第一步对它使多余的。第二步,清理表缓存,这个操作对于myisam有意义,关闭myisam表时,会强制要求表的缓存落盘,这对于物理备份myisam表是有意义的,因为物理备份是直接拷贝物理文件。对于innodb表,则无需这样,因为innodb有自己的redolog,只要记录当时LSN,然后备份LSN以后的redolog即可。第三步,主要是保证能获取一致性的binlog位点,这点对于myisam和innodb作用是一样的。

所以总的来说,FTWRL对于innodb引擎而言,最重要的是获取一致性位点,前面两个步骤是可有可无的,因此如果业务表全部是innodb表,这把大锁从原理上来讲是可以拆的,而且percona公司也确实做了这样的事情,具体大家可以参考blog链接。此外,官方版本的5.5和5.6对于mysqldump做了一个优化,主要改动是,5.5备份一个表,锁一个表,备份下一个表时,再上锁一个表,已经备份完的表锁不释放,这样持续进行,直到备份完成才统一释放锁。5.6则是备份完一个表,就释放一个锁,实现主要是通过innodb的保存点机制。相关的bug可以参考链接:http://bugs.mysql.com/bug.php?id=71017

MySQL查询条件中字符串包含空格的问题

问题
最近在联调某个业务时发现使用的签名总是验证不过,在MySQL中查询了该业务的私钥配置和业务侧的配置是一样的,问题就出在SQL查询这里,最后将配置导出到本地发现私钥后面多了一个空格,将空格删除然后签名计算OK。问题是:为什么在DB查询条件中的字符串没有包含空格也可以查到实际包含空格的这条记录呢?

原因
如果字段是char或varchar类型,那么在字符串比较的时候MySQL使用PADSPACE校对规则,会忽略字段末尾的空格字符,若想做到精确匹配可以使用下面几种方法:
方法1:使用like语句;
方法2:使用binary类型,例如,select binary ‘a’ = ‘a ‘
方法3:再添加一个length()条件,例如,select col from table where col = ‘a ‘ and LENGTH(col) = LENGTH(‘a ‘)

官方手册说明(5.0版本):
http://dev.mysql.com/doc/refman/5.0/en/char.html
11.1.6.1. The CHAR and VARCHAR Types
All MySQL collations are of type PADSPACE. This means that all CHAR, VARCHAR, and TEXT values in MySQL are compared without regard to any trailing spaces. “Comparison” in this context does not include the LIKE pattern-matching operator, for which trailing spaces are significant.

转载地址:https://blog.csdn.net/delphiwcdj/article/details/16983993

insert into a select * from b 锁问题分析

很长一段时间,都认为”insert into a select * from b” 会导致b表被锁住。在最近一次组内同事的质疑中,发现错了.b表会不会被锁,其实和隔离级别有一定关系,下面我们具体分析一下

环境说明:
1.隔离级别:REPEATABLE-READ
2.MySQL版本:5.6.29
3.Binlog格式:ROW
4.建表SQL:

CREATE TABLE `os_diskio_history_bak` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`ip` varchar(50) NOT NULL,
`tags` varchar(100) DEFAULT NULL,
`fdisk` varchar(50) NOT NULL DEFAULT '0',
`disk_io_reads` bigint(18) NOT NULL DEFAULT '0',
`disk_io_writes` bigint(18) NOT NULL DEFAULT '0',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`YmdHi` bigint(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_ymdhi` (`YmdHi`) USING BTREE,
KEY `idx_ip_ymdhi` (`ip`,`YmdHi`),
KEY `idx_io_reads` (`disk_io_reads`),
KEY `idx_io_writes` (`disk_io_writes`)
) ENGINE=InnoDB AUTO_INCREMENT=484556 DEFAULT CHARSET=utf8

测试用例:
1.直接插入

终端1:
mysql> insert into os_diskio_history_bak select * from os_diskio_history;
终端2:
mysql> show engine innodb status;
mysql tables in use 2, locked 2
929 lock struct(s), heap size 112168, 207989 row lock(s), undo log entries 207063
mysql> show engine innodb status;
mysql tables in use 2, locked 2
1636 lock struct(s), heap size 194088, 363913 row lock(s), undo log entries 362280
mysql> show engine innodb status;
mysql tables in use 2, locked 2
1984 lock struct(s), heap size 226856, 440646 row lock(s), undo log entries 438666

从上面,我们看到 row lock 和 lock struct 在不停的增长,被锁的行数在不断增加

2.按主键降序插入

终端1:mysql> insert into os_diskio_history_bak select * from os_diskio_history order by id desc;
终端2:
mysql> show engine innodb status;
mysql tables in use 2, locked 2
502 lock struct(s), heap size 63016, 109585 row lock(s), undo log entries 109583
mysql> show engine innodb status;
mysql tables in use 2, locked 2
630 lock struct(s), heap size 79400, 137782 row lock(s), undo log entries 137781
mysql> show engine innodb status;
mysql tables in use 2, locked 2
920 lock struct(s), heap size 112168, 201581 row lock(s), undo log entries 201580
mysql> show engine innodb status;
mysql tables in use 2, locked 2
1240 lock struct(s), heap size 144936, 271567 row lock(s), undo log entries 271566

从上面,我们看到 row lock 和 lock struct 在不停的增长,被锁的行数在不断增加,其实同测试案例1
结论:
通过主键排序或则不加排序字段的导入操作”insert into a select * from b”,是会锁b表,但他的锁是逐步地锁定已经扫描过的记录。
默认:
主键升序的select :从第一行开始扫描到最后,即第一行开始锁直到最后。
主键倒序select:从最后一行开始扫描到最前,即最后一行开始锁直到第一行。

3.B 表使用非主键字段排序

终端1:mysql> insert into os_diskio_history_bak select * from os_diskio_history order by YmdHi desc;
终端2:
mysql> show engine innodb status;
mysql tables in use 2, locked 2
2190 lock struct(s), heap size 259624, 486689 row lock(s)
mysql> show engine innodb status;
mysql tables in use 2, locked 2
2191 lock struct(s), heap size 259624, 486689 row lock(s)
mysql> show engine innodb status;
mysql tables in use 2, locked 2
2192 lock struct(s), heap size 259624, 486689 row lock(s), undo log entries 44409

从上面,我们看到 row lock 和 lock struct 值一直没有变,他的锁是一开始就会锁定整张表,不能进行任何操作,直到锁释放了

结论:insert into a select * from b 的导入操作是会锁定原表,但是锁是有2种情况:“逐步锁”,“全锁”

现在我们调整隔离级别为:RC

终端1:
mysql> set tx_isolation='READ-COMMITTED';
mysql> insert into os_diskio_history_bak select * from os_diskio_history order by YmdHi desc;
终端2:
mysql> show engine innodb status;
mysql tables in use 2, locked 1
2 lock struct(s), heap size 360, 0 row lock(s), undo log entries 41394
mysql> show engine innodb status;
mysql tables in use 2, locked 1
2 lock struct(s), heap size 360, 0 row lock(s), undo log entries 95860

从上面,我们看到 row lock 为 0

总结:
1.在RR模式下:在按主键进行排序或不排序时,采用的是逐步锁定行
2.在RR模式下:在按非主键进行排序时,采用的是表锁
3.在RC模式下:无论按主键还是非主键排序,B表都不加锁

DBPRoxy 监控用户(backend-monitor-pwds)未配置,引起后端DB日志出现登陆报错

最近,在新上线的一套DBProxy+MySQL项目过程中,发现后端MySQL DB出现登陆报错:

[Warning] Access denied for user ‘apps’@’192.168.1.146’ (using password: NO)

以上报错为测试环境,模拟产生的报错信息,实际生产环境是 Access denied for user ‘root’@’proxy IP’ (using password: NO)

查看了其它DBProxy后端的DB,没有此类报错。

对比问题DBPrroxy和正常DBProxy的配置文件,版本等信息,发现都是一样,无差异。

这样看来,好紧张,居然有root用户尝试远程登陆,这是多危险,被攻击了????当时的第一反应(其实要说明一点,root用户其实设置了只能本地登录)。

登陆问题DBProxy服务器,查询进程,top,抓包,看proxy日志,系统日志,都未发现异常,但是,后端所有DB的报错还在持续。

果断,在测试环境复现问题,复现环境如下:

MySQL Version:MySQL 5.7.22

DBPrroxy Version:5.0.99-agent-admin

MySQL 重要参数配置:log_warnings = 2 (设定是否将警告信息记录进错误日志。默认设定为1,表示启用;可以将其设置为0以禁用;而其值为大于1的数值时表示将新发起连接时产生的“失败的连接”和“拒绝访问”类的错误信息也记录进错误日志)

后端DB报错截图如下:

4

在测试环境复现,我们发现如下几个问题:

1.如果用错误的账号来连接DBProxy时,DBProxy的日志会打印出报错信息(这里的192.168.1.146 为DBProxy的IP地址),但我们生产环境上DBProxy没有任何相关报错日志

测试命令如下:

[apps@unify-mysql mysql]$ mysql -uapp -p -h192.168.1.146 -P8888
Enter password:
ERROR 1045 (28000): Access denied for user ‘app’@’192.168.1.146’ (using password: YES)

DBProxy报错截图如下:

2

2.从DB后端错误日志,我们发现一些问题:

生产环境是root@proxy ip 登陆报错,root账号,后端DB是存在这个账号的,这个也就是我们第一反应被攻击的缘由

测试环境是apps@proxy ip登陆报错,apps账号,后端DB不存在这个账号,这是怎么回事呢?大家猜猜!!!文章结尾,我揭晓原因~~~

3.我们尝试从DBProxy,剔除一台从库,看看还会不会报错,剔除后,发现日志干净了,一点报错也没有,到这里,我们心里就踏实了,不是被攻击了,是DBProxy自己去连后端DB的!!

4.于是我们仔细检查DB日志,发现登陆存在一定的规律性,每隔4秒,出现一次报错,截图如下:

1

同时,我们看了DBProxy的相关参数,发现有一项参数,正好设置的是4秒,同时,我咨询了官方的海涛同学,证实了我们刚才的猜想,答复是这样的:

MySQL协议里,如果没有显示的指明用户名的话,会使用root连接。DBProxy里,如果没有配置monitor账号,DBProxy在监控后端DB的时候,会使用NULL用户名去连接DB,MySQL端会认为是root账号连接的。默认proxy的monitor周期检测是4s一次,但是每次检测会遍历每个DB,所以并非完全4s精准~

于是,我们设置monitor账号,命令如下:

set backend-monitor-pwds=dbproxy_user:dbproxy_user;

此时参数的截图如下:

3

此时,我们再看后端DB的日志,发现错误消失了,问题到此,算是找到原因,并解决了

总结:

1.为什么线上环境报错日志是root连,而测试环境报错日志是用apps连??

如果贵司已经进行目录标准化后,我相信您的部署,肯定不是用官方的脚本,而是按照自己的规则定义,比如目录属主,安装路径,启动用户等等,这里就是因为线上用root用户启动,目录属主为root。而测试环境是我自定义安装的,属主为apps用户,启动用户也是apps用户

2.线上DBProxy文件,对比一致,为什么只有这一套环境有报错??

由于DB配置文件不标准化,部署因人而异,log_warnings设置成0了,这个因公司而异,很多公司,这个参数的确设置为0

3.最后,要说明的,任何自动化的前提,都需要标准化,这里不仅目录标准化,还有配置标准化,部署标准化,操作标准化等等。任重而道远~~~

DBProxy Warning: Syntax Forbidden Set option:^A 分析及应对方案

      在我们的日常运维中,随着公司业务的持续发展,DB的单机性能逐渐满足不了要求,大家首先想到的可能是拆表,拆库,扩容,归档,提升硬件配置等等,但实际业务场景中,往往优先出现瓶颈的是读的性能,这也就是,我下面要说的数据库读写分离。
       目前,每个公司的读写分离策略都不太一样,有的采用的是开源的数据库中间件,对应用开发来说,是透明的;有的公司采用的是代码层时间读写分离,需要对代码有一定的倾入性;也有采用自研其他方案。
       现阶段,由于我们绝大多数应用是PHP应用,业务持续发展,代码层实现读写分离,过于繁琐,持续时间长,研发经历有限,最终,经过调研,先后选型了Atlas/DBProxy,目前,公司读写分离中间件,90%都是使用DBProxy。在使用过程中,难免会碰到各种问题,但相对来说,DBProxy的稳定性不错,官方答复问题的速度也是非常迅速,是一个非常棒的开源团队,这里特别需要感谢一下,DBProxy官方团队的海涛同学,多次协助分析问题。
       DBProxy的下载,部署等环节,这里忽略,请移步官方文档,文档写的非常明确详实。
       DBProxy github地址:https://github.com/Meituan-Dianping/DBProxy
       我们使用DBProxy过程中,也并非一帆风顺,也遇到一些问题,比如,业务代码里有一些,Proxy不支持的语法问题,如Proxy Warning – Syntax Forbidden Set option:^A
———————————————————————————————————
下面,我们重点进行分析这个警告的来源及处理方案:
1.报错产生的环境:
         DBProxy Version:5.0.99-agent-admin
         MySQL Version:5.6.25 / 5.5.5-10.2.14-MariaDB-log
         PHP Version:5.6.11
         WARNING:2018-06-25 15:16:24.281125: (critical)proxy-plugin.c:2125(proxy_read_query) event_thread(1) C:192.168.1.146:52396 S:192.168.1.146:8888(thread_id:0) Usr:dbproxy_user Db:dbproxy Proxy Warning – Syntax Forbidden Set option:^A
2.遇到这个问题,首先,我们第一时间打开sql log,因为从日志看,没法知道具体是什么set操作,proxy不支持。
        mysql> set sql-log=on;
        mysql> set sql-log-slow-ms=0;
        mysql> set long-query-time=0;
3.查看SQL日志,这个目录一般在安装路径下的log/sql目录下,部分日志如下:(这里我们利用测试代码,重现错误,后面提供php测试代码)
       2018-06-25 15:16:24.281250: C:192.168.1.146:52396 C_db:dbproxy C_usr:dbproxy_user [Slow Query] 0.164(ms) Set option ^A
       2018-06-25 15:16:24.281876: C_begin:2018-06-25 15:16:24.281327 C:192.168.1.146:52396 C_db:dbproxy C_usr:dbproxy_user S:192.168.1.146:3307(thread_id:1041192) S_db:dbproxy S_usr:dbproxy_user inj(type:4
 bytes:0 rows:0) 0.438(ms) OK Query:SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary
       2018-06-25 15:16:24.281977: C:192.168.1.146:52396 C_db:dbproxy C_usr:dbproxy_user [Slow Query] 0.648(ms) Query SET character_set_connection=utf8, character_set_results=utf8, character_set_client=bina
ry
       2018-06-25 15:16:24.282334: C_begin:2018-06-25 15:16:24.282083 C:192.168.1.146:52396 C_db:dbproxy C_usr:dbproxy_user S:192.168.1.146:3307(thread_id:1041192) S_db:dbproxy S_usr:dbproxy_user inj(type:4
bytes:0 rows:0) 0.204(ms) OK Query:SET sql_mode=”
       从上面的日志看,最初认为是SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary,SET sql_mode=” 这些操作的不支持造成,但是,反复测试发现,这些不是根源,而且,从日志观察看,提示OK Query,说明,这个操作,Proxy是支持的,并且是成功,如果不支持的话,Proxy日志应该报ERRQuery.
       所以,从日志看,就是一个Set option ^A,让研发按关键字搜索代码,研发表示懵逼,代码里没有这个操作,搜索其他set操作,如SET character_set_connection=utf8 ,代码倒是有,于是,排查问题,陷入僵局,代码里没有,那么是哪里的问题,但是,直觉告诉你,这肯定和代码有关系,于是,有了下面的抓包分析问题。
       测试代码:测试代码1
4.在DB层或proxy层抓包分析:
抓包:tcpdump -i any tcp and port 3306 -s 500 -w proxy.pcap
分析工具:wireshark
定位截图:
QQ图片20180628104223
       从抓包分析,我们看到,做了multi statements off 操作造成,但是代码里的确没有这个操作,此时,我们判断,可能是PHP自身的函数造成的。
        搜了很多multi statement相关文档后,怀疑是使用了multi_query()这个函数,我们用multi_query()进行查询,的确可以复现问题,感觉快要看到光明了。呵呵….,再次让研发帮忙搜索代码,研发又一次懵逼,代码里没有用到这个函数,擦,瞬间掉到冰窟窿里了,这是哪里出了问题。
       使用mysqli_quey()函数测试代码:测试代码2
       于是,重新模拟代码测试发现(见测试代码1),模拟的代码里只有mysql_connect()和mysql_query()这两个关键函数,会不会是这两个函数的问题呢???
       于是,我们进行了替换,修改为:mysqli_query(),mysql_connect 修改为 mysqli_connect() 问题解决
       见测试代码:测试代码3
       有关mysql_connect()/mysql_query(),PHP官方是这样说的:
       mysql_connect — 打开一个到 MySQL 服务器的连接
       Warning
       本扩展自 PHP 5.5.0 起已废弃,并在自 PHP 7.0.0 开始被移除。应使用 MySQLi 或 PDO_MySQL 扩展来替换之
       这也就是,为什么php7的应用代码,没有这个问题。其实,虽然php5开始废弃,但是并没有被移除,所以,老的函数依然可以使用。这也就是我这边是php5.6的代码,为什么有这个问题
5.最终处理方案:
       mysql_connect()/mysql_query() 替换为 mysqli_connect()/mysqli_query()
6.测试代码如下:
       每段代码的开头和结尾分别加上<?php 和 ?>,进行测试

1.测试代码1:
$con = mysql_connect("192.168.1.146:8888","dbproxy_user","dbproxy_user");
if (!$con)
{
die('Could not connect: ' . mysql_error());
}
mysql_select_db("dbproxy", $con);
$sql_1 = "SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary";
mysql_query($sql_1,$con);
$sql_2 = "SET sql_mode=''";
mysql_query($sql_2,$con);
$sql_3 = "select * from shirt limit 1;";
mysql_query($sql_3,$con);
mysql_close($con);

2.测试代码2:
$mysqli = new mysqli("192.168.1.146", "dbproxy_user", "dbproxy_user", "",'8888');
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
$mysqli->query("SET NAMES 'UTF8'");
$mysqli->query("SET sql_mode=''");
$mysqli->query("SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary");
$sql = "INSERT INTO test(id) VALUES (1);";
$sql.= "select id from test;";
if (!$mysqli->query($sql)) {
echo "Multi query failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->multi_query($sql)) {
echo "Multi query failed: (" . $mysqli->errno . ") " . $mysqli->error;
}

3.测试代码3:
$con = mysqli_connect("192.168.1.146","dbproxy_user","dbproxy_user","dbproxy","8888");
if (!$con)
{
die('Could not connect: ' . mysql_error());
}
$sql_1 = "SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary";
$sql_2 = "SET sql_mode=''";
$sql_3 = "select * from shirt limit 1;";
mysqli_query($con,$sql_1);
mysqli_query($con,$sql_2);
mysqli_query($con,$sql_3);
mysqli_close($con);