2013-02-06 23 views
3

我有一個任務,我需要解析一個非常大的文件,並將結果寫入mysql數據庫。 「非常大」意味着我們正在討論約1.4GB的CSV數據,總計約1000萬行文本。解析一個非常大的文件到mysql

事情不是「怎麼做」,但如何做到這一點快。我的第一個方法是在沒有任何速度優化的情況下使用php,然後讓它運行幾天直到完成。不幸的是,它現在已經運行了48小時,並且只處理了總文件的2%。因此,這不是一種選擇。

文件格式如下:

A:1,2 

其中逗號的量隔開的數字繼「:」可以是0-1000。該示例數據集必須進入一個表,如下所示:

| A | 1 | 
| A | 2 | 

所以現在,我沒有這樣說:

$fh = fopen("file.txt", "r"); 

$line = ""; // buffer for the data 
$i = 0; // line counter 
$start = time(); // benchmark 

while($line = fgets($fh)) 
{ 
    $i++;  
    echo "line " . $i . ": "; 

    //echo $i . ": " . $line . "<br>\n"; 

    $line = explode(":", $line); 

    if(count($line) != 2 || !is_numeric(trim($line[0]))) 
    { 
     echo "error: source id [" . trim($line[0]) . "]<br>\n"; 
     continue; 
    } 

    $targets = explode(",", $line[1]); 

    echo "node " . $line[0] . " has " . count($targets) . " links<br>\n"; 

    // insert links in link table 
    foreach($targets as $target) 
    { 
      if(!is_numeric(trim($target))) 
      { 
       echo "line " . $i . " has malformed target [" . trim($target) . "]<br>\n"; 
       continue; 
      } 

      $sql = "INSERT INTO link (source_id, target_id) VALUES ('" . trim($line[0]) . "', '" . trim($target) . "')"; 
      mysql_query($sql) or die("insert failed for SQL: ". mysql_error()); 
     } 
} 

echo "<br>\n--<br>\n<br>\nseconds wasted: " . (time() - $start); 

這顯然是不以任何方式速度進行了優化。任何提示重新開始?我應該換用另一種語言嗎?

+4

第一我腦子裏的事情就是使用'MySQLi'或'PDO',這樣你就可以利用準備好的語句。 – Passerby

+1

如果你的輸入數據是一個CSV文件,也許你可以使用LOAD DATA INFILE,更多信息:http://dev.mysql.com/doc/refman/5.0/en/load-data.html – m4t1t0

回答

2

第一個優化是插入一個事務 - 每個100或1000行提交併開始一個新的事務。顯然你必須使用支持事務的存儲引擎。

然後通過top命令觀察CPU使用情況 - 如果您有多個內核,則mysql進程沒有太多工作,並且PHP進程執行大部分工作,重寫腳本以接受從參數跳過n行的參數開始時只能輸入10000行左右。然後啓動腳本的多個實例,每個實例都有不同的起點。

第三種解決方案是將文件轉換爲帶有PHP的CSV(根本不寫入,只是寫入文件),並使用LOAD DATA INFILE作爲m4t1t0的建議。

+0

不幸的是,這是運行在沒有太多資源的虛擬機上。所以我決定從一開始就跳過第二個解決方案。無論如何,第一個解決方案工作得很好。看到我的帖子下面... – xenonite

0

我發現你的描述相當混亂 - 它與你提供的代碼不匹配。

如果(!計數($線)= 2 || is_numeric(修剪($行[0])))

微調這裏是多餘的 - 空白不改變行爲is_numberic。但是你已經說過,這條線的開頭是一個字母 - 因此這總是會失敗。

如果你想加快速度,那麼切換到使用輸入流處理而不是消息處理(PHP數組可能非常慢),或者使用不同的語言並將插入語句聚合到多行插入中。

+0

對不起,從一開始就沒有說清楚:一切都在數字! A,B也是數字。 – xenonite

0

我會先用腳本創建一個SQL文件。然後通過在SQL文件的開始/結尾處放置適當的命令(可以讓腳本執行此操作),使用此http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html來鎖定表。

然後,只需使用command工具將SQL注入數據庫(最好在數據庫所在的機器上)。

+0

好吧,這將再次導致創建另一個huuuuuuuge - 甚至更大的文件,必須寫入數據庫。顯然然後沒有解析,但是... – xenonite

1

按照承諾,附上你會發現我在這篇文章中的解決方案。我基準它,它橫空出世,它比舊:) 確保40倍的速度(!) - 還是有很大的優化空間,但它的速度不夠快,我現在:)

$db = mysqli_connect(/*...*/) or die("could not connect to database"); 

$fh = fopen("data", "r"); 

$line = "";    // buffer for the data 
$i = 0;     // line counter 
$start = time();  // benchmark timer 
$node_ids = array(); // all (source) node ids 

mysqli_autocommit($db, false); 

while($line = fgets($fh)) 
{ 
$i++; 

echo "line " . $i . ": "; 

$line = explode(":", $line); 
$line[0] = trim($line[0]); 

if(count($line) != 2 || !is_numeric($line[0])) 
{ 
    echo "error: source node id [" . $line[0] . "] - skipping...\n"; 
    continue; 
} 
else 
{ 
    $node_ids[] = $line[0]; 
} 

$targets = explode(",", $line[1]); 

echo "node " . $line[0] . " has " . count($targets) . " links\n"; 

// insert links in link table 
foreach($targets as $target) 
{ 
    if(!is_numeric($target)) 
    { 
     echo "line " . $i . " has malformed target [" . trim($target) . "]\n"; 
     continue; 
    } 

    $sql = "INSERT INTO link (source_id, target_id) VALUES ('" . $line[0] . "', '" . trim($target) . "')"; 
    mysqli_query($db, $sql) or die("insert failed for SQL: ". $db::error); 
} 

if($i%1000 == 0) 
{ 
    $node_ids = array_unique($node_ids); 
    foreach($node_ids as $node) 
    { 
     $sql = "INSERT INTO node (node_id) VALUES ('" . $node . "')"; 
     mysqli_query($db, $sql); 
    } 
    $node_ids = array(); 

    mysqli_commit($db); 
    mysqli_autocommit($db, false); 
    echo "committed to database\n\n"; 
} 
} 

echo "<br>\n--<br>\n<br>\nseconds wasted: " . (time() - $start); 
+1

進一步的優化將包括:a)編譯包含多個數據記錄的sql插入語句(這現在相當不理想......)b)使用預處理語句c)運行該腳本並行地解析文件的不同部分 – xenonite