本查询用于显示好友动态。
好友动态表设计如下 用户具体动作表
表名:userAction
Id 自增ID Int
UID 用户数字ID Int
UserId 用户昵称 Nvarhcar(20)
uaType 动态类型
uaMes 动态文本 Text
uaDate 动态时间戳 Datetime 哪些好友有动作的表
表名:uaUsers
id
uaId 动态ID Int 需要索引
uauUId 接收人数字ID Int 需要索引
uauUserId 接收人昵称 Nvarhcar(20)
uauDate 创建时间 Datetime 我查询好友动态的语句如下:(5个不重复的好友的动态)
select top 5 * from userAction a
where a.id in (select top 1 b.id from userAction b,uaUsers c where b.UID=a.UID and c.uaId=b.id and c.uauUId=@UId order by b.uadate desc) and DATEDIFF(dd,uaDate, getdate() )<= 2
order by a.uadate desc
已经建立存储过程,传入用户ID为参数以上语句经过测试并未发现问题。后来好友动态功能上线,这个语句几乎超时。我count两个数据库,记录数都上万。只要清楚了7天前的好友动态,但是速度还是不理想。使用查询分析器查看执行计划,如下图(为方便查看,图略做了修改)
我的索引部分几乎没用到。我的索引如下
求教大虾帮我分析下原因提供一些较好的解决办法。感激不尽。
好友动态表设计如下 用户具体动作表
表名:userAction
Id 自增ID Int
UID 用户数字ID Int
UserId 用户昵称 Nvarhcar(20)
uaType 动态类型
uaMes 动态文本 Text
uaDate 动态时间戳 Datetime 哪些好友有动作的表
表名:uaUsers
id
uaId 动态ID Int 需要索引
uauUId 接收人数字ID Int 需要索引
uauUserId 接收人昵称 Nvarhcar(20)
uauDate 创建时间 Datetime 我查询好友动态的语句如下:(5个不重复的好友的动态)
select top 5 * from userAction a
where a.id in (select top 1 b.id from userAction b,uaUsers c where b.UID=a.UID and c.uaId=b.id and c.uauUId=@UId order by b.uadate desc) and DATEDIFF(dd,uaDate, getdate() )<= 2
order by a.uadate desc
已经建立存储过程,传入用户ID为参数以上语句经过测试并未发现问题。后来好友动态功能上线,这个语句几乎超时。我count两个数据库,记录数都上万。只要清楚了7天前的好友动态,但是速度还是不理想。使用查询分析器查看执行计划,如下图(为方便查看,图略做了修改)
我的索引部分几乎没用到。我的索引如下
求教大虾帮我分析下原因提供一些较好的解决办法。感激不尽。
http://topic.csdn.net/u/20091130/21/fb718680-98ff-4afb-98d8-cff2f8293ed5.html?24281
--先试试这个
SELECT TOP 5 * FROM userAction a
WHERE NOT EXISTS(
SELECT TOP 1 1
FROM userAction b,uaUsers c
WHERE b.UID=a.UID
AND c.uaId=b.id
AND c.uauUId=@UId
AND b.uadate>a.uadate
)
AND DATEDIFF(dd,uaDate, getdate() ) <= 2
--这个.
SELECT * INTO #tmpUser FROM uaUsers WHERE uauUId=@UId select top 5 * from userAction a
where a.id in (
select top 1 b.id
from userAction b,#tmpUser c
where b.UID=a.UID and c.uaId=b.id
and c.uauUId=@UId
order by b.uadate desc
) and DATEDIFF(dd,uaDate, getdate() ) <= 2
order by a.uadate desc DROP TABLE #tmpUser
--这个
select top 5 * from userAction a
where a.id in (select top 1 b.id from userAction b,uaUsers c where b.UID=a.UID and c.uaId=b.id and c.uauUId=@UId order by b.uadate desc) and DATEDIFF(dd,uaDate, getdate() ) <= 2
order by a.uadate desc
OPTION(HASH JOIN)
不加索引的情况下,你一个个的试吧,若效果都不理想,那就考虑加索引了。
用到#tmpUser的那个也要10几秒?
1.
实现这样的需求,目前的表设计个人感觉不到位。
很多应用似乎都有类似的需求,应该有更好的设计,勤思考吧。2.
查询计划表明主要的消耗点在table scan 和 sort,那就对症下药吧。试试下面的方法:
首先在userAction 表上建一个索引:CREATE INDEX idx_test ON userAction(uauDate,UID,id)将查询改写成:select top 5 * from userAction a
where a.id = (select top 1 b.id from userAction b,uaUsers c
where b.UID=a.UID and c.uaId=b.id and c.uauUId=@UId order by b.uadate desc)
and uaDate >= DATEADD(d,DATEDIFF(d,0,GETDATE()-2),0)
order by a.uadate desc 其中2点说明:
1)改写uaDate条件让其符合SARG
2)既然已经是 TOP 1 了就直接写等号吧(写IN看起来有点怪)3.
#6楼查询效率高的原因是主查询没了order by子句,缺少order by子句
查询的语义也就变了。同理LZ写的查询去掉order by子句也会快不少的。
此时实际的数据分布会对查询速度有较大影响,简单说就是有的用户会
比较快有的用户会比较慢。6楼的改写还依赖一个事实:userAction表的
(UID,uaDate)组合必须是unique的,否则结果就不对了,即使加上order BY
子句。换句话说,如果userAction表能满足这样一种假设:
因为Id是自增长的,对于同一个UID/UserId,MAX(Id)是否可以作为该用户的
最新动态呢?如果可以,那么下面的查询也许对于实现这个需求有点帮助。SELECT UID,MAX(id) AS id
FROM userAction
WHERE uaDate >= DATEADD(d,DATEDIFF(d,0,GETDATE()-2),0)
GROUP BY UID
4.
userAction表uaMes字段一定要用text类型嘛?
根据目前的定义(uaType数据类型未知)估计,uaMes定义成nvarchar(3500)应该是没有问题的。
如果一定要用text类型,并且字段数据长度较小的行又占有一定的比例的话,考虑
开启表的 text in row 选项,对查询会有一点帮助的。5.升级到SQL Server 2005/2008 T-SQL增强会带来更好的实现方式。
WHERE NOT EXISTS(
SELECT TOP 1 1
FROM userAction b,uaUsers c
WHERE b.UID=a.UID
AND c.uaId=b.id
AND c.uauUId=@UId
AND b.uadate>a.uadate
)
AND DATEDIFF(dd,uaDate, getdate() ) <= 2 ORDER BY uadate DESC
加了order by 和lz的查询效率基本一致。还是无法避免 table scan 和 sort
#6的不加 order by 一样会table scan和sort.打赌吧,加了order by 後我说速度跟#6的会差不多。
sort是肯定没有了,不然不会快的。
2.加了order by 后就必须要扫描完整张表,而且要必须要排序。
1.#6的一定会scan完整张表,然後接着一个sort(按uadate),而不是楼主原来的2个sort, 而且会少了table spool等緩衝.2.加上order by 後,可能会出现2个sort,也可能只有1个sort,但肯定没有spool等操作.
表:userAction : ID 设为 聚集index,在(uaDate,UID)上建个非聚集索引
表:uaUsers : 在(uauUId,uaId)上建非聚集index select top 5 c.*
from uausers a
inner join (
select max(id) id
from useraction
where uadate >= dateadd(d,datediff(d,0,getdate()-2),0)
group by uid
) b on a.uaid = b.id
inner join useraction c on b.id=c.id
where a.uauuid=@uid
order by a.uaid desc 上面的查询依赖userAction的ID自增,且对于每个用户他的max(id)表示他的最近一次动作。
因为table scan的结果很有可能已被cache到bp中,剩下是要针对每一笔已资料做对应的搜索动作,(即Nested Loop 里层的每一个操作,这里的3%的sort 跟table spool等操作才是最消耗资源的)。
不清楚啊,加了HASH JOIN 不知道执行计划变成了啥样~~都在猜。
2.15%的sort运算符的输出行数一定是<=N,原因是top运算符的存在,当然最差情况是一条不落的输出全部的N行记录。N有多大呢?:-)
而倒霉的人要等到第N条记录被输出后,才能看到结果。
TOP 运算符在最顶层,所以table scan的结果 (不知道DATEDIFF(dd,uaDate,Getdate())<=2会不会下推到table scan中)会一条不落的参與循環,这个N嘛,lz说了算,哈哈。
而判断的依据是看到了Row Count Spool运算符。所以,SQL Server已经将lz的查询优化成 exists/not exists 了。
如果DATEDIFF(dd,uaDate,Getdate()) <=2不是在table scan里完成的,微软可以关门了 :-)
我同意第1个NL运算符是 Left Semi Join/Left Anti Semi Join, 但我反对"SQL Server已经将lz的查询优化成 exists/not exists "的说法。用Left Semi Join/Left Anti Semi Join运算的原因是lz的语句中用了 IN , 所以要处理 NULL值的情况。
如果lz把 IN 改成 = ,应该会变成 NL/Inner 了。
查询不复杂,逻辑等效的前提。 exists/ not exists 的计划和 in/not in 通常是一样的。
收回这句话:所以,SQL Server已经将lz的查询优化成 exists/not exists 了。这样说法本身就是有问题的,谢谢Garnett_KG提醒。
这个就未必了,正如30,31楼所说。
那个幸运的家伙最先输出的5条记录正好都是他的,查询就此over了。第一个嵌套循环
连接的内部处理过程只要被处理5次。没必要在执行剩下的N-5次了。这就是top
运算符的作用: 满足输出的行数后,查询即完成。
你再回过头去再lz的执行计划,15% sort之後就是NL, 最上面才是TOP运算,NL里层的TOP 是子查询里面的TOP.
只有经过顶层的TOP才输出是5行啊换句话说,TOP 一要等所有的数据按uaDate排序完後才能决定是哪5行返回啊。
对应这个查询,第一个NL的内部处理过程执行的次数是由sort运算符决定的。
假设table scan 中满足DATEDIFF(dd,uaDate,Getdate()) <=2条件的记录有N条。
所以sort的最小输出行数是5,最大输出行数是N,那么相应的NL的内部被执行
的次数也就是最少5次,最多N次。下面的查询,我瞎诌的,但愿能说明问题 ;-)USE Northwind
GO
SELECT TOP 10 * FROM dbo.[Order Details] AS a
WHERE
a.OrderID IN (SELECT TOP 1 OrderID FROM dbo.Orders WHERE a.OrderID = ABS(OrderID - 1000000)
ORDER BY ShipVia)
ORDER BY Discount DESCSELECT TOP 10 * FROM dbo.[Order Details] AS a
WHERE
a.OrderID IN (SELECT TOP 1 OrderID FROM dbo.Orders WHERE a.OrderID = ABS(OrderID)
ORDER BY ShipVia)
ORDER BY Discount DESC
两个不同的查询,用的相同的计划。Order Details聚集索引扫描输出的都是
2155行,第一个查询的sort也输出了2155行,因此NL的内部被执行了2155次。
而第2个查询sort输出了10行,那么NL的内部只被执行了10次。
语句1:select top 1 b.id from userAction b,uaUsers c where c.uaId=b.id and c.uauUId=162235 order by b.uadate desc语句2:select max(b.id) from userAction b,uaUsers c where c.uaId=b.id and c.uauUId=162235语句3:select top 5 * from userAction a
where a.id =(select top 1 b.id from userAction b,uaUsers c where b.UID=a.UID and c.uaId=b.id and c.uauUId=162235 order by b.uadate desc) and uadate >= dateadd(d,datediff(d,0,getdate()-2),0)
order by a.uadate desc 语句4:select top 5 * from userAction a
where a.id = (select max(b.id) from userAction b,uaUsers c where b.UID=a.UID and c.uaId=b.id and c.uauUId=162235 ) and uadate >= dateadd(d,datediff(d,0,getdate()-2),0)
order by a.uadate desc
from uausers a
inner join (
select max(id) id
from useraction
where uadate >= dateadd(d,datediff(d,0,getdate()-2),0)
group by uid
) b on a.uaid = b.id
inner join useraction c on b.id=c.id
where a.uauuid=162235
order by a.uaid desc 此句未找到数据
SELECT TOP 5 * FROM userAction a
WHERE NOT EXISTS(
SELECT TOP 1 1
FROM userAction b,uaUsers c
WHERE b.UID=a.UID
AND c.uaId=b.id
AND c.uauUId=162235
AND b.uadate>a.uadate
)
AND DATEDIFF(dd,uaDate, getdate() ) <= 2 ORDER BY uadate DESC
此句找到重复数据,可能和我插入的数据重复有关(日期)