zping.com

SQL SERVER,ORACLE数据库分析,设计,研究,优化,重构等

统计

常用链接

留言簿(3)

友情衔接

阅读排行榜

评论排行榜

SQL 大数据量的优化例子讨论

  今天在itput上看了一篇文章,是讨论一个语句的优化:

     原贴地址: http://www.itpub.net/viewthread.php?tid=1015964&extra=&page=1

  一,发现问题

   优化的语句:     

请问以下语句如何优化:
CREATE TABLE aa_001
   (    ip 
VARCHAR2(28), 
        name 
VARCHAR2(10), 
        password 
VARCHAR2(30)           )

select * from aa_001 where ip in (1,2,3order by name desc;
--目前表中记录有一千多万条左右,而且in中的值个数是不确定的。

  以上就是优化的需要优化的语句和情况。

 

   不少人在后面跟帖:有的说没办法优化,有的说将IN该为EXISTS,有的说在ip上建立索引复合索引(ip,name)等等。

  二,提出问题

     那这样的情况,能优化吗,如何优化?今天就来讨论这个问题。

  三,分析问题

        1,数据量1千万多条。

        2,in中的值个数是不确定

     3.1 分析数据分布

      这里作者没有提到ip列的数据的分布情况,目前ip列的数据分布可能有以下几种:

         1,ip列(数据唯一,或者数据重复的概率很小)

         2,ip列 (数据不均匀,可能有些数据重复多,有些重复少)

         3,ip列 (数据分布比较均匀,数据大量重复,主要就是一些同样的数据(可能只有上万级别不同的ip数据等)

 

     解决问题:

         1,对于第一种数据分布情况,只要在ip列建立一个索引即可。这时不管表有多少行, in个数是不确定的情况下,都很快。

         2,对应第二中数据分布情况,在ip列建立索引,效果不好。因为数据分布不均匀,可能有些快,有些慢

         3,对应第三种数据分布情况,在ip列建立索引,速度肯定慢。

        注意:这里的 order by name desc 是在取出数据后再排序的。而不是取数据前排序 

 

     对于2,3两个情况,因为都是可能需要取出大量的数据,优化器就采用表扫描(table scan),而不是索引查找(index seek) ,速度很慢,因为这时表扫描效率要优于索引查找,特别是高并发情况下,效率很低。

 

    那对应2,3中情况,如何处理。是将in改成exists。其实在sql server 2005和oracle里的优化器在in后面数据少时,效率是一样的。这时采用一般的索引效率很低。这时如果在ip列上建立聚集索引,效率会比较高。我们在SQL server 2005中做个测试。

 

   表:[dbo].[[zping.com]]]中有约200万条数据。包含列Userid, id, Ruleid等列。按照上面的情况查询一下类似语句: 

select  * from [dbo].[[zping.com]]] where 
userid 
in ('402881410ca47925010cb329c7670ffb','402881ba0d5dc94e010d5dced05a0008'
,
'4028814111a735e90111a77fa8e30384'order by Ruleid desc

   我们先看userid的数据分布情况,执行下面语句:

select userid,count(*from [dbo].[[zping.com]]] group by userid order by 2

   这时我们看看数据分布:总共有379条数据,数据两从1到15万都有,数据分布倾斜严重。下图是其中一部分。

 

 

   这时如果在ip上建立非聚集索引,效率很低,而且就是强行索引扫描,效率也很低,会发现IO次数比表扫描还高。这时只能在ip上建立聚集索引。这时看看结果。

  这时发现,搜索采用了(clustered index seek)聚集搜索扫描。

  在看看查询返回的结果: 

(156603 行受影响)
表 
'[zping.com]'。扫描计数 8,逻辑读取 5877 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
表 
'Worktable'。扫描计数 0,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    返回15万行,才不到6千次IO。效率较高,因为这15万行要排序,查询成本里排序占了51%。当然可以建立(userid,Ruleid)复合聚集索引,提高性能,但这样做DML维护成本较高。建议不采用。

 

   从上面的测试例子可以看出, 优化的解决办法:

     数据分布为1:建立ip索引即可

     数据分布为2,3:在ip列建立聚集索引。

http://www.zping.com/  热爱祖国

posted on 2008-08-13 19:22 zping.com 阅读(3092) 评论(24)  编辑 收藏 网摘 所属分类: SQL Server 优化

评论

#1楼  2008-08-13 19:27 Tony Zhou      

高!   回复  引用  查看    

#2楼  2008-08-13 20:12 Miser      

不错 学习   回复  引用  查看    

#3楼  2008-08-13 20:25 金色海洋(jyk)      

第一,分页显示数据,不要一下子把所有的数据都显示出来,都显示出来了也是看不过来的。

第二、如果可能的话给name建立聚集索引,如果不行的话那么只能建立非聚集索引了。建立聚集索引,再分页的情况下,效果一定很明显(快)。非聚集索引未作测试,效果不详。

第三,就是给IP建立非聚集索引了。

第四,磁盘阵列,增加硬盘的读写速度也是一种方法。现在硬盘都很便宜了,用串口的就行,像这种需求不必追求sici的。   回复  引用  查看    

#4楼  2008-08-13 21:47 Cheney Shue      

1. 对ip使用Hash Partition,Partitioning Pruning效率很高。
2. 如果对name索引后实现Full Index Scans,可以简省排序成本。

个人觉得最有效的办法时使用IOT,前2个column作index,name倒序,include password,溢出都不要了。以ip做Hash分区。

另外,对optimizer来说,in和exist其实是一样的,简单的语句可以简省解析成本.   回复  引用  查看    

#5楼  2008-08-13 22:00 Steven Chen      

mark   回复  引用  查看    

#6楼  2008-08-13 22:07 Phantaci.com      

IP转换成整型吧。再分别在IP和name上建非聚集索引.
  回复  引用  查看    

#7楼 [楼主] 2008-08-13 22:14 zping.com      

@Phantaci.com
不太可能换成整型,而且“分别在IP和name上建非聚集索引”也不会提高性能   回复  引用  查看    

#8楼  2008-08-13 22:20 Phantaci.com      

@zping.com
暴汗。。用了IP这个名字,确不是IP地址的意思..
误会了。。   回复  引用  查看    

#9楼  2008-08-13 22:22 PerfectDesign      

针对您的几点分析,我想提出几点我的意见:
2,对应第二中数据分布情况,在ip列建立索引,效果不好。因为数据分布不均匀,可能有些快,有些慢
查询器可以利用IP上的统计信息,来正确选择到底是seek还是scan。

数据分布为2,3:在ip列建立聚集索引
除非您的这个表是IP地址分配表(即此表只读),否则,在含有数据插入或者更新时,你把IP列作为聚集索引只能是个笑话了。   回复  引用  查看    

#10楼  2008-08-13 22:25 RicCC      

语句本身很简单,只要符合基本的优化原则效率上都不会有明显差别
其它方面的一些优化思路:
1. 看样子是oracle数据库,设计好分区效率会不错
2. 从字段名的语义上理解,ip应该是唯一的,如果这样就更好优化了
sql server 2000下面,单表2、3千万数据,有好的索引查询起来也是比较慢的   回复  引用  查看    

#11楼  2008-08-13 22:26 PerfectDesign      

@RicCC
赞同您的意思,这样的情景优化,只有分区才可以   回复  引用  查看    

#12楼 [楼主] 2008-08-13 22:29 zping.com      

@PerfectDesign
分区,这里的ip如果没有规律,怎么分区,即使用hash分区,提高的性能有限。不会达到只有几千次IO就可以的优化效果   回复  引用  查看    

#13楼  2008-08-13 22:46 PerfectDesign      

@zping.com
如果使用IP来做聚集索引就好比使用GUID来做聚集索引了,这样做是不合适的。
聚集索引是一个表的灵魂,他从设计意义上来讲是所有字段的依赖字段,从物理上讲,是整个表的脊梁。
如果采用IP做聚集索引,会使得插入和更新带来大量的页拆分,页面的不连贯,会使得IO增大很多。
另外自己不大喜欢使用char类型的做索引,那样非页节点太胖了,一般都是int或者是datetime。
观点仅供参考   回复  引用  查看    

#14楼 [楼主] 2008-08-13 23:01 zping.com      

@PerfectDesign
用ip做聚集索引,不是合适不合适的问题。而是业务的要求这样。你说的也有道理,但在我优化的系统中char(32)的字段里建立聚集索引,为何,我也不想,但是数据库是这样存放的,业务要求这样。不建立聚集索引,就是慢,我也想该成int,或者datetime类型。但不行啊。

还说到分区功能。我优化一个SQL server 2005是标准版的系统。标准版没有分区功能,客户也不可能去升级到企业版数据库。最后只能使用分区视图来分区。

其实优化系统有时就是在数据库,程序和业务之间的一种择中。   回复  引用  查看    

#15楼  2008-08-13 23:17 PerfectDesign      

mssql怎么会有VARCHAR2?   回复  引用  查看    

#16楼 [楼主] 2008-08-13 23:20 zping.com      

我这是拿一个在oracle中出现的例子来讨论的。   回复  引用  查看    

#17楼  2008-08-13 23:20 PerfectDesign      

ps hash 分区在mssql里是没有的

可能我最关键的地方没有阐释清楚,聚集索引的字段选择上,最重要的是他是插入有序的,或者说的递增或者递减的,而不是乱序插入,这个是引起性能瓶颈的最重要问题。而不是char型字段建聚集索引的可行性上。
我提到的char型不要建索引,只是因为char型相比int和datetime它太胖了而已。   回复  引用  查看    

#18楼 [楼主] 2008-08-13 23:25 zping.com      

@PerfectDesign
 在SQL server的确是没有hash分区,但可以实现,我的blog有个例子,可以看看:
     Sql Server 2005 实现Oracle 10g 的hash表分区功能 

    插入按顺序是会引起一点性能问,但相对来说这点时间的牺牲是值得的。   回复  引用  查看    

#19楼  2008-08-14 12:09 Garnett_KG [未注册用户]


我支持在IP上建聚集索引.

你在任何字段上建聚集索引都会有碎片的问题啦...除非你是用只递增或递減的字段(Identity?) , 笑话,那样有用吗?


CREATE TABLE aa_001
( ip VARCHAR2(28),
name VARCHAR2(10),
password VARCHAR2(30) )

select * from aa_001 where ip in (1,2,3) order by name desc

再评论之前麻烦看一下表构构跟需求再说。

  回复  引用    

#20楼  2008-08-14 17:31 haitian      

学习
索引很重要。   回复  引用  查看    

#21楼  2008-08-15 09:16 roberto      

@PerfectDesign

非常认同你这句查询器可以利用IP上的统计信息,来正确选择到底是seek还是

这么简单的结构,谈建什么索引和dml的代价都是很虚的,还是必须和实际使用
过程中数据分布的情况来处理。具体到使用过程中,说不定怎么建索引都搞不
定达不到要求,说不定最后没办法之下的解决方法是把查询和输入数据分开为两
个相同结构的表,隔一段时间做同步,把用来查询的那个大量数据的表数据分布
搞得最优了事(类似于SQL早期版本上手工实现表“分区”)。

这样没有数据详细分布情况的具体问题采用的方法到底哪个好还是那个坏不重
要,重要的是能够比较深入一点的全面考虑问题。博主的最后两点优化方法单
薄了一点,应该把文章中没说到而评论中说到的东西在主文中先讲讲就好好一
些。   回复  引用  查看    

#22楼  2008-08-26 07:36 dmh [未注册用户]

对IP做聚集索引不好,根据情况使用复合索引或者分区表   回复  引用    

#23楼  2008-09-25 20:44 啊合 [未注册用户]

才1千万啊?
既然1千万啦,插入可能非常平凡吧。
看select * from aa_001 where ip in (1,2,3) order by name desc; 与系统中其他语句哪个执行的更多啦。
如果这个相当高啦,IP又不唯一,也不知道怎么样啦。
IP,name 上建立主键吧。
  回复  引用    

#24楼  2008-09-25 20:54 啊合 [未注册用户]

既然路过,顺便问个问题,有空也可以帮帮我啦。
现在一个表:

CREATE TABLE [dbo].[PQC_PA](
[Line] [varchar](20) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_Line] DEFAULT (''),
[WO] [varchar](16) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_WO] DEFAULT (''),
[Model] [varchar](16) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_Cust_Model] DEFAULT (''),
[Customer] [varchar](16) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_Customer] DEFAULT (''),
[SN] [varchar](20) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_SN] DEFAULT (''),
[PN] [varchar](20) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_PN] DEFAULT (''),
[Error_Code] [varchar](6) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_Error_Code] DEFAULT (''),
[IP] [varchar](20) COLLATE SQL_Latin1_General_CP850_CI_AS NULL CONSTRAINT [DF_PQC_PA_IP] DEFAULT (''),
[TransDateTime] [varchar](14) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_TransDateTime] DEFAULT (''),
[InputDateTime] [varchar](14) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_InputDateTime] DEFAULT (''),
[Station] [varchar](20) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_Station] DEFAULT (''),
[OPID] [varchar](12) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_OPID] DEFAULT (''),
[Num] [tinyint] NOT NULL CONSTRAINT [DF_PQC_Num] DEFAULT ((0)),
[Defect_Code] [varchar](10) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_Defect_Code] DEFAULT (''),
[FirstTest] [varchar](3) COLLATE SQL_Latin1_General_CP850_CI_AS NOT NULL CONSTRAINT [DF_PQC_FirstTest] DEFAULT (''),
[CustStation] [varchar](50) COLLATE SQL_Latin1_General_CP850_CI_AS NULL CONSTRAINT [DF__PQC_PA__CustStat__11FF8BD8] DEFAULT (''),
[PQCFlag] [char](1) COLLATE SQL_Latin1_General_CP850_CI_AS NULL CONSTRAINT [DF_PQC_PA_PQCFlag] DEFAULT ('')
) ON [PRIMARY]

每天数据塞入 30 万到表中,而且SN 随机,但是平均每个SN在表中占有 20--30行(分布相当均匀),执行 SN 上的查找比较多,Transdatetime 按照时间在一天平均推进,这里建立的索引为:
SN,Transdatetime,Station 主键,插入分页严重,但为了查询,哎,不知道怎么最好了。

  回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-09-04 21:39 编辑过
Google站内搜索


相关链接: