PHP多并发XML写入异常解决及脚本调用排队实现
问题:多用户同时调用XML修改函数导致文件内容丢失
我在自定义WordPress插件中使用一段PHP脚本打开XML文档、获取DOM并修改,单独运行正常,但多用户或同一用户同时调用该函数时,XML文件会丢失所有元素,仅保留<?xml version="1.0" encoding="utf-8"?>。想知道这是否和PHP函数的异步特性有关?该怎么解决?是否需要对函数调用排队?具体怎么实现?
原代码:
function ym_update_product_xml($p_id, $p_name, $p_status, $p_exerpt, $p_price, $p_inventory){ if($p_id){ $xml = new DOMDocument('1.0', 'utf-8'); $xml->formatOutput = true; $xml->preserveWhiteSpace = false; $xml->load(YDE_INC . 'all_products.xml'); $ym_all_products = $xml->documentElement; $xpath = new DOMXPath($xml); $_product_name = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_name'); $_product_status = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_status'); $_product_exerpt = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_exerpt'); $_product_price = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_price'); $_product_invt = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_invt'); if( $p_name != null ){$_product_name->item(0)->nodeValue = $p_name;} if( $p_status != null ){$_product_status->item(0)->nodeValue = $p_status;} if( $p_exerpt != null ){$_product_exerpt->item(0)->nodeValue = $p_exerpt;} if( $p_price != null ){$_product_price->item(0)->nodeValue = $p_price;} if( $p_inventory != null ){$_product_invt->item(0)->nodeValue = $p_inventory;} $xml->save(YDE_INC . 'all_products.xml'); return true; }else{ return false; } }
解决方案
原因分析
这不是PHP异步特性的问题,核心是文件并发读写冲突:
- 多个请求同时加载XML到内存
- 其中一个请求先修改并覆盖原文件
- 另一个请求拿着旧的内存数据再覆盖文件,直接冲掉之前的修改,甚至因为同时写入导致文件结构损坏,只剩XML声明。
核心解决办法:给文件加排他锁
用PHP的flock()函数实现文件排他锁,确保同一时间只有一个进程能读写XML文件,不需要复杂的调用排队。
修改后的代码
function ym_update_product_xml($p_id, $p_name, $p_status, $p_exerpt, $p_price, $p_inventory){ if(!$p_id){ return false; } $xml_file = YDE_INC . 'all_products.xml'; // 打开文件获取读写指针 $fp = fopen($xml_file, 'r+'); if(!$fp){ return false; } // 获取排他锁(阻塞模式,直到拿到锁才继续) if(flock($fp, LOCK_EX)){ $xml = new DOMDocument('1.0', 'utf-8'); $xml->formatOutput = true; $xml->preserveWhiteSpace = false; // 通过文件指针加载XML,避免重复打开文件 $xml->load($fp); $xpath = new DOMXPath($xml); $_product_name = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_name'); $_product_status = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_status'); $_product_exerpt = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_exerpt'); $_product_price = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_price'); $_product_invt = $xpath->query('/ym_all_products/product[@id="' . $p_id . '"]/product_invt'); // 增加节点存在判断,避免空指针报错 if($p_name != null && $_product_name->length > 0){ $_product_name->item(0)->nodeValue = $p_name; } if($p_status != null && $_product_status->length > 0){ $_product_status->item(0)->nodeValue = $p_status; } if($p_exerpt != null && $_product_exerpt->length > 0){ $_product_exerpt->item(0)->nodeValue = $p_exerpt; } if($p_price != null && $_product_price->length > 0){ $_product_price->item(0)->nodeValue = $p_price; } if($p_inventory != null && $_product_invt->length > 0){ $_product_invt->item(0)->nodeValue = $p_inventory; } // 清空原文件内容后写入新XML fseek($fp, 0); ftruncate($fp, 0); $xml->save($fp); // 释放锁并关闭文件 flock($fp, LOCK_UN); fclose($fp); return true; }else{ // 获取锁失败,关闭文件返回错误 fclose($fp); return false; } }
关键修改说明
- 用
fopen()获取文件指针,后续所有操作通过指针完成,避免重复打开文件 flock($fp, LOCK_EX)获取排他锁,阻塞模式下会等待直到拿到锁,确保单进程操作- 保存前用
fseek()和ftruncate()清空原文件,避免残留旧内容 - 增加节点存在判断,防止找不到对应节点时报错
- 操作完成后必须释放锁并关闭文件,避免死锁
高并发场景补充
如果你的插件并发量极高,文件锁可能导致请求等待时间过长,这时可以用WordPress的后台队列(比如WP Cron或自定义队列系统),把XML修改任务放到队列中,由后台进程依次执行。但对于绝大多数场景,文件锁已经足够解决问题,实现成本更低。
内容的提问来源于stack exchange,提问作者Web Design
相关产品推荐
相关产品推荐

