谁能一眼告诉我为什么这个 Action 这么慢?

发布于 2024-09-19 17:00:20 字数 7240 浏览 7 评论 0原文

起初并没有注意到这个东西很慢,因为数据库中没有很多“树节点”。现在确实慢了,一看这有什么大问题吗?我真的需要优化它,在重新设计整个事情之前,我想知道是否有什么突出的地方是真正的痛点。我已经将缓慢的部分缩小到递归存储库函数,这是这篇文章的最后一件事,但我必须定义一些导致它的事情......祝你好运。 (注意:我没有写这个,我只是被置于损害控制状态,我正在努力理解它。)

首先需要理解一些事情:

JsTreeNode 定义:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace App.Models
{
    public class JsTreeNode
    {
        public JsTreeNode()
        {
        }

        public Attributes attributes { get; set; }
        public Data data { get; set; }
        public string state { get; set; }
        public List<JsTreeNode> children { get; set; }
    }

    public class Attributes
    {
        public string id { get; set; }
        public string rel { get; set; }
        public string mdata { get; set; }
        public string href { get; set; }
        public string complete { get; set; }
        public string edit { get; set; }
        public string title { get; set; }
        public string resourceAccountID { get; set; }
    }

    public class Data
    {
        public string title {get;set;}
        public string icon {get;set;}
    } 
}

现在 TreeNodes 表定义:

CREATE TABLE [dbo].[TreeNodes](
    [TreeNodeID] [int] IDENTITY(1,1) NOT NULL,
    [TreeID] [int] NOT NULL,
    [ParentNodeID] [int] NULL,
    [ResourceID] [int] NULL,
    [NodeOrder] [int] NOT NULL,
    [NodeName] [nvarchar](250) NOT NULL,
    [CreateBy] [int] NULL,
    [CreateDate] [datetime] NULL,
    [ModifyBy] [int] NULL,
    [ModifyDate] [datetime] NULL,
    [TreeRevisionID] [int] NULL,

树修订表定义...

CREATE TABLE [dbo].[TreeRevisions](
    [TreeRevisionID] [int] IDENTITY(1,1) NOT NULL,
    [TreeID] [int] NOT NULL,
    [Notes] [text] NULL,
    [CreatedBy] [int] NOT NULL,
    [CreatedDate] [datetime] NOT NULL,
    [ModifyBy] [int] NOT NULL,
    [ModifyDate] [datetime] NOT NULL,

现在这是 Get Action 本身,需要 20-25 秒,您可以在其中看到它调用我在下面进一步定义的存储库函数这个线程。

public ContentResult Get(string target,int id)
        {
            int revisionID = Convert.ToInt32(Request.QueryString["revisionID"]);
            int tempUserID = (Request.QueryString["userID"] != null)
                                 ? Convert.ToInt32(Request.QueryString["userID"])
                                 : UserID;

            var nodesList = new List<JsTreeNode>();
            if(target.Contains("tree"))
            {
                tree tree = _treeRepository.GettreeByID(id);

                var cnode = new JsTreeNode
                                       {
                                           attributes = new Attributes {id = "0",title = tree.treeName},
                                           data = new Data {title = tree.treeName},
                                           children = _treeNodesRepository.GetNodesBytreeID(id, null,revisionID),
                                           state = "open"
                                       };
                cnode.attributes.rel = "root";
                cnode.attributes.mdata = "{draggable : true}";
                nodesList.Add(cnode);
            }
            else
            {

                var trees = _CategoryRepository.getAlltreesByCategoryID(id);

                IQueryable<tree> custom;

                if(revisionID != 0)
                {
                    custom = from c in trees
                              where
                                  c.AccountID == AccountID
                              select c;
                } else
                {
                    custom = from c in trees
                             where
                                 c.AccountID == AccountID && c.PublishedRevisionID != null && c.PublishedRevisionID != 0
                             select c;
                }

                var acme = from c in trees where c.AccountID == acmeContent.acmeID select c;

                foreach (var tree in (custom.Count() > 0) ? custom : acme)
                {
                    if(revisionID == 0 && tree.PublishedRevisionID == null) continue;
                    int tempRev = (revisionID != 0) ? revisionID : (int)tree.PublishedRevisionID;

                    if(custom.Count() == 1)
                    {
                        var tempNodes = _treeNodesRepository.GetNodesBytreeID(tree.treeID, null, tempUserID, tempRev);
                        nodesList.AddRange(tempNodes);
                    }
                    else
                    {
                        var cnode = new JsTreeNode();
                        cnode.attributes = new Attributes { id = tree.treeID.ToString(), title = tree.treeName };
                        cnode.data = new Data { title = tree.treeName };
                        cnode.children = _treeNodesRepository.GetNodesBytreeID(tree.treeID, null, tempUserID, tempRev);
                        cnode.attributes.rel = "Folder";

                        nodesList.Add(cnode);
                    }
                }
            }

            var ser = new JavaScriptSerializer();
            string res = ser.Serialize(nodesList);

            return Content(res,"application/json");

        }

...最后,“罪魁祸首”,看看它如何称呼自己:

public List<JsTreeNode> GetNodesByTreeID(int TreeID, int? parentID,int userID, int revisionID)
        {

            IQueryable<UserTreeNode> TreeNodes = GetAllTreeNodesByTreeIDAndParentNodeID(TreeID, parentID,userID,revisionID);
            List<JsTreeNode> nodesList = new List<JsTreeNode>();



            foreach (UserTreeNode node in TreeNodes)
            {
                string nodeName = node.Node.NodeName.Replace("'", "&#39;");

                JsTreeNode cnode = new JsTreeNode
                                       {
                                           attributes = new Attributes
                                                            {id = node.Node.TreeNodeID.ToString(),
                                                            title = nodeName},
                                           data = new Data {title = nodeName}
                                       };

                if(node.Node.ResourceID != null)
                {
                    cnode.attributes.complete = (node.IsComplete) ? "true" : "false";
                    cnode.attributes.rel = ResourceTypes.ReturnResourceTypeName(_resourceRepository.getResourceByID(Convert.ToInt32(node.Node.ResourceID)).ResourceTypeID);
                    cnode.attributes.href = "/resource/" + node.Node.ResourceID + "?minimal=true&nodeID=" + node.Node.TreeNodeID.ToString();
                }
                else
                {
                    var nodeChildren = GetNodesByTreeID(TreeID, node.Node.TreeNodeID,userID,revisionID);
                    if (nodeChildren.Count > 0)
                        cnode.children = nodeChildren;

                    cnode.attributes.complete = "false";
                    cnode.attributes.rel = "Folder";
                }

                nodesList.Add(cnode);
            }

            return nodesList;
        }

This thing was not noticed to be slow at first because there were not many 'tree nodes' in the database. Now it's really slow, at a glance is there anything major that is wrong with this? I really need to optimize it and before I rework the entire thing I was wondering if anything stands out as being the real pain point. I have narrowed down the slow part to a recursive repository function which is the last thing on this post, I have to define a few things leading up to it though... good luck. (NOTE: I did not write this, I was just put on damage control and I am trying to wrap my head around it.)

A few things to be understood first:

The JsTreeNode definition:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace App.Models
{
    public class JsTreeNode
    {
        public JsTreeNode()
        {
        }

        public Attributes attributes { get; set; }
        public Data data { get; set; }
        public string state { get; set; }
        public List<JsTreeNode> children { get; set; }
    }

    public class Attributes
    {
        public string id { get; set; }
        public string rel { get; set; }
        public string mdata { get; set; }
        public string href { get; set; }
        public string complete { get; set; }
        public string edit { get; set; }
        public string title { get; set; }
        public string resourceAccountID { get; set; }
    }

    public class Data
    {
        public string title {get;set;}
        public string icon {get;set;}
    } 
}

Now the TreeNodes table definition:

CREATE TABLE [dbo].[TreeNodes](
    [TreeNodeID] [int] IDENTITY(1,1) NOT NULL,
    [TreeID] [int] NOT NULL,
    [ParentNodeID] [int] NULL,
    [ResourceID] [int] NULL,
    [NodeOrder] [int] NOT NULL,
    [NodeName] [nvarchar](250) NOT NULL,
    [CreateBy] [int] NULL,
    [CreateDate] [datetime] NULL,
    [ModifyBy] [int] NULL,
    [ModifyDate] [datetime] NULL,
    [TreeRevisionID] [int] NULL,

The tree revisions table definition...

CREATE TABLE [dbo].[TreeRevisions](
    [TreeRevisionID] [int] IDENTITY(1,1) NOT NULL,
    [TreeID] [int] NOT NULL,
    [Notes] [text] NULL,
    [CreatedBy] [int] NOT NULL,
    [CreatedDate] [datetime] NOT NULL,
    [ModifyBy] [int] NOT NULL,
    [ModifyDate] [datetime] NOT NULL,

Now here is the Get Action itself that is taking 20-25 seconds, you can see in there that it calls repository function that I have defined further down the this thread.

public ContentResult Get(string target,int id)
        {
            int revisionID = Convert.ToInt32(Request.QueryString["revisionID"]);
            int tempUserID = (Request.QueryString["userID"] != null)
                                 ? Convert.ToInt32(Request.QueryString["userID"])
                                 : UserID;

            var nodesList = new List<JsTreeNode>();
            if(target.Contains("tree"))
            {
                tree tree = _treeRepository.GettreeByID(id);

                var cnode = new JsTreeNode
                                       {
                                           attributes = new Attributes {id = "0",title = tree.treeName},
                                           data = new Data {title = tree.treeName},
                                           children = _treeNodesRepository.GetNodesBytreeID(id, null,revisionID),
                                           state = "open"
                                       };
                cnode.attributes.rel = "root";
                cnode.attributes.mdata = "{draggable : true}";
                nodesList.Add(cnode);
            }
            else
            {

                var trees = _CategoryRepository.getAlltreesByCategoryID(id);

                IQueryable<tree> custom;

                if(revisionID != 0)
                {
                    custom = from c in trees
                              where
                                  c.AccountID == AccountID
                              select c;
                } else
                {
                    custom = from c in trees
                             where
                                 c.AccountID == AccountID && c.PublishedRevisionID != null && c.PublishedRevisionID != 0
                             select c;
                }

                var acme = from c in trees where c.AccountID == acmeContent.acmeID select c;

                foreach (var tree in (custom.Count() > 0) ? custom : acme)
                {
                    if(revisionID == 0 && tree.PublishedRevisionID == null) continue;
                    int tempRev = (revisionID != 0) ? revisionID : (int)tree.PublishedRevisionID;

                    if(custom.Count() == 1)
                    {
                        var tempNodes = _treeNodesRepository.GetNodesBytreeID(tree.treeID, null, tempUserID, tempRev);
                        nodesList.AddRange(tempNodes);
                    }
                    else
                    {
                        var cnode = new JsTreeNode();
                        cnode.attributes = new Attributes { id = tree.treeID.ToString(), title = tree.treeName };
                        cnode.data = new Data { title = tree.treeName };
                        cnode.children = _treeNodesRepository.GetNodesBytreeID(tree.treeID, null, tempUserID, tempRev);
                        cnode.attributes.rel = "Folder";

                        nodesList.Add(cnode);
                    }
                }
            }

            var ser = new JavaScriptSerializer();
            string res = ser.Serialize(nodesList);

            return Content(res,"application/json");

        }

...and Finally, the 'culprit', see how it calls itself:

public List<JsTreeNode> GetNodesByTreeID(int TreeID, int? parentID,int userID, int revisionID)
        {

            IQueryable<UserTreeNode> TreeNodes = GetAllTreeNodesByTreeIDAndParentNodeID(TreeID, parentID,userID,revisionID);
            List<JsTreeNode> nodesList = new List<JsTreeNode>();



            foreach (UserTreeNode node in TreeNodes)
            {
                string nodeName = node.Node.NodeName.Replace("'", "'");

                JsTreeNode cnode = new JsTreeNode
                                       {
                                           attributes = new Attributes
                                                            {id = node.Node.TreeNodeID.ToString(),
                                                            title = nodeName},
                                           data = new Data {title = nodeName}
                                       };

                if(node.Node.ResourceID != null)
                {
                    cnode.attributes.complete = (node.IsComplete) ? "true" : "false";
                    cnode.attributes.rel = ResourceTypes.ReturnResourceTypeName(_resourceRepository.getResourceByID(Convert.ToInt32(node.Node.ResourceID)).ResourceTypeID);
                    cnode.attributes.href = "/resource/" + node.Node.ResourceID + "?minimal=true&nodeID=" + node.Node.TreeNodeID.ToString();
                }
                else
                {
                    var nodeChildren = GetNodesByTreeID(TreeID, node.Node.TreeNodeID,userID,revisionID);
                    if (nodeChildren.Count > 0)
                        cnode.children = nodeChildren;

                    cnode.attributes.complete = "false";
                    cnode.attributes.rel = "Folder";
                }

                nodesList.Add(cnode);
            }

            return nodesList;
        }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

陈年往事 2024-09-26 17:00:20

此时,我最好的建议是启动 Visual Studio Profiler(与 Sql Profiler 不同)并了解进程的哪一部分实际上花费了最多时间。

如果您没有带有分析器的 VS 版本,您可以查看此搜索其他应用程序分析器。第一个点击是针对 asp.net 的。

My best suggestion at this point in the process is to spin up Visual Studio Profiler (different from Sql Profiler) and get an idea of what part of the process is actually taking the most amount of time.

If you don't have a version of VS with the profiler, you can check out this search for other application profilers. First hit is for asp.net.

葬心 2024-09-26 17:00:20

您发布的表包含一个标识列,但这些列未指定为主键(至少在您显示的示例中不是)。

我无法找到您的 LinqToSql 查询结构,因此很难对查询的优化发表评论。我怀疑它全部包含在: GetAllTreeNodesByTreeIDAndParentNodeID

在 Sql Studio 中单独运行这些语句:

sp_help TreeNodes
sp_help TreeRevisions

第 6 个结果集是索引列表...在您的情况下 - 该结果集可能为空。如果你没有索引,那就是你的第一个问题。

乍一看,我建议添加这些索引

TreeNodes
  TreeNodeID primary key
x TreeID nonclustered index
  TreeRevisionID nonclustered index
* TreeID, ParentNodeId nonclustered index

TreeRevisions
  TreeRevisionID primary key
  TreeID nonclustered index

,标有 * 的索引可能是您当前查询方式中最重要的索引。列顺序在多列索引中很重要。

还可以考虑通过 TreeId 获取整个树并在内存中进行更多过滤/整形。这将避免在数据库中递归/重复查询。通过这种方法,标有 x 的索引是重要的索引。

The tables you posted include an identity column, but these columns are not designated as primary keys (at least, not in the sample you show).

I'm unable to find your LinqToSql query constructions, so it's really hard to comment on optimization of the queries. I suspect it's all contained within : GetAllTreeNodesByTreeIDAndParentNodeID

Run these statements individually in Sql Studio:

sp_help TreeNodes
sp_help TreeRevisions

The 6th result set is a list of indexes... in your case - this result set may be empty. If you have no indexes, that's your #1 problem.

At a glance, I would recommend these indexes be added

TreeNodes
  TreeNodeID primary key
x TreeID nonclustered index
  TreeRevisionID nonclustered index
* TreeID, ParentNodeId nonclustered index

TreeRevisions
  TreeRevisionID primary key
  TreeID nonclustered index

That one marked with a * is probably the most important one for your current way of querying. Column order is important in a multi-column index.

Also consider fetching the whole tree by TreeId and doing more filtering/shaping in memory. This will avoid recursive/repetitive querying in the database. With this approach, the index marked with an x is the significant one.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文