PHP实现30个异地同构数据库的并行查询方案咨询
针对你要并行查询30个同构数据库、优化总耗时的需求,除了你提到的mysqli::poll/MYSQLI_ASYNC和pcntl_fork,还有几个实用的方案可以考虑,我给你逐一拆解:
1. 基于异步IO框架(Swoole/ReactPHP)
这是目前PHP生态里异步处理的主流方案,不需要依赖pcntl扩展,而是通过事件循环来处理多个数据库连接的异步查询,资源占用比多进程低很多。
比如用Swoole的异步MySQL客户端,你可以同时发起30个查询请求,每个请求完成后通过回调收集结果,最后汇总:
<?php $dbConfigs = [/* 你的30个数据库配置数组 */]; $results = []; $completed = 0; foreach ($dbConfigs as $key => $config) { $mysql = new Swoole\MySQL(); $mysql->connect($config, function ($mysql, $result) use ($key, &$results, &$completed, $dbConfigs) { if ($result === false) { $results[$key] = ['error' => $mysql->connect_error]; } else { $mysql->query('SELECT * FROM your_table', function ($mysql, $result) use ($key, &$results, &$completed) { $results[$key] = $result; $mysql->close(); $completed++; // 所有查询完成后处理结果 if ($completed === count($dbConfigs)) { var_dump($results); } }); } }); }
ReactPHP的思路类似,用其react/mysql组件实现异步查询,适合不想用Swoole这种重型扩展的场景。
2. HTTP接口封装 + Guzzle并发请求
如果不想直接操作数据库异步扩展,可以给每个数据库实例封装一个简单的HTTP查询接口(比如用PHP-FPM写个小脚本,或者Swoole HTTP服务),然后用Guzzle的并发请求能力同时调用这些接口:
<?php use GuzzleHttp\Client; use GuzzleHttp\Pool; use GuzzleHttp\Psr7\Request; $client = new Client(); $requests = function ($dbUrls) { foreach ($dbUrls as $url) { yield new Request('GET', $url . '?query=SELECT+*+FROM+your_table'); } }; $pool = new Pool($client, $requests([/* 30个数据库接口URL */]), [ 'concurrency' => 30, 'fulfilled' => function ($response, $index) use (&$results) { $results[$index] = json_decode($response->getBody(), true); }, 'rejected' => function ($reason, $index) use (&$results) { $results[$index] = ['error' => $reason->getMessage()]; }, ]); // 发起请求并等待完成 $promise = $pool->promise(); $promise->wait(); // 处理汇总结果 var_dump($results);
这种方案的好处是完全脱离数据库扩展的限制,甚至可以用其他语言写接口,缺点是多了一层HTTP开销,但如果查询本身耗时远大于HTTP请求的开销,这点影响可以忽略。
3. Redis队列 + 多进程Worker
如果需要更灵活的分布式处理,可以把查询任务放到Redis队列中,启动多个Worker进程(比如30个)并行消费任务,执行查询后把结果存回Redis,主进程等待所有任务完成后汇总:
- 主进程(任务分发):
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $dbConfigs = [/* 30个数据库配置 */]; // 把每个数据库的查询任务推入队列 foreach ($dbConfigs as $key => $config) { $redis->lPush('db_query_queue', json_encode([ 'key' => $key, 'config' => $config, 'query' => 'SELECT * FROM your_table' ])); } // 设置任务总数,方便Worker通知完成 $redis->set('db_query_total', count($dbConfigs));
- Worker进程(任务执行):
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $mysqli = new mysqli(); while (true) { $task = $redis->rPop('db_query_queue'); if (!$task) { usleep(10000); continue; } $task = json_decode($task, true); // 连接数据库执行查询 $mysqli->real_connect($task['config']['host'], $task['config']['user'], $task['config']['password'], $task['config']['dbname']); $result = $mysqli->query($task['query'])->fetch_all(MYSQLI_ASSOC); // 存储结果 $redis->hSet('db_query_results', $task['key'], json_encode($result)); // 标记任务完成 $completed = $redis->incr('db_query_completed'); if ($completed == $redis->get('db_query_total')) { $redis->set('db_query_done', 1); } }
- 主进程等待并汇总:
// 等待所有任务完成 while (!$redis->get('db_query_done')) { sleep(1); } // 获取所有结果 $results = []; foreach ($dbConfigs as $key => $_) { $results[$key] = json_decode($redis->hGet('db_query_results', $key), true); } var_dump($results);
这种方案的优势是解耦了任务分发和执行,Worker可以弹性扩容,甚至部署在不同机器上,适合后续业务规模扩大的场景。
4. MySQL FEDERATED存储引擎(特殊场景)
如果你的数据库都是MySQL,且权限允许,可以在一个中心数据库上创建FEDERATED表,映射到其他30个数据库的目标表,然后通过UNION ALL一次性查询所有FEDERATED表:
-- 先创建一个FEDERATED表模板 CREATE TABLE remote_table ( id INT PRIMARY KEY, ... -- 和目标表结构一致 ) ENGINE=FEDERATED CONNECTION='mysql://user:password@remote_host/dbname/table_name'; -- 为每个数据库创建对应的FEDERATED表,比如remote_table_1, remote_table_2... -- 然后查询所有表 SELECT * FROM remote_table_1 UNION ALL SELECT * FROM remote_table_2 ... UNION ALL SELECT * FROM remote_table_30;
MySQL会并行处理这些FEDERATED表的查询(取决于max_parallel_execution_count等配置),实现类似并行查询的效果。不过这个方案有不少限制:比如不支持事务、部分函数,且性能可能不如纯异步方案,但胜在实现简单,不需要修改太多业务代码。
总结下:
- 如果追求高性能和低资源占用,Swoole/ReactPHP异步IO是首选;
- 如果不想依赖特殊扩展,Guzzle+HTTP接口最灵活;
- 如果需要分布式或弹性扩容,Redis队列+Worker更合适;
- 如果是纯MySQL环境且场景简单,FEDERATED引擎可以快速实现。
内容的提问来源于stack exchange,提问作者DevilaN

