[英]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.