我有一个程序来填充最高级别节点或父节点,以便在每个子节点和孙子节点旁边填充。
我首先做了一个树结构,然后通过解析填充每个子/孙子节点旁边的最高级别节点或根节点。
但是当我在大数据集>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语句。那就去做吧。(这一步之后你可能会很开心)
显然在内存中重新创建树不起作用。对于在关系数据库中描述树的问题,简单的自引用表确实是一个简单而明显的解决方案,但是正如您所发现的,它也非常有限。如果您的数据存储在邻接表中,这将相对容易。
如果我们有一个可以更新的字段,那么可以迭代地标记树,例如....
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混合解决方案运行得稍微快一些。
首先为每个子节点的根ID添加一列:
ALTER TABLE targets ADD COLUMN Root_ID INT;
然后将根ID设置为管理员的ID:
$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的EXPLAIN:
*************************** 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