如何用PHP XMLReader解析XML并去重插入MySQL表?
问题:XMLReader解析嵌套XML插入MySQL时重复数据过多
我有如下结构的XML文件:
<?xml version="1.0" encoding="UTF-8"?> <products currency="EUR"> <product id="457654754" vat="13.0"> <price gross="0.09" net="0.07"/> <sizes> <size id="0" name="gfdgfdgfdg" panel_name="dfgfdgfdgfd" code_producer="fgdfgfdg" iaiext:code_external="457547547547" code="354643643643" weight="4" iaiext:weight_net="10" > <stock id="1" quantity="333"/> </size> </sizes> </product> </products>
用PHP的XMLReader解析时能正确输出属性值,但插入MySQL时出现大量重复数据(预期2500行,实际48000行)。解析代码如下:
<?php $xml = new XMLReader; $xml->open('XML_URL'); while ($xml->read()) { if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'product') echo $xml->getAttribute('id').'<br />'; if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'price') echo $xml->getAttribute('net').'<br />'; if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'size') echo $xml->getAttribute('iaiext:code_external').'<br />'; if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'stock') echo $xml->getAttribute('quantity').'<br />'; } ?>
MySQL表结构:
CREATE TABLE `table` ( `auto_increm` int(11) NOT NULL, `un_id` int(11) NOT NULL, `price` decimal(15,2) NOT NULL, `quantity` int(11) NOT NULL, `ean` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; ALTER TABLE `table` ADD PRIMARY KEY (`auto_increm`); ALTER TABLE `table` MODIFY `auto_increm` int(11) NOT NULL AUTO_INCREMENT; COMMIT;
属性映射规则:
- XML中
product的id→ MySQL的un_id - XML中
price的net→ MySQL的price - XML中
size的iaiext:code_external→ MySQL的ean - XML中
stock的quantity→ MySQL的quantity
之前处理扁平XML时插入正常,但嵌套XML就出问题了,怎么解决重复插入的问题?
问题根源
之前的代码在XMLReader循环中,每遇到一个匹配元素(如product、price、size、stock)就单独处理,没有把属于同一组的数据关联起来。同时XMLReader会遍历所有节点(包括文本节点、结束标签等),导致多次触发插入逻辑,产生大量重复数据。
解决方法
需要把同一product下的price、size、stock数据关联起来,仅在完整收集到一组数据后执行一次插入。同时精准定位节点,跳过无效遍历。
优化后的代码
<?php // 数据库连接(替换为你的实际参数) $conn = mysqli_connect('localhost', 'username', 'password', 'database_name'); if (!$conn) { die('数据库连接失败: ' . mysqli_connect_error()); } $xml = new XMLReader(); $xml->open('XML_URL'); // 存储当前产品的临时数据 $currentProduct = []; while ($xml->read()) { // 只处理元素节点,跳过文本、结束标签等 if ($xml->nodeType !== XMLReader::ELEMENT) { continue; } // 读取product节点,开始收集数据 if ($xml->name === 'product') { $currentProduct['un_id'] = $xml->getAttribute('id'); // 定位price节点,获取net价格 $xml->read(); while ($xml->nodeType !== XMLReader::ELEMENT || $xml->name !== 'price') { if (!$xml->read()) break; } if ($xml->name === 'price') { $currentProduct['price'] = $xml->getAttribute('net'); } // 定位sizes节点,遍历内部的size while ($xml->nodeType !== XMLReader::ELEMENT || $xml->name !== 'sizes') { if (!$xml->read()) break; } if ($xml->name === 'sizes') { $xml->read(); // 遍历sizes下的所有size节点 while ($xml->nodeType !== XMLReader::END_ELEMENT || $xml->name !== 'sizes') { if ($xml->nodeType === XMLReader::ELEMENT && $xml->name === 'size') { $currentProduct['ean'] = $xml->getAttribute('iaiext:code_external'); // 定位当前size下的stock节点 $xml->read(); while ($xml->nodeType !== XMLReader::ELEMENT || $xml->name !== 'stock') { if (!$xml->read()) break; } if ($xml->name === 'stock') { $currentProduct['quantity'] = $xml->getAttribute('quantity'); // 收集完完整数据,执行插入(用预处理语句防注入) $stmt = mysqli_prepare($conn, "INSERT INTO `table` (un_id, price, ean, quantity) VALUES (?, ?, ?, ?)"); mysqli_stmt_bind_param($stmt, 'dssi', $currentProduct['un_id'], $currentProduct['price'], $currentProduct['ean'], $currentProduct['quantity']); mysqli_stmt_execute($stmt); mysqli_stmt_close($stmt); } } // 移动到下一个节点 if (!$xml->read()) break; } } } } $xml->close(); mysqli_close($conn); ?>
关键改进点
- 数据关联收集:将同一product下的所有关联数据存入临时数组,仅在集齐完整数据后执行一次插入,避免重复提交。
- 精准节点遍历:通过循环条件定位目标节点,跳过无关的文本、结束标签等节点,减少无效遍历次数。
- 预处理语句:使用
mysqli_prepare绑定参数,既防止SQL注入,又保证数据类型与表结构匹配。 - 避免重复触发:不再单独处理每个元素节点,而是在product节点内部一次性完成子节点读取与数据整合。
额外去重保障
如果XML本身存在重复数据,或需要避免插入表中已有的数据,可给表添加唯一约束:
-- 假设un_id+ean是唯一标识组合 ALTER TABLE `table` ADD UNIQUE KEY `unique_product_size` (`un_id`, `ean`);
然后修改插入语句,选择忽略重复或更新现有数据:
- 忽略重复:
INSERT IGNORE INTO ... - 更新现有数据:
$stmt = mysqli_prepare($conn, "INSERT INTO `table` (un_id, price, ean, quantity) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE price=VALUES(price), quantity=VALUES(quantity)");
内容的提问来源于stack exchange,提问作者qwertyg
相关产品推荐
相关产品推荐

