随笔-39  评论-229  文章-0  trackbacks-0
大家如果对SQL优化感兴趣的话,可以看看这个链接,
http://sqlblog.com/blogs/adam_machanic/archive/2008/04/22/sql-server-query-processing-puzzle-like-vs.aspx
这个是一个sql牛人的 博客,他在上面提出了一个小问题:
USE TempDB
GO
CREATE TABLE b1 (blat1 nCHAR(5NOT NULL)
CREATE TABLE b2 (blat2 VARCHAR(200NOT NULL)
GO
INSERT b1
SELECT LEFT(AddressLine1, 5AS blat1 FROM AdventureWorks.Person.Address
INSERT b2
SELECT AddressLine1 AS blat2
FROM AdventureWorks.Person.Address
GO

然后执行如下的查询语句:
SELECT * FROM b1  
JOIN b2 ON    b2.blat2 LIKE b1.blat1 + '%'
这个语句在他的机器上是花费了3分钟(在我的机器上是7分钟......)

他想问大家有什么办法可以提高速度而不采用新建对象(比如临时表,索引,索引视图)
如果能回答出来,你可以直接给他发邮件:Machanic#@pythian.com 。
当然我已经给他邮件了,那么你呢?


(最终我的优化是1.6秒,在我的T40上)
Tag标签: sql优化
posted on 2008-04-24 01:07 PerfectDesign 阅读(3907) 评论(54)  编辑 收藏 所属分类: MSSQL调优日志

评论:
#1楼  2008-04-24 08:28 | RicCC      
SELECT *
FROM (select distinct blat1 from b1) t1
inner JOIN
(select left(blat2,5) as c1,blat2 from b2) t2 ON t2.c1=t1.blat1

不知道原作者是不是考考大家这两个知识点的,你看看效果怎么样
我用80万数据测试,每条数据join结果重复30多条,所以结果总数应当有3千万左右,我T43 SQL 2005跑不出来

他原来的查询中重复记录比较多,理论上使用merge join应当是最理想的算法,但是没有索引,排序带来的成本将导致折扣比较大,因此hash join可能是最好的选择,你用上面的语句对比一下merge join和hash join的效率看看
  回复  引用  查看    
#2楼  2008-04-24 08:59 | 李战      

  回复  引用  查看    
#3楼  2008-04-24 09:03 | 姜敏      
Lz,把你优化的程序,不妨贴出来给大家分享下.
  回复  引用  查看    
#4楼  2008-04-24 09:03 | lost [未注册用户]
你更牛
  回复  引用  查看    
#5楼 [楼主] 2008-04-24 09:14 | PerfectDesign      
@姜敏
我先低调,呵呵
不过我的最少时间是1.6秒
  回复  引用  查看    
#6楼  2008-04-24 09:18 | 狼Robot      
SELECT * FROM b1 inner hash JOIN b2 ON b2.blat2 LIKE b1.blat1 + '%'

消息 8622,级别 16,状态 1,第 1 行
由于此查询中定义了提示,查询处理器未能生成查询计划。请重新提交查询,并且不要在查询中指定任何提示,也不要使用 SET FORCEPLAN。
  回复  引用  查看    
#7楼 [楼主] 2008-04-24 09:21 | PerfectDesign      
@狼Robot
使用like的连接不能使用hash

  回复  引用  查看    
#8楼 [楼主] 2008-04-24 09:35 | PerfectDesign      
hash算法是将等号旁的值经过hash后,进行查找对比的,经过了hash,like已经不能匹配查找了。
  回复  引用  查看    
#9楼  2008-04-24 09:42 | 生鱼片      
路过
  回复  引用  查看    
#10楼  2008-04-24 09:44 | 狼Robot      
@PerfectDesign
这个代码是楼主你贴的啊,你说执行需要7分钟,怎么我都执行不了呢?
  回复  引用  查看    
#11楼 [楼主] 2008-04-24 09:47 | PerfectDesign      
恩,才发现原来自己贴错了
谢谢!
  回复  引用  查看    
#12楼 [楼主] 2008-04-24 09:55 | PerfectDesign      
@RicCC
你的方案的确是好,但是我查出来的行数是17w行
但是在我上面运行你的结果是19508行
  回复  引用  查看    
#13楼 [楼主] 2008-04-24 09:57 | PerfectDesign      
去掉你的distinct数据行数就对了,
但是在我机器上跑上2.3秒
  回复  引用  查看    
#14楼  2008-04-24 10:01 | 狼Robot      
SELECT * FROM b1 JOIN b2 ON b2.blat2 LIKE b1.blat1 + '%'
结果:107462行
时间:00:02:19

楼主能不能贴下代码,我很菜,学习下.

楼主说的优化是怎么优化?是需要查询出来同样的结果吗?
  回复  引用  查看    
#15楼 [楼主] 2008-04-24 10:11 | PerfectDesign      
@狼Robot
你的机器这么好........
三倍差距啊.........

(107462 行受影响)
表 'Worktable'。扫描计数 0,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'b2'。扫描计数 1,逻辑读取 75 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'b1'。扫描计数 1,逻辑读取 47 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

SQL Server 执行时间:
CPU 时间 = 220 毫秒,占用时间 = 1810 毫秒。

SQL Server 执行时间:
CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。



  回复  引用  查看    
#16楼 [楼主] 2008-04-24 10:12 | PerfectDesign      
我的IO和Ricc去掉distinct的是一样多,不过写法不一样
  回复  引用  查看    
#17楼 [楼主] 2008-04-24 10:13 | PerfectDesign      
SELECT * FROM b1 inner
JOIN b2 ON left(b2.blat2,5)=b1.blat1 option(hash join)


  回复  引用  查看    
#18楼  2008-04-24 10:19 | young5335 [未注册用户]
SELECT * FROM b1
JOIN b2 ON Substring(b2.blat2,1,5)=b1.blat1
  回复  引用  查看    
#19楼  2008-04-24 10:21 | young5335 [未注册用户]
SELECT * FROM b1
JOIN b2 ON Substring(b2.blat2,1,5)=b1.blat1
这个在我的机器上运行的时机为00:00:00

不过我想,更觉的情况应该是这样吧:
SELECT * FROM b1
JOIN b2 ON Substring(b2.blat2,1,len(b1.blat1))=b1.blat1
  回复  引用  查看    
#20楼  2008-04-24 10:29 | xmx006 [未注册用户]
我改了下,执行计划用的hash join,快了一些,感觉效果还不是特别明显:

SELECT *
FROM b1 inner JOIN b2
ON cast(b2.blat2 as nchar(5))=b1.blat1
  回复  引用  查看    
#21楼 [楼主] 2008-04-24 10:29 | PerfectDesign      
@young5335
不太可能吧?
在我的机器上已经三分钟了,还没出结果..............

  回复  引用  查看    
#22楼  2008-04-24 10:30 | 狼Robot      
@PerfectDesign

楼主的结果是在哪里看到的?我只能看到时间和结果集.

我的破电脑是神州的.
  回复  引用  查看    
#23楼  2008-04-24 10:32 | Frank_Ning [未注册用户]
我运行的结果是1秒
不知对不对
请指教

SELECT * FROM b1
JOIN b2 ON b1.blat1 = LEFT(b2.blat2,5)

(107462 行受影响)
表 'Worktable'。扫描计数 0,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'b2'。扫描计数 1,逻辑读取 75 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 'b1'。扫描计数 1,逻辑读取 47 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

  回复  引用  查看    
#24楼 [楼主] 2008-04-24 10:33 | PerfectDesign      
@young5335
你的代码我改成了这样:
SELECT * FROM b1
JOIN b2 ON Substring(b2.blat2,0,5)=b1.blat1

速度是快很多,但是结果集行数不一样。
  回复  引用  查看    
#25楼 [楼主] 2008-04-24 10:34 | PerfectDesign      
@Frank_Ning
Y,跟我的一样,只不过我指定了连接方式
  回复  引用  查看    
#26楼 [楼主] 2008-04-24 10:36 | PerfectDesign      
@xmx006
似乎你的cast和我的left效率一样。
  回复  引用  查看    
#27楼 [楼主] 2008-04-24 10:36 | PerfectDesign      
@狼Robot
你可以使用
set statistics io on
set statistics time on
打开统计信息
  回复  引用  查看    
#28楼  2008-04-24 10:38 | Frank_Ning [未注册用户]
TO PerfectDesign:
因为我想了一下,可以用最简单的方式解决问题不是最好吗?呵呵。
  回复  引用  查看    
#29楼  2008-04-24 10:51 | young5335 [未注册用户]
我测了一下(未精确测试),楼主跟我的一样结果一样,虽然行数不同,在我的机器上是107462行,运行时间同样为○
  回复  引用  查看    
#30楼  2008-04-24 10:52 | young5335 [未注册用户]
再用一个更通用的方法:
with tmp as
(
Select * From b1,(Select TOP 1 datalength(b1.blat1)/2 as l From b1) as r
)
Select * From tmp Join b2 On SubString(b2.blat2,1,l)=tmp.blat1

运行行数同样,时间为00:00:01
  回复  引用  查看    
#31楼  2008-04-24 10:53 | A.Z! [未注册用户]
真牛B
  回复  引用  查看    
#32楼  2008-04-24 11:01 | 1111111 [未注册用户]
加 option (loop join)
  回复  引用  查看    
#33楼  2008-04-24 11:02 | xmx006 [未注册用户]
--引用--------------------------------------------------
PerfectDesign: @xmx006
似乎你的cast和我的left效率一样。
--------------------------------------------------------
是啊,效率一样的,我测试的数据少些,只输出155032 行记录,花了将近10秒,机器太差了。
  回复  引用  查看    
#34楼 [楼主] 2008-04-24 11:06 | PerfectDesign      
@1111111
没测试过loop的,但是感觉性能应该很差劲
因为loop最好适合双方有索引而且是大小表的loop
这个情景下2表数据都一样的,loop不适用。
  回复  引用  查看    
#35楼  2008-04-24 11:29 | willieQ      
有意思,关注~~~
  回复  引用  查看    
#36楼  2008-04-24 12:28 | 金色海洋(jyk)      
不清楚是什么意思。
  回复  引用  查看    
#37楼  2008-04-24 12:49 | 簡簡單單..      
Mark
  回复  引用  查看    
#38楼  2008-04-24 13:04 | RicCC      
这里这么热闹啊,都不关心股票的?

纯技术上的优化这样的方案都八九不离十的,估计作者是了解了查询执行过程的一些细节或碰到了这样的问题,所以写了那篇blog
要继续优化就得结合数据量、逻辑以及临时表、索引的建法等方面做文章

刚把AdventureWorks数据库给装上,作者原来的那个查询用时2.8s,我给的查询去掉distinct和直接b1 join b2 on blat1=left(blat2,5)或类似的方案都在1.6s, 1.7s的样子
  回复  引用  查看    
#39楼 [楼主] 2008-04-24 13:14 | PerfectDesign      
@RicCC
呵呵,深度套牢中啊,即使今天反弹,也只是九牛一毛罢了........

只是原作者要求不使用临时表索引,索引视图等
  回复  引用  查看    
#40楼  2008-04-24 13:17 | A.Z! [未注册用户]
--引用--------------------------------------------------
PerfectDesign: @RicCC
呵呵,深度套牢中啊,即使今天反弹,也只是九牛一毛罢了........

--------------------------------------------------------



同情...
  回复  引用  查看    
#41楼  2008-04-24 13:54 | Zhuang miao      
说答案吧
  回复  引用  查看    
#42楼 [楼主] 2008-04-24 14:06 | PerfectDesign      
@A.Z!
没心情看股市,只有写文章来消遣了

@Zhuang miao
我的个人答案在17楼,另外其他人提供的也是可行的,效率一样,写不同而已
  回复  引用  查看    
#43楼  2008-04-24 14:38 | 假正经哥哥2 [未注册用户]
答案大同小异
没有什么太大的意思
  回复  引用  查看    
#44楼  2008-04-24 15:57 | RicCC      
@PerfectDesign
被套这么严重啊
我中石油20的本一直拖后腿,今天终于在18上面甩光,后面就轻松了,做好准备抓行情
  回复  引用  查看    
#45楼  2008-04-24 16:50 | snowwolflibo      
"速度是快很多,但是结果集行数不一样。"

我不知道为什么以下两个SQL结果不一样

SELECT * FROM b1
JOIN b2 ON Substring(b2.blat2,0,5)=b1.blat1



SELECT * FROM b1
JOIN b2 ON b2.blat2 LIKE b1.blat1 + '%'

难道与是否unicode有关?

  回复  引用  查看    
#46楼 [楼主] 2008-04-24 16:59 | PerfectDesign      
@snowwolflibo
稍后会给出另外一个post做为解释,简单的将,是因为在没有选择的情况下,连接使用了本不该使用的loop join


@RicCC
不破发你就买了.........
不过有个同事48进了中石油..........
  回复  引用  查看    
#47楼  2008-04-24 23:47 | RicCC      
@PerfectDesign
不过有个同事48进了中石油

这是吹响冲锋号的,恐怖
发现我上面把2.8分钟写成2.8s了
  回复  引用  查看    
#48楼 [楼主] 2008-04-25 12:18 | PerfectDesign      
http://www.cnblogs.com/perfectdesign/archive/2008/04/25/hashloopmerge.html

这个优化测试的分析思路,大家可以指点指点。
  回复  引用  查看    
#49楼  2008-04-25 13:51 | 没剑      
楼主,借问一下,数据在哪里?
AdventureWorks.Person.Address
  回复  引用  查看    
#50楼 [楼主] 2008-04-25 14:24 | PerfectDesign      
安装sql2005的时候选装示例数据库即可
  回复  引用  查看    
#51楼  2008-04-25 14:35 | 没剑      
--引用--------------------------------------------------
PerfectDesign: 安装sql2005的时候选装示例数据库即可
--------------------------------------------------------
谢谢
  回复  引用  查看    
#52楼  2008-04-25 15:35 | <Null> [未注册用户]
没有人考虑到磁盘缓存对查询的影响吗?
关掉硬盘缓存再执行才准确吧。
  回复  引用  查看    
#53楼  2008-04-25 15:36 | 没剑      
--引用--------------------------------------------------
snowwolflibo: &quot;速度是快很多,但是结果集行数不一样。&quot;

我不知道为什么以下两个SQL结果不一样

SELECT * FROM b1
JOIN b2 ON Substring(b2.blat2,0,5)=b1.blat1



SELECT * FROM b1
JOIN b2 ON b2.blat2 LIKE b1.blat1 + '%'

难道与是否unicode有关?

--------------------------------------------------------
上面的两句是明显不一样的结果的操作好不好~
第一句算是精确查找,后一句是like。。。这两句完全没有比的必要
  回复  引用  查看    
#54楼  2008-07-09 13:13 | tempdb [未注册用户]
SELECT * FROM b1 JOIN b2 ON
LEFT(b2.blat2,5)=b1.blat1
  回复  引用  查看