人
们
在使用
SQL
时
往往会陷入一个
误
区,即太
关
注于所得的
结
果是否正确,而忽略了不同的
实现
方法之
间
可能存在的性能差异,
这种
性能差异在大型的或是
复杂
的数据
库环
境中(如
联
机事
务处
理
OLTP
或决策支持系
统
DSS
)中表
现
得尤
为
明
显
。
笔者在工作
实
践中
发现
,不良的
SQL
往往来自于不恰当的索引
设计
、不充份的
连
接条件和不可
优
化的
where
子句。
在
对
它
们进
行适当的
优
化后,其运行速度有了明
显
地提高!
下面我将从
这
三个方面分
别进
行
总结
:
为
了更直
观
地
说
明
问题
,所有
实
例中的
SQL
运行
时间
均
经过测试
,不超
过
1秒的均表示
为
(
< 1
秒)。
----
测试环
境
:
主机:
HP LH II----
主
频
:
330MHZ----
内存:
128
兆
----
操作系
统
:
Operserver5.0.4----
数据
库
:
Sybase11.0.3
一、不合理的索引
设计
----
例:表
record
有
620000
行,
试
看在不同的索引下,下面几个
SQL
的运行情况:
---- 1.
在
date
上建有一非个群集索引
select count(*) from record where date >'19991201' and date < '19991214'and amount >2000 (25秒)
select date ,sum(amount) from record group by date(55秒)
select count(*) from record where date >'19990901' and place in ('BJ','SH') (27秒)
----
分析:
----
date
上有大量的重
复值
,在非群集索引下,数据在物理上随机存放在数据
页
上,在范
围查
找
时
,必
须执
行一次表
扫
描才能找到
这
一范
围
内的全部行。
---- 2.
在
date
上的一个群集索引
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 (14秒)
select date,sum(amount) from record group by date(28秒)
select count(*) from record where date >'19990901' and place in ('BJ','SH')(14秒)
----
分析:
----
在群集索引下,数据在物理上按
顺
序在数据
页
上,重
复值
也排列在一起,因而在范
围查
找
时
,可以先找到
这
个范
围
的起末点,且只在
这
个范
围
内
扫
描数据
页
,避免了大范
围扫
描,提高了
查询
速度。
---- 3.
在
place
,
date
,
amount
上的
组
合索引
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 (26秒)
select date,sum(amount) from record group by date(27秒)
select count(*) from record where date >'19990901' and place in ('BJ, 'SH')(< 1秒)
----
分析:
----
这
是一个不很合理的
组
合索引,因
为
它的前
导
列是
place
,第一和第二条
SQL
没有引用
place
,因此也没有利用上索引;第三个
SQL
使用了
place
,且引用的所有列都包含在
组
合索引中,形成了索引覆盖,所以它的速度是非常快的。
---- 4.
在
date
,
place
,
amount
上的
组
合索引
select count(*) from record where date >'19991201' and date < '19991214' and amount >2000(< 1秒)
select date,sum(amount) from record group by date(11秒)
select count(*) from record where date >'19990901' and place in ('BJ','SH')(< 1秒)
----
分析:
----
这
是一个合理的
组
合索引。它将
date
作
为
前
导
列,使
每
个
SQL
都可以利用索引,并且在第一和第三个
SQL
中形成了索引覆盖,因而性能达到了最
优
。
---- 5.
总结
:
----
缺省情况下建立的索引是非群集索引,但有
时
它并不是最佳的;合理的索引
设计
要建立在
对
各
种查询
的分析和
预测
上。
一般来
说
:
①
.
有大量重
复值
、且
经
常有范
围查询
(
between, >,<
,
>=,< =
)和
order by
、
group by
发
生的列,可考
虑
建立群集索引;
②
.
经
常同
时
存取多列,且
每
列都含有重
复值
可考
虑
建立
组
合索引;
③
.
组
合索引要尽量使
关键查询
形成索引覆盖,其前
导
列一定是使用最
频
繁的列。
二、不充份的
连
接条件:
例:表
card
有
7896
行,在
card_no
上有一个非聚集索引,表
account
有
191122
行,在
account_no
上有一个非聚集索引,
试
看在不同的表
连
接条件下,两个
SQL
的
执
行情况:
select sum(a.amount) from account a,card b where a.card_no = b.card_no(20秒)
select sum(a.amount) from account a,card b where a.card_no = b.card_no and a.account_no=b.account_no(< 1秒)
----
分析:
----
在第一个
连
接条件下,最佳
查询
方案是将
account
作外
层
表,
card
作内
层
表,利用
card
上的索引,其
I/O
次数可由以下公式估算
为
:
外
层
表
account
上的
22541
页
+
(外
层
表
account
的
191122
行
*
内
层
表
card
上
对应
外
层
表第一行所要
查
找的
3
页
)
=595907
次
I/O
在第二个
连
接条件下,最佳
查询
方案是将
card
作外
层
表,
account
作内
层
表,利用
account
上的索引,其
I/O
次数可由以下公式估算
为
:外
层
表
card
上的
1944
页
+
(外
层
表
card
的
7896
行
*
内
层
表
account
上
对应
外
层
表
每
一行所要
查
找的
4
页
)
= 33528
次
I/O
可
见
,只有充份的
连
接条件,真正的最佳方案才会被
执
行。
总结
:
1.
多表操作在被
实际执
行前,
查询优
化器会根据
连
接条件,列出几
组
可能的
连
接方案并从中找出系
统开销
最小的最佳方案。
连
接条件要充份考
虑带
有索引的表、行数多的表;内外表的
选择
可由公式:外
层
表中的匹配行数
*
内
层
表中
每
一次
查
找的次数确定,乘
积
最小
为
最佳方案。
2.
查
看
执
行方案的方法
--
用
set showplan
on
,打
开
showplan
选项
,就可以看到
连
接
顺
序、使用何
种
索引的信息;想看更
详细
的信息,需用
sa
角色
执
行
dbcc(3604,310,302)
。
三、不可
优
化的
where
子句
1.
例:下列
SQL
条件
语
句中的列都建有恰当的索引,但
执
行速度却非常慢:
select * from record wheresubstring(card_no,1,4)='5378'(13秒)
select * from record whereamount/30< 1000(11秒)
select * from record whereconvert(char(10),date,112)='19991201'(10秒)
分析:
where
子句中
对
列的任何操作
结
果都是在
SQL
运行
时
逐列
计
算得到的,因此它不得不
进
行表搜索,而没有使用
该
列上面的索引;
如果
这
些
结
果在
查询编译时
就能得到,那
么
就可以被
SQL
优
化器
优
化,使用索引,避免表搜索,因此将
SQL
重写成下面
这样
:
select * from record where card_no like'5378%'(< 1秒)
select * from record where amount< 1000*30(< 1秒)
select * from record where date= '1999/12/01'(< 1秒)
你会
发现
SQL
明
显
快起来!
2.
例:表
stuff
有
200000
行,
id_no
上有非群集索引,
请
看下面
这
个
SQL
:
select count(*) from stuff where id_no in('0','1')
(
23
秒)
分析:
---- where
条件中的
'in'
在
逻辑
上相当于
'or'
,所以
语
法分析器会将
in ('0','1')
转
化
为
id_no ='0' or id_no='1'
来
执
行。
我
们
期望它会根据
每
个
or
子句分
别查
找,再将
结
果相加,
这样
可以利用
id_no
上的索引;
但
实际
上(根据
showplan
)
,
它却采用了
"OR
策略
"