带有子菜单的大量性能下降渲染菜单

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 无关

以上是关于带有子菜单的大量性能下降渲染菜单的主要内容,如果未能解决你的问题,请参考以下文章

带有多个子菜单下拉菜单的 jQuery 导航菜单关闭父菜单项

为带有子菜单的移动菜单添加关闭功能

带有悬停子菜单的固定菜单有点关闭

Flutter Web:需要带有子菜单的菜单

带有悬停垂直子菜单的 CSS 水平菜单

带有 jQ​​uery 条件的垂直菜单/子菜单