【问题标题】:Parse Large XML File in PHP Efficiently to Generate SQL在 PHP 中高效地解析大型 XML 文件以生成 SQL
【发布时间】:2015-12-28 01:21:24
【问题描述】:

我正在尝试解析一个大型 XML 文件并将其加载到 MySQL 中。我已经使用simplexml 来解析它,它运行良好,但是对于这个大型 XML 文件来说速度很慢。现在我正在尝试使用XMLReader

这是 XML 的示例:

<?xml version="1.0" encoding="UTF-8"?>

<drug type="biotech" created="2005-06-13" updated="2015-02-23">
<drugbank-id primary="true">DB00001</drugbank-id>
<drugbank-id>BIOD00024</drugbank-id>
<drugbank-id>BTD00024</drugbank-id>
<name>Lepirudin</name>
<description>Lepirudin is identical </description>
<cas-number>120993-53-5</cas-number>
<groups>
  <group>approved</group>
</groups>
<pathways>
<pathway>
  <smpdb-id>SMP00278</smpdb-id>
  <name>Lepirudin Action Pathway</name>
  <drugs>
    <drug>
      <drugbank-id>DB00001</drugbank-id>
      <name>Lepirudin</name>
    </drug>
    <drug>
      <drugbank-id>DB01373</drugbank-id>
      <name>Calcium</name>
    </drug>
  </drugs>
...
</drug>

<drug type="biotech" created="2005-06-15" updated="2015-02-25">
...
</drug>

这是我使用simplexml的方法:

<?php

$xml = simplexml_load_file('drugbank.xml');

$servername = "localhost"; // Example : localhost
$username   = "root";
$password   = "pass";
$dbname     = "dbname";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
} 

$xmlObject_count  = $xml->drug->count();

for ($i=0; $i < $xmlObject_count; $i++) {
    $name = $xml->drug[$i]->name;
    $description  = $xml->drug[$i]->description;
    $casnumber = $xml->drug[$i]->{'cas-number'};

    // ...

    $created = $xml->drug[$i]['created'];
    $updated = $xml->drug[$i]['updated'];
    $type = $xml->drug[$i]['type'];


    $sql = "INSERT INTO `drug` (name, description,cas_number,created,updated,type) 
VALUES ('$name', '$description','$casnumber','$created','$updated','$type')";

    if ($conn->query($sql) === TRUE) {
        $last_id = $conn->insert_id;
    } else {
        echo "outer else Error: " . $sql . "<br>" . $conn->error. "<br>" ;
    }
}

$conn->close();

它工作正常,它给了我 7,789 行。但是,我想使用XMLReader 来解析它。但是XMLReader 的问题我发现它提供了超过 35,000 行。

如果您查看 XML,您可以看到在 &lt;drug /&gt; 节点内还有一些其他的 &lt;drugs&gt;&lt;drug&gt; 子节点。我该如何克服这个问题?

这是我使用XMLReader 的过程:

<?php

$servername = "localhost"; // Example : localhost
$username   = "root";
$password   = "pass";
$dbname     = "dbname";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
} 

$reader = new XMLReader();
$reader->open('drugbank.xml');
while ($reader->read())
{
    if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'drug')
    {
        $doc = new DOMDocument('1.0', 'UTF-8');
        $xml = simplexml_import_dom($doc->importNode($reader->expand(),true));

        $name = $xml->name;
        $description  = $xml->description;
        $casnumber = $xml->{'cas-number'};

        // ...

        $sql = "INSERT INTO `drug` (name, description,cas_number,created,updated,type) 
VALUES ('$name', '$description','$casnumber','$created','$updated','$type')";

        if ($conn->query($sql) === TRUE) {
            $last_id = $conn->insert_id;
        } else {
            echo "outer else Error: " . $sql . "<br>" . $conn->error. "<br>" ;
        }
    }
}

$conn->close();

在这个例子中,我发现它提供了超过 35,000 行。

【问题讨论】:

  • 使用 PHP 解析大型 XML 文件是个坏主意

标签: php performance simplexml xmlreader


【解决方案1】:

好的,我有一个工作示例给你,它在执行速度、内存使用和数据库负载方面有了很大的提高:

<?php
define('INSERT_BATCH_SIZE', 500);
define('DRUG_XML_FILE', 'drugbank.xml');

$servername = "localhost"; // Example : localhost
$username   = "root";
$password   = "pass";
$dbname     = "dbname";

function parseXml($mysql)
{
    $drugs = array();

    $xmlReader = new XMLReader();
    $xmlReader->open(DRUG_XML_FILE);

    // Move our pointer to the first <drug /> element.
    while ($xmlReader->read() && $xmlReader->name !== 'drug') ;

    $drugCount = 0;
    $totalDrugs = 0;

    // Iterate over the outer <drug /> elements.
    while ($xmlReader->name == 'drug')
    {
        // Convert the node into a SimpleXMLElement for ease of use.
        $item = new SimpleXMLElement($xmlReader->readOuterXML());

        $name = $item->name;
        $description = $item->description;
        $casNumber = $item->{'cas-number'};
        $created = $item['created'];
        $updated = $item['updated'];
        $type = $item['type'];

        $drugs[] = "('$name', '$description','$casNumber','$created','$updated','$type')";
        $drugCount++;
        $totalDrugs++;

        // Once we've reached the desired batch size, insert the batch and reset the counter.
        if ($drugCount >= INSERT_BATCH_SIZE)
        {
            batchInsertDrugs($mysql, $drugs);
            $drugCount = 0;
        }

        // Go to next <drug />.
        $xmlReader->next('drug');
    }

    $xmlReader->close();

    // Insert the leftovers from the last batch.
    batchInsertDrugs($mysql, $drugs);

    echo "Inserted $totalDrugs total drugs.";
}

function batchInsertDrugs($mysql, &$drugs)
{
    // Generate a batched INSERT statement.
    $statement = "INSERT INTO `drug` (name, description, cas_number, created, updated, type) VALUES";
    $statement = $statement . ' ' . implode(",\n", $drugs);

    echo $statement, "\n";

    // Run the batch INSERT.
    if ($mysql->query($statement))
    {
        echo "Inserted " . count($drugs) . " drugs.";
    }
    else
    {
        echo "INSERT Error: " . $statement . "<br>" . $mysql->error. "<br>" ;
    }

    // Clear the buffer.
    $drugs = array();
}

// Create MySQL connection.
$mysql = new mysqli($servername, $username, $password, $dbname);
if ($mysql->connect_error)
{
    die("Connection failed: " . $mysql->connect_error);
}

parseXml($mysql);

我使用same dataset 测试了这个示例。 以您现在的方式使用 SimpleXML 会导致在内存中解析整个文档,这很慢且占用大量内存。这种方法使用XMLReader,这是一个快速的拉解析器。您仍然可以使用PHP SAX XML Parser 来加快速度,但它的模式有点复杂,上面的示例会明显比您开始使用的更好。

我的示例中的另一个重大变化是我们使用了 MySQL 批量插入,因此我们实际上只在处理每个 500(可配置)项目时访问数据库。您可以调整此数字以获得更好的性能。在某个时间点之后,查询将变得太大而 MySQL 无法处理,但您一次可以做的不仅仅是500

如果您希望我进一步解释其中的任何部分,或者您有任何问题,请在 cmets 中告诉我! :)

【讨论】:

  • 感谢您的及时回答...您的解决方案完美运行,比我的要好得多
猜你喜欢
  • 1970-01-01
  • 2015-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-15
  • 1970-01-01
  • 1970-01-01
  • 2011-04-04
相关资源
最近更新 更多