簡體   English   中英

優化PHP代碼以利用大型數據集構建Tree結構

[英]Optimize PHP code to build Tree structure out of large dataset

我有一個程序,可在每個子節點和大子節點旁邊填充最高級別的節點或父節點。

我首先制作了一個樹結構,然后進行解析以填充每個子級/孫級節點旁邊的最高級別節點或根節點。

但是,當我在大於20000行的大數據集上運行程序時,出現此錯誤:

Fatal error:  Out of memory (allocated 1807745024) (tried to allocate 36 bytes) in C:\xampp\htdocs\test_project\index.php on line 20

這是我的代碼:

<?php

include('mysql_config.php');

set_time_limit(0);
ini_set('memory_limit', '2048M');
$r = mysql_query("SELECT Emp_ID AS id,fname AS name,Manager_ID AS parent_id FROM targets");
        $data = array();
        while($row = mysql_fetch_assoc($r)) {
         $data[] = $row;
         }    
$j = mysql_query("SELECT Emp_ID AS id,fname AS name,Manager_ID AS parent_id FROM targets where Type = 'Super Manager'");
        $parent_data = array();
        while($row = mysql_fetch_assoc($j)) {
         $parent_data[] = $row;
         }           

function buildtree($src_arr, $parent_id = 0, $tree = array())
{
    foreach($src_arr as $idx => $row)
    {
        if($row['parent_id'] == $parent_id)
        {
            foreach($row as $k => $v)
                $tree[$row['id']][$k] = $v;
            unset($src_arr[$idx]);
            $tree[$row['id']]['children'] = buildtree($src_arr, $row['id']);
        }
    }
    ksort($tree);
    return $tree;
}

function fetch_recursive($tree, $parent_id, $parentfound = false, $list = array())
{
    foreach($tree as $k => $v)
    {
        if($parentfound || $k == $parent_id)
        {
            $rowdata = array();
            foreach($v as $field => $value)
                if($field != 'children')
                    $rowdata[$field] = $value;
            $list[] = $rowdata;
            if($v['children'])
                $list = array_merge($list, fetch_recursive($v['children'], $parent_id, true));
        }
        elseif($v['children'])
            $list = array_merge($list, fetch_recursive($v['children'], $parent_id));
    }
    return $list;
}
foreach($parent_data as $value)
{ 
echo '<pre>';
$result_data = fetch_recursive(buildtree($data),(int)$value['id']);
print_r($result_data);
echo '</pre>';
  if(!empty($result_data)){
   foreach($result_data as $child_val){
     $su_id=(int)$value['id'];
     $name_man=(string)$value['name'];
     $dest_id=$child_val['id'];
       mysql_query("update targets set SM_ID ='$su_id',SM_Name='$name_man' where Emp_ID='$dest_id'") or die (mysql_error());
     }
    }
}

?>

我如何優化代碼以解決此錯誤。 我用100行嘗試了這段代碼,並且運行良好。

原始問題陳述

我的數據庫中有以下數據:

Manager_ID Employee_ID

AAA   BBB
AAA   CCC
AAA   DDD
BBB   EEE
BBB   FFF
CCC   GGG
FFF   HHH
III   JJJ
JJJ   KKK
JJJ   LLL

我希望用各自的最高級別根節點填充子節點,以便所有子節點都具有映射到它們的根級別數據/父節點,如下所示:

Employee_ID 1st Level Node
AAA         Root
BBB         AAA
CCC         AAA
DDD         AAA
EEE         AAA
FFF         AAA
GGG         AAA
HHH         AAA
III         Root
JJJ         III
KKK         III
LLL         III

我嘗試創建PHP函數來創建樹,但是無法從那里獲取它以將最后或最高級別的根填充到各個子節點。

經驗法則:對於每個標量值,PHP都有40字節的開銷。 這加起來很快。

不要使用不推薦使用的mysql_*接口; 使用mysqli_*PDO

解決問題的方法:將所有數據放入臨時MySQL表中,然后使用SQL語句重新排列為分層格式。 啊; 您似乎已經在表中有數據。

SQL將在層次結構的每個級別包含一個SQL語句,可能在一個語句中執行該級別的所有記錄。 PHP代碼將控制級別,因為MySQL沒有層次結構的概念。

這種變化甚至可能比PHP更快地運行-您最終將得到少量查詢,而不是當前設計的20000(或更多)查詢。

這是一種轉換代碼的方法。 從最里面的for循環開始。 考慮一下如何將循環變成一條 SQL語句。 然后去做 (這一步過后,您可能會很高興。)

首先,在表格中為每個孩子的根ID添加一列:

ALTER TABLE targets ADD COLUMN Root_ID INT;

然后將根標識設置為管理員標識:

$sql = "UPDATE targets SET Root_ID = Manager_ID";
if (!$conn->query($sql)) {
    error_log($conn->error);
    die("Database error occurred, see log.");
}

然后將根ID迭代更新為該員工的經理的ID。 當不進行任何更改時,循環將停止,因為不存在其根ID本身具有管理器的子級。 此時,所有記錄的根ID將設置為沒有經理的員工記錄(即根員工)。

$affectedRows = 1;
while ($affectedRows > 0) {
    $sql = "UPDATE targets AS child
        INNER JOIN targets AS parent
          ON child.Root_ID = parent.Emp_ID
        SET child.Root_Id = parent.Manager_ID";
    if (!$conn->query($sql)) {
        error_log($conn->error);
        die("Database error occurred, see log.");
    }
    $affectedRows = $conn->affected_rows;
    echo "Changed $affectedRows rows\n"; // demo only, remove this echo
}

現在,您可以逐行打印出員工的根源,而不必一次存儲所有數據:

$sql = "(SELECT Emp_ID, Root_ID FROM targets)
    UNION
    (SELECT t1.Manager_ID, 'Root' FROM targets AS t1
     LEFT OUTER JOIN targets AS t2 ON t1.Manager_ID = t2.Emp_ID
     WHERE t2.Emp_ID IS NULL)
    ORDER BY Emp_ID";
if (($result = $conn->query($sql, MYSQLI_USE_RESULT)) === false) {
    error_log($conn->error);
    die("Database error occurred, see log.");
}
printf("%-10s %-10s\n", "Emp_ID", "Root_ID");
while ($row = $result->fetch_assoc()) {
    printf("%-10s %-10s\n", $row["Emp_ID"], $row["Root_ID"]);
}
$result->free();

輸出:

Emp_ID     Root_ID   
AAA        Root      
BBB        AAA       
CCC        AAA       
DDD        AAA       
EEE        AAA       
FFF        AAA       
GGG        AAA       
HHH        AAA       
III        Root      
JJJ        III       
KKK        III       
LLL        III       

發表您的評論:

為了對此進行優化,我添加了一個索引PRIMARY KEY (Emp_ID, Manager_ID) 這是我的表定義:

CREATE TABLE `targets` (
  `Manager_Id` char(3) NOT NULL,
  `Emp_Id` char(3) NOT NULL,
  `Root_ID` char(3) DEFAULT NULL,
  PRIMARY KEY (`Emp_Id`,`Manager_Id`)
) ENGINE=InnoDB;

這是UPDATE的查詢EXPLAIN:

*************************** 1. row ***************************
               id: 1
      select_type: UPDATE
            table: child
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 10
         filtered: 100.00
            Extra: Using where
*************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: parent
       partitions: NULL
             type: ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 3
              ref: test.child.Root_ID
             rows: 1
         filtered: 100.00
            Extra: Using index

這是SELECT UNION的說明:

*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: targets
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 2
  select_type: UNION
        table: t1
   partitions: NULL
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 6
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: Using index
*************************** 3. row ***************************
           id: 2
  select_type: UNION
        table: t2
   partitions: NULL
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 3
          ref: test.t1.Manager_Id
         rows: 1
     filtered: 100.00
        Extra: Using where; Not exists; Using index
*************************** 4. row ***************************
           id: NULL
  select_type: UNION RESULT
        table: <union1,2>
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: Using temporary; Using filesort

顯然,無法在內存中重新創建樹。 簡單的自引用表確實是解決在關系數據庫中描述樹的問題的簡單而明顯的解決方案,但是正如您所發現的,它也非常有限。 如果您的數據存儲在鄰接列表中,這將相對容易。

如果我們只有一個字段可以更新,則可以迭代地標記樹,例如...。

 run_query('update yourtable set flag=0');
 foreach ($root_id as $root) {
     $d=1;
     run_query('update yourtable set flag=$d where id=$root');
     do {
        $rows_changed=run_query('update yourtable y1 
           set y1.flag=$d+1
           where y1.parent in (select y2.id from yourtable y2
             where y2.flag=$d');
        ++$d;
     } while ($rows_changed);
     run_query('update yourtable set root=$root, flag=0
        where flag>0');
 }

(注釋代碼未經測試)。

當然,這可以像在mysql過程中那樣實現,它將比混合的php / mysql解決方案運行得更快。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM