PHP项目里如果要用MyCat做分库分表,多数情况下你并不需要引入新的客户端扩展。MyCat对外暴露的是一个标准MySQL协议端口,PHP侧使用PDO或者mysqli都可以直接连上。然而“能连上”和“用得稳”是两回事。MyCat虽然模拟MySQL握手和查询流程,但它终究是一个中间层,很多原生MySQL的细节被简化或改写。比如连接状态、预处理语句、事务边界、字符集这些,在实际接入时都需要单独验证。下面内容会从MyCat的协议实现讲起,给出PHP连接MyCat的两种典型写法,再结合读写分离和常见报错,帮你梳理出一条稳定的接入路径。

一、MyCat的前端协议与连接模型
MyCat的定位是数据库中间件,前端负责接收客户端的SQL请求,后端再转发到真实的MySQL实例。为了做到对应用透明,MyCat实现了MySQL协议的服务端部分。连接建立时,它会先读取客户端发来的握手响应,包括用户名、密码、数据库名等信息,然后到自己的server.xml里查找对应的逻辑用户。如果校验通过,就返回一个伪造的MySQL握手成功包,让客户端以为自己连上了一台MySQL服务器。这个过程PHP完全无感知,所以PDO或mysqli的驱动都不用换。
但与原生MySQL不同的是,MyCat并不维护真正的会话状态。一些会话级变量比如sql_mode、autocommit、transaction isolation,MyCat可能只在内存里记录,不会同步到后端所有节点。这意味着如果你在PHP代码里执行set session wait_timeout=600,在MyCat连接中是生效的,但切换后端连接后这个设置可能丢失。另一个关键差异是预处理语句。MySQL协议里的预处理需要服务端保存语句句柄,MyCat虽然支持部分预处理,但当SQL需要路由到多个分片时,预处理模式可能被转换成文本协议执行,所以PDO的模拟预处理选项需要根据场景调整。
MyCat的前端连接池和后端连接池也是分离的。PHP到MyCat的连接数可以通过server.xml里的系统参数限制,MyCat到后端MySQL的连接数则受每一个dataHost配置的maxCon控制。这个双层连接模型会带来一个新的问题:PHP进程如果使用长连接,MyCat上的前端连接不会轻易释放,一旦PHP端脚本异常退出,MyCat上可能出现半开连接,需要靠idleTimeout或MySQL自身的wait_timeout来回收。
二、PHP使用PDO连接MyCat的完整示例与调优
先看一段最常见的PDO连接代码。host填MyCat所在服务器地址,端口默认8066,dbname填MyCat逻辑库名,这个逻辑库是在MyCat的schema.xml里定义好的,不一定对应后端某一个真实库。用户名和密码则来自server.xml里配置的MyCat用户,而不是后端MySQL的用户。
<?php
$host = '192.168.1.100';
$port = 8066;
$dbname = 'testdb';
$user = 'mycat_user';
$pass = 'mycat_pass';
$dsn = "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_TIMEOUT => 5,
]);
echo '连接MyCat成功';
} catch (PDOException $e) {
echo '连接失败:' . $e->getMessage();
}
?>
代码里把PDO::ATTR_EMULATE_PREPARES设成了false。这样PDO会尽量使用MySQL原生的预处理语句。不过MyCat对原生预处理的支持依赖SQL路由结果,如果SQL只落在单个分片,原生预处理通常没问题;一旦SQL被拆分到多个分片,MyCat可能会回退到文本协议执行,此时关闭模拟预处理并不会提升性能,反而可能在极端情况下触发驱动兼容问题。所以如果你的业务SQL大多带分片键、只操作单分片,可以关掉模拟预处理;如果存在大量跨分片查询,建议保持默认的true,让PDO在本地做参数转义更稳妥。
超时参数也很重要。PDO::ATTR_TIMEOUT设置的是连接阶段的超时时间,单位秒。MyCat在高并发下如果连接数打满,新连接可能会在队列里等待,此时5秒超时可以让PHP快速失败,避免请求堆积。查询超时则可以通过MYSQL_ATTR_READ_TIMEOUT和MYSQL_ATTR_WRITE_TIMEOUT单独控制,这两个选项需要作为驱动选项传入PDO构造函数的第四个参数,例如[PDO::MYSQL_ATTR_READ_TIMEOUT => 3]。不建议使用无限等待,因为MyCat后端如果某个分片慢查询拖住,PHP的数据库连接会长时间不返回,进而耗尽FPM进程。
三、mysqli连接MyCat与读写分离配合
mysqli连接MyCat的写法和连接原生MySQL几乎一样,只需要把host和port指向MyCat即可。下面这段代码建立连接后,查询和更新都会先发到MyCat,再由MyCat决定路由到哪个后端节点。
<?php
$mysqli = mysqli_init();
if (!$mysqli) {
die('mysqli初始化失败');
}
$host = '192.168.1.100';
$port = 8066;
$user = 'mycat_user';
$pass = 'mycat_pass';
$dbname = 'testdb';
$connected = @$mysqli->real_connect($host, $user, $pass, $dbname, $port, null, MYSQLI_CLIENT_FOUND_ROWS);
if (!$connected) {
die('连接失败:' . $mysqli->connect_error);
}
$mysqli->set_charset('utf8mb4');
$res = $mysqli->query('SELECT id, order_no FROM orders WHERE user_id = 1001');
while ($row = $res->fetch_assoc()) {
var_dump($row);
}
$mysqli->close();
?>
读写分离是MyCat的强项。PHP代码不需要判断当前SQL是读还是写,只需要把SQL统一发到MyCat。MyCat会根据schema.xml里的dataHost配置,结合SQL类型和balance参数自动分发。例如下面这段配置定义了一个写主机和两个读主机,balance为1时,所有读操作会随机落在读主机上,写操作仍然走写主机。
<dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="writeM1" url="192.168.1.101:3306" user="mysql_user" password="mysql_pass">
<readHost host="readS1" url="192.168.1.102:3306" user="mysql_user" password="mysql_pass" />
<readHost host="readS2" url="192.168.1.103:3306" user="mysql_user" password="mysql_pass" />
</writeHost>
</dataHost>
在上面配置中,后端MySQL的用户名密码是真实数据库的用户,和PHP连接MyCat所用的逻辑用户不同。很多开发者一开始会把这两个用户混在一起,导致MyCat启动后检测心跳失败,或者PHP连接MyCat成功但一执行SQL就报权限错误。排查时先确认MyCat日志中后端数据源是否初始化成功,再确认PHP端使用的MyCat用户是否有对应逻辑库的访问权限。
主从延迟是读写分离方案里绕不开的问题。MyCat没有强制读写一致性,如果业务上刚插入一条数据紧接着要读出来,读请求有可能落到还没同步完成的从库上。解决方式有两种:一是把这类强一致性的读操作放到事务里,MyCat对事务内的SQL会优先发送到写主机;二是在MyCat层面调整balance参数,或在SQL中显式使用/*#mycat:db_type=master*/注解强制走主库。PHP侧只需要在SQL文本前面拼接这个注解即可,不需要改连接逻辑。
四、连接MyCat的常见故障与排查思路
PHP连接MyCat时报错最常见的是连接超时或拒绝连接。如果MyCat进程没有启动,或者监听的端口不是PHP代码里填的那个,会出现SQLSTATE[HY000] [2002] Connection refused。确认MyCat是否在运行可以用netstat -tlnp | grep 8066查看端口监听情况。如果端口正常但连接仍然被拒,检查server.xml里是否添加了PHP所在IP的访问权限,MyCat默认允许所有IP,但部分版本会限制。
认证失败是另一个高频问题。MyCat的用户体系独立于后端MySQL,server.xml里配置的用户和密码必须与PHP连接字符串完全一致。比如server.xml里密码是mycat_pass,PHP里写成mycat_pass1,会报Access denied for user。此外,还要注意MyCat逻辑库名的大小写,MyCat默认逻辑库名大小写敏感,dbname填错会提示Unknown database。
SQL执行报错时不要只在PHP侧看异常信息,MyCat的wrapper.log通常记录了更完整的路由过程和错误原因。比如跨分片join、子查询、函数不支持等,MyCat会返回类似ERR CODE: 1064或can't find table的错误。查看日志目录下的wrapper.log,重点看SQL被路由到了哪些节点、是否有分片规则未命中。如果看到can't find table但表名明明存在,大概率是schema.xml里没有为该表配置分片规则,或者表配置在了错误的schema下。
字符集问题也值得单独强调。PHP连接MyCat时如果设置charset=utf8mb4,MyCat前端协议会协商这个字符集,但它转发到后端MySQL时可能使用自己配置的字符集。若两边不一致,中文或emoji数据写入后会乱码。建议在MyCat的server.xml中将system节点的charset属性设为utf8mb4,同时每个dataHost的连接配置也保持一样。PHP侧尽量统一使用utf8mb4,不要用utf8。
五、总结
PHP把MyCat当作标准MySQL协议连接,这个思路本身没有错,但需要理解中间层和真实数据库之间的差异。连接参数、超时控制、预处理模式、读写分离策略以及日志排查,每个环节都可能影响稳定性。接入MyCat之前,建议先在测试环境用相同版本的PHP驱动和MyCat配置跑通核心SQL,尤其要覆盖跨分片查询和事务场景。只有把协议兼容性测试做扎实,才能在生产环境放心地让PHP和MyCat一起工作。
PHP连接MyCatMyCat中间件MySQL协议修改时间:2026-09-25 07:52:32