PHP MongoDB客户端批量更新50万+文档时出现缓冲区溢出错误
PHP MongoDB批量更新触发缓冲区溢出错误的解决方案
环境信息
- PHP 8.2.6
- Debian GNU/Linux 11 (bullseye)
- MongoDB PHP扩展版本:1.15.0+1.11.1+1.9.2+1.7.5
错误信息
/build/php-mongodb-1.15.0+1.11.1+1.9.2+1.7.5/build-8.2/src/libmongoc/src/libmongoc/src/mongoc/mongoc-buffer.c:139 _mongoc_buffer_append(): precondition failed: (buffer->datalen + data_size) < INT_MAX
问题场景
批量更新50万条MongoDB文档时进程终止并抛出上述错误,限制单次更新数据集在20万条以内可正常运行。尝试按100万条为周期提交批量写入,但问题未解决。
原批量更新代码
$manager = new \MongoDB\Driver\Manager('mongodb://localhost:27017'); $bulk = new \MongoDB\Driver\BulkWrite(["ordered" => false]); while (is_object($richiesta = $richieste->fetch_object())) { // ... 处理$dati和$aXML的逻辑 $bulk->update( ['_id' => $dati['id_mov_contab']], $aXML, ['multi' => false, 'upsert' => true] ); } if ($bulk->count() > 0) { $manager->executeBulkWrite('catasto.catasto', $bulk); }
未解决问题的修改代码
$manager = new \MongoDB\Driver\Manager('mongodb://localhost:27017'); $bulk = new \MongoDB\Driver\BulkWrite(["ordered" => false]); $c=0; while (is_object($richiesta = $richieste->fetch_object())) { // ... 处理$dati和$aXML的逻辑 $bulk->update( ['_id' => $dati['id_mov_contab']], $aXML, ['multi' => false, 'upsert' => true] ); if($c%1000000 ==0 && $bulk->count()>0 ) { $manager->executeBulkWrite('catasto.catasto', $bulk); $manager = new \MongoDB\Driver\Manager('mongodb://localhost:27017'); $bulk = new \MongoDB\Driver\BulkWrite(["ordered" => false]); } $c++; } if($bulk->count()>0) { $manager->executeBulkWrite('catasto.catasto', $bulk); }
解决方案
1. 降低批量提交阈值
之前设置的100万条远超过安全阈值(20万条),导致批量操作的总数据量超过INT_MAX(约2GB)的缓冲区限制。将批量大小调整为20万以内,比如10万条,确保每次提交的总数据量不触发缓冲区溢出。
2. 优化批量操作逻辑
无需重复创建Manager对象(复用即可),仅在每次提交后重置BulkWrite对象即可,减少不必要的资源开销。
3. 检查单条更新数据大小
如果$aXML包含较大内容(比如大段XML数据),单条更新的BSON体积会增加,此时需要进一步降低批量大小(比如5万条),确保批量总数据量不接近2GB限制。
修正后的代码示例
$manager = new \MongoDB\Driver\Manager('mongodb://localhost:27017'); $bulk = new \MongoDB\Driver\BulkWrite(["ordered" => false]); $batchSize = 100000; // 设置为10万条一批 $count = 0; while (is_object($richiesta = $richieste->fetch_object())) { // ... 处理$dati和$aXML的逻辑 $bulk->update( ['_id' => $dati['id_mov_contab']], $aXML, ['multi' => false, 'upsert' => true] ); $count++; // 达到批量大小则提交并重置 if ($count % $batchSize === 0) { $manager->executeBulkWrite('catasto.catasto', $bulk); $bulk = new \MongoDB\Driver\BulkWrite(["ordered" => false]); } } // 处理剩余未提交的少量数据 if ($bulk->count() > 0) { $manager->executeBulkWrite('catasto.catasto', $bulk); }
4. 可选:分批查询源数据
如果源数据($richieste)是从数据库查询的结果,可以改为分批查询(比如每次取10万条),避免一次性加载大量数据到内存,进一步降低整体内存占用。
内容的提问来源于stack exchange,提问作者caiofior
相关产品推荐
相关产品推荐

