带有子菜单的大量性能下降渲染菜单
Posted
技术标签:
【中文标题】带有子菜单的大量性能下降渲染菜单【英文标题】:Massive performance degradation rendering menus with submenus 【发布时间】:2011-09-26 16:06:44 【问题描述】:每当我呈现一个菜单项时,对于我从数据库中找到的每个菜单项,我都会使用它来检查其子菜单。
我的控制器渲染菜单项和递归函数检测其子菜单如下
public function renderAction()
$menu = $this -> _request -> getParam('menu');
$itemArray = $this -> getSubItems($menu);
$container = new Zend_Navigation($itemArray);
$this -> view -> navigation() -> setContainer($container);
private function getSubItems($menu, $parent = 0)
$mapperMenuItem = new Apanel_Model_Mapper_MenuItem();
$menuItems = $mapperMenuItem -> getItemsByMenu($menu, $parent);
if(count($menuItems) > 0)
$itemArray = array();
foreach($menuItems as $item)
$label = $item -> label;
$uri = $this -> getSubItemUrl($item);
$subItems = $this -> getSubItems($menu, $item -> id);
if(count($subItems))
$tArray['pages'] = $subItems;
$tArray['label'] = $label;
$tArray['uri'] = $uri;
$itemArray[] = $tArray;
unset($tArray);
if(count($itemArray))
return $itemArray;
else
return null;
else
return null;
private function getSubItemUrl($item)
if(!empty($item -> link))
$uri = $item -> link;
else
$pageMapper = new Apanel_Model_Mapper_Page();
$details = $pageMapper -> getPageDetails($item -> page_id);
$pageClass = "CMS_Content_Item_".ucwords($details['namespace']);
$page = new $pageClass($item -> page_id);
$title = str_replace(" ", "-", strtolower($details['name']));
$uri = $this -> view -> url(array(
"namespace" => $details['namespace'],
"title" => $title
),'page-view');
return $uri;
MenuItem Mapper 中的 getItemsByMenu 函数
public function getItemsByMenu($menuId, $parent = 0)
$select = $this -> getDbTable() -> select();
$select -> where("menu_id = ?", $menuId)
-> where("parent = ?", $parent)
-> order("position");
$items = $this -> getDbTable() -> fetchAll($select);
if($items -> count() > 0)
return $items;
else
return null;
我的应用程序中呈现了大约 4 种不同类型的菜单,我注意到执行过程中性能显着下降。我经常遇到执行超时,菜单的渲染时间大约为 35 秒,而没有菜单的渲染时间大约为 22 秒。这一切都在本地主机中。我的递归有什么缺陷吗?我可以采取什么措施来提高代码的性能?
【问题讨论】:
能否贴出getItemsByMenu和getSubItemUrl的代码? @TimFountain,我已经更新了我的问题。 【参考方案1】:我看不到任何可以解释 35 秒执行时间的东西,除非您的菜单表中有 100,000 项根本没有索引。建议:
确保您在菜单项表上有一个索引:menu_id, parent, position
(这是三个字段的一个索引,字段按此顺序排列。
我假设getPageDetails
正在执行另一个数据库查询。理想情况下,您希望在加载菜单项时加载这些详细信息(通过加入 pages 表),这样您就可以将页面数据数组传递给 getPageDetails
而不必对每个项目进行额外的查询。
如果这没有带来任何奇迹般的改进,请尝试启用数据库分析器,以便查看导致问题的数据库查询的数量或速度。
【讨论】:
根据您的第一点。如果您指的是该表,我确实有一个索引作为该表中的主键。关于您的第二点,如何执行带有连接的自定义查询? 如果你只有一个主键索引,那么你肯定需要按照我的第一个建议添加一个额外的索引——这应该会有很大帮助。至于自定义连接,您可以在选择对象上使用joinInner
来拉入一个额外的表,尽管这对于 Zend_Db_Table 可能有点奇怪,所以请先尝试索引。
我以前没有使用过索引....您能否用一个可能的示例详细说明您的第一个示例。我在每个字段上都设置了索引类型为index
的索引。
一次性运行:ALTER TABLE yourtable ADD INDEX menuParentPosition (menu_id, parent, position )
将“yourtable”替换为数据库表名。这将创建我建议的索引,所以看看之后你是否看到任何改进。
应用了你的所有建议,现在 DOM 在 10 秒内完成加载,渲染在大约 15 秒内完成。那是相当不同的。谢谢【参考方案2】:
这里明显的问题是您从数据库中获取菜单的方式。
如果您为每个菜单项请求获取它的子菜单,您将很快以 大量 请求结束。一个简单的解决方案是实现缓存,但您可以先尝试改进查询菜单的方式。
映射树的一个很好的替代方法是使用materialized path,而不是引用父项。这意味着您在字段中存储一个字符串,该字符串包含当前项目的路径,以逗号分隔。真正的好处是,您可以在路径字段上使用正则表达式在一个请求中获得一整棵树:
//get the whole tree of menu 1
SELECT * FROM menuitems WHERE path REGEXP '^1' ORDER BY path;
| ID | Name | Path |
| 1 | Hardware | "1" |
| 2 | Printers | "1,1" |
| 3 | Laser printers | "1,1,1"|
| 4 | Ink printers | "1,1,2"|
| 5 | Screens | "1,2" |
| 6 | Flat Screens | "1,2,1"|
| 7 | Touch Screens | "1,2,1"|
然后使用一些代码,例如一些递归函数,您可以构建整个导航。
并考虑缓存这类东西
【讨论】:
您提供的链接不包含任何文章。 是的,抱歉,唯一与任何技术无关的资源。你在这里have an explanation for mongoDb,但原理是一样的。不同之处在于路径与行 ID 无关以上是关于带有子菜单的大量性能下降渲染菜单的主要内容,如果未能解决你的问题,请参考以下文章