【老话重谈,树遍历,树地图】如何更有效,更精确,更快速的查询

Jose_Xu 2010-07-15 11:32:42
树,递归实现。类似淘宝那种所谓大型分类的表,无限扩展,怎么做才会更好?单表树查询性能=>缓存。不用说了。

那么如何利用无限树分类,查询关联的详细表的性能做好最优。

如下树:
一级
----一级_1
----一级_2
-------一级_2_1
二级
----二级_1
-------二级_1_1
-----------二级_1_1_1
----二级_2
--------二级_2_1
三级

我需要做的是,查询一级下面所有的产品数据。包括一级下所有分类下的产品数据(这里的分类是无限未知的层次)。
普通的做法,可能就是递归查询这个树找出所有子节点的ID,组合起来进行IN查询了。这样做的效率差的地方就是递归查询ID组合,然后再去查询产品表。
select * from Product where id in(1,11,12,13);

另外就是在表结构内加地图(每个节点数据都包含节点所在的位置,以为层次结构)
00001 一级 00001
00002 一级_1 0000100002
00003 一级_2 0000100003
00004 一级_1_1 000010000200004
00005 二级 00005
..... 省略。。。。
如此可见,结构确实比之前要清晰的多,而且保存了节点位置地图。可是缺点仍然可见,查询一级下面的所有自己点,需要进行like查询。性能还是慢,无法更准确更直接的定位。更为麻烦的是,这种处理办法需要写触发器来进行处理每次的增删改带来的表结构变化。

最后一种方案
同样,加字段保存地图。可这个地图存放的是所有子节点包括本身节点的ID串。如:100001,100002,100003 这样的字符串。最后始终是需要进行IN查询的。同样的需求,这样更直接,更准确的拿来做查询。可是难题还是在如何记录这一串ID串,而且每次进行增删改之后表结构带来的变化。性能又怎么保障。
这里我更倾重于查询速度,而忽略制造这些地图。为了保障地图的有效性和对数据库的带来的性能,地图不保存在数据库中。而是保存在缓存中,缓存是永久缓存,当有增删改的同时,对缓存进行清空。重新制造地图。这样快的,更精确的对其他表查询。而不用再去考虑树带来的递归损耗(因为在获取的时候就已经处理好放在缓存中,而这个缓存的命中率是非常高的)。

下面给出代码:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[ ] args)
{
//获取数据源
IList<Tree> entities = new List<Tree>();
entities.Add( new Tree() { ID = 10001, FatherID = 0, Name = "一级" } );
entities.Add( new Tree() { ID = 10002, FatherID = 0, Name = "二级" } );
entities.Add( new Tree() { ID = 10003, FatherID = 0, Name = "三级" } );
entities.Add( new Tree() { ID = 10004, FatherID = 10001, Name = "一级_1" } );
entities.Add( new Tree() { ID = 10005, FatherID = 10001, Name = "一级_2" } );
entities.Add( new Tree() { ID = 10006, FatherID = 10005, Name = "一级_2_1" } );
entities.Add( new Tree() { ID = 10007, FatherID = 10002, Name = "二级_1" } );
entities.Add( new Tree() { ID = 10008, FatherID = 10002, Name = "二级_2" } );
entities.Add( new Tree() { ID = 10009, FatherID = 10008, Name = "二级_2_1" } );
//递归起始点
Recursive( entities, 0, null );

foreach ( Tree t in entities )
{
Console.WriteLine( "ID:{0},Maps:{1}", t.ID, t.Maps );
}

Console.Read();
}
/// <summary>
/// 节点递归 -- 填充树地图
/// </summary>
/// <param name="entities">数据集</param>
/// <param name="fatherID">FatherID</param>
/// <param name="entityFather">子节点数据,用于保存地图</param>
static void Recursive(IList<Tree> entities, int fatherID, Tree entityFather)
{
var query = from c in entities where c.FatherID == fatherID select c;
foreach ( Tree entity in query )
{
if ( entityFather != null )
{
entityFather.Maps +=
!string.IsNullOrEmpty( entityFather.Maps ) ? "," + entity.ID : entity.ID.ToString();
Recursive( entities, entity.ID, entityFather );
}
else
{
entity.Maps +=
!string.IsNullOrEmpty( entity.Maps ) ? "," + entity.ID : entity.ID.ToString();
Recursive( entities, entity.ID, entity );
Recursive( entities, entity.ID, null );
}
}
}
}

public class Tree
{
public int ID { get; set; }
public string Name { get; set; }
public int FatherID { get; set; }
public string Maps { get; set; }
}
}


输出结果:

ID:10001,FatherID:0 ,Maps:10001,10004,10005,10006
ID:10002,FatherID:0 ,Maps:10002,10007,10008,10009
ID:10003,FatherID:0 ,Maps:10003
ID:10004,FatherID:10001,Maps:10004
ID:10005,FatherID:10001,Maps:10005,10006
ID:10006,FatherID:10005,Maps:10006
ID:10007,FatherID:10002,Maps:10007
ID:10008,FatherID:10002,Maps:10008,10009
ID:10009,FatherID:10008,Maps:10009



如果有更好的方案(包括性能上),愿恭听。
...全文
226 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
allenzhang0002 2010-07-16
  • 打赏
  • 举报
回复
jf......
Jose_Xu 2010-07-16
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 coolieman 的回复:]

读写分离好象就只有用递归哦
[/Quote]
还可以用二叉树。

反正对树操作的算法 就那么几个,因为分类树最多也不会超过200吧,而且深度也不会超过5层。递归足够了
CoolieMan 2010-07-16
  • 打赏
  • 举报
回复
读写分离好象就只有用递归哦
想哥 2010-07-16
  • 打赏
  • 举报
回复
学习,帮楼主顶起。
Jose_Xu 2010-07-16
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 coolieman 的回复:]

还可以用Left

表结构内加地图(每个节点数据都包含节点所在的位置,以为层次结构)
ID Name Path
1 一级 0
2 一级_1 0,1
3 一级_2 0,1
4 一级_1_1 0,1,2
5 二级 0

获取Name为“一级”的下属节点
先获取出该名称的Path为“0”,再加上“,” 与该名称的ID
……
[/Quote]

我理解这种方式,但是会引发一连串的维护问题,维护什么,一些增删改操作对树结构本身带来的变化,而这个地图字段存储在数据库中,就意味着需要写一些触发器就这别的来处理地图。是不是更麻烦?而且对数据库本身虽然造成的影响非常小,可是我对性能方面仍然遵循一种原则:“读写分离”。
CoolieMan 2010-07-16
  • 打赏
  • 举报
回复
还可以用Left

表结构内加地图(每个节点数据都包含节点所在的位置,以为层次结构)
ID Name Path
1 一级 0
2 一级_1 0,1
3 一级_2 0,1
4 一级_1_1 0,1,2
5 二级 0

获取Name为“一级”的下属节点
先获取出该名称的Path为“0”,再加上“,” 与该名称的ID
string path="0,1"
然后
select * from Product where left(Path,len(path))=path
段传涛 2010-07-16
  • 打赏
  • 举报
回复
先帮你顶, 研究研究。
hyblusea 2010-07-16
  • 打赏
  • 举报
回复
楼主这种方式是比较大众化的, 而采用存储过程的非递归的方式不知道楼主测试过没,我认为性能是非常不错的

http://topic.csdn.net/u/20100715/23/14cb47cc-ea8f-43ba-be47-2afd48fafdaa.html
ycg_893 2010-07-16
  • 打赏
  • 举报
回复
而 IN(1,2,3) 这种不会用到索引,性能反而不如 Like '001%'
ycg_893 2010-07-16
  • 打赏
  • 举报
回复
like '001%' 这种可以的,会用到索引,所以速度应该不会很慢,如果是聚集索引就更快了

Like '%001' 或 '%001%' 就不会用到索引,所以性能就不行

110,549

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧