数据库优化相关介绍
最近为了解决日志系统性能瓶颈的问题,我查询了一些数据库优化的资料,现对这几天的学习做一个总结。数据库相关的优化是一个非常麻烦而且复杂的事情,他涉及到的方面很多,我在这方面并没有很多的经验,所以我们需要通过不同途径来了解这方面的知识。
一. 优化SQL语句。
人们在使用SQL时往往会陷入一个误区,即太关注于所得的结果是否正确,而忽略了不同的实现方法之间可能存在的性能差异,这种性能差异在大型的或是复杂的数据库环境中(如联机事务处理OLTP或决策支持系统DSS)中表现得尤为明显。而不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的where子句。在对它们进行适当的优化后,其运行速度将会有明显地提高。下面是我收集到的一些关于SQL优化方面的介绍。
1.合理使用索引
索引是数据库中重要的数据结构,它的根本目的就是为了提高查询效率。现在大多数的数据库产品都采用IBM最先提出的ISAM索引结构。索引的使用要恰到好处,其使用原则如下:
●在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则由优化器自动生成索引。
●在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引。
●在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度。
●如果待排序的列有多个,可以在这些列上建立复合索引(compound index)。
●使用系统工具。如Informix数据库有一个tbcheck工具,可以在可疑的索引上进行检查。在一些数据库服务器上,索引可能失效或者因为频繁操作而使得读取效率降低,如果一个使用索引的查询不明不白地慢下来,可以试着用tbcheck工具检查索引的完整性,必要时进行修复。另外,当数据库表更新大量数据后,删除并重建索引可以提高查询速度。
2.避免或简化排序
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:
●索引中不包括一个或几个待排序的列;
●group by或order by子句中列的次序与索引的次序不一样;
●排序的列来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。
3.消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作:
SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008
虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:
SELECT * FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT * FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
4.避免相关子查询
一个列的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。
5.避免困难的正规表达式
MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗费时间。例如:SELECT * FROM customer WHERE zipcode LIKE “98_ _ _”
即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT * FROM customer WHERE zipcode >“98000”,在执行查询时就会利用索引来查询,显然会大大提高速度。
另外,还要避免非开始的子串。例如语句:SELECT * FROM customer WHERE zipcode[2,3] >“80”,在where子句中采用了非开始子串,因而这个语句也不会使用索引。
6.使用临时表加速查询
把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序操作,而且在其他方面还能简化优化器的工作。例如:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
AND cust.postcode>“98000”
ORDER BY cust.name
如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个临时文件中,并按客户的名字进行排序:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
ORDER BY cust.name
INTO TEMP cust_with_balance
然后以下面的方式在临时表中查询:
SELECT * FROM cust_with_balance
WHERE postcode>“98000”
临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘I/O,所以查询工作量可以得到大幅减少。
注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意不要丢失数据。
7.用排序来取代非顺序存取
非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。
有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。
8. 其它注意的细节
a).尽量避免反复访问同一张或几张表,尤其是数据量较大的表,可以考虑先根据条件提取数据到临时表中,然后再做连接。
b) 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该改写;如果使用了游标,就要尽量避免在游标循环中再进行表连接的操作。
C). 注意where字句写法,必须考虑语句顺序,应该根据索引顺序、范围大小来确定条件子句的前后顺序,尽可能的让字段顺序与索引顺序相一致,范围从大到小。
d). 不要在where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
e. 尽量使用exists代替select count(1)来判断是否存在记录,count函数只有在统计表中所有行数时使用,而且count(1)比count(*)更有效率。
f. 尽量使用“>=”,不要使用“>”。
h. 注意一些or子句和union子句之间的替换
i. 注意insert、update操作的数据量,防止与其他应用冲突。如果数据量超过200个数据页面(400k),那么系统将会进行锁升级,页级锁会升级成表级锁。
二. 优化MS SQL Server 2000。
3. MS SQL Server 2000 相关学习总结。
3.1系统表
名称
|
地址
|
说明
|
sysaltfiles
|
主数据库
|
保存数据库的文件
|
syscharsets
|
主数据库
|
字符集与排序顺序
|
sysconfigures
|
主数据库
|
配置选项
|
syscurconfigs
|
主数据库
|
当前配置选项
|
sysdatabases
|
主数据库
|
服务器中的数据库
|
syslanguages
|
主数据库
|
语言
|
sysmessages
|
主数据库
|
查看当前@@ERROR的消息
|
syslogins
|
主数据库
|
登陆帐号信息
|
sysoledbusers
|
主数据库
|
链接服务器登陆信息
|
sysprocesses
|
主数据库
|
进程
|
sysremotelogins
|
主数据库
|
远程登录帐号
|
syscolumns
|
每个数据库
|
列
|
sysconstrains
|
每个数据库
|
限制
|
sysfilegroups
|
每个数据库
|
文件组
|
sysfiles
|
每个数据库
|
文件
|
sysforeignkeys
|
每个数据库
|
外部关键字
|
sysindexs
|
每个数据库
|
索引
|
sysmenbers
|
每个数据库
|
角色成员
|
sysobjects
|
每个数据库
|
所有数据库对象
|
syspermissions
|
每个数据库
|
权限
|
systypes
|
每个数据库
|
用户定义数据类型
|
sysusers
|
每个数据库
|
用户
|
1. 查询当前系统中有那些数据库可以使用
Select * from master.dbo.sysdatabases
3.2系统存储过程
1. sp_help
报告有关数据库对象(sysobjects 表中列出的任何对象)、用户定义数据类型或 Microsoft® SQL Server™ 所提供的数据类型的信息
示例
A. 返回有关所有对象的信息
下面的示例列出有关 sysobjects 中每个对象的信息。
USE master
EXEC sp_help
B. 返回有关单个对象的信息
下面的示例显示有关 publishers 表的信息。
USE pubs
EXEC sp_help publishers
2. sp_helpfile
返回与当前数据库关联的文件的物理名称及特性。使用此存储过程确定附加到服务器或从服务器分离的文件名。
示例
下面的示例返回有关 pubs 中的文件的信息。
USE pubs
EXEC sp_helpfile
3.3全局变量
1.@@rowcount
返回受上一语句影响的行数。
示例
下面的示例执行 UPDATE 语句并用 @@ROWCOUNT 来检测是否有发生更改的行。
UPDATE authors SET au_lname = 'Jones'
WHERE au_id = '999-888-7777'
IF @@ROWCOUNT = 0
print 'Warning: No rows were updated'
2.@@ERROR
返回最后执行的 Transact-SQL 语句的错误代码。
示例:用 @@ERROR 检测几条语句的成功
下面的示例取决于 INSERT 和 DELETE 语句的成功操作。局部变量在两条语句后均被设置为 @@ERROR 的值,并且用于此操作的共享错误处理例程中。
USE pubs
GO
DECLARE @del_error int, @ins_error int
-- Start a transaction.
BEGIN TRAN
-- Execute the DELETE statement.
DELETE authors
WHERE au_id = '409-56-7088'
-- Set a variable to the error value for
-- the DELETE statement.
SELECT @del_error = @@ERROR
-- Execute the INSERT statement.
INSERT authors
VALUES('409-56-7008', 'Bennet', 'Abraham', '415 658-9932',
'6223 Bateman St.', 'Berkeley', 'CA', '94705', 1)
-- Set a variable to the error value for
-- the INSERT statement.
SELECT @ins_error = @@ERROR
-- Test the error values.
IF @del_error = 0 AND @ins_error = 0
BEGIN
-- Success. Commit the transaction.
PRINT "The author information has been replaced"
COMMIT TRAN
END
ELSE
BEGIN
-- An error occurred. Indicate which operation(s) failed
-- and roll back the transaction.
IF @del_error <> 0
PRINT "An error occurred during execution of the DELETE
statement."
IF @ins_error <> 0
PRINT "An error occurred during execution of the INSERT
statement."
ROLLBACK TRAN
END
GO
3.@@IDENTITY
返回最后插入的标识值
下面的示例向带有标识列的表中插入一行,并用 @@IDENTITY 显示在新行中使用的标识值。
INSERT INTO jobs (job_desc,min_lvl,max_lvl)
VALUES ('Accountant',12,125)
SELECT @@IDENTITY AS 'Identity'
查看当前错误码对应的描述信息。
select [description] from master.dbo.sysmessages where error = @@ERROR
3.4 常用SQL
1.得到数据库中所有用户表
Select [name] from sysObjects Where xtype='U'and [name]<>'dtproperties' Order By [name]
2.得到数据库中所有用户视图
Select [name] From sysObjects Where xtype='V' And [name]<>'syssegments' And [name]<>'sysconstraints' Order By [name]
3.获得指定表中所有的列
Select c.name As ColumnName, t.name As TypeName From syscolumns c, systypes t, sysobjects o
Where c.xtype = t.xusertype And c.id = o.id And o.name = 't_LogData' Order By c.colorder
4.获得表中所有列的详细信息
Select ColOrder = col.colorder, --排序号
ColumnName = col.name, --列名
TypeName = type.name,--数据类型名称
Length = (Case When type.name='nvarchar' Or type.name='nchar' Then col.length/2 Else col.length End), --长度
[PRECISION] = COLUMNPROPERTY(col.id, col.name, 'PRECISION'), --精度
Scale = ISNULL(COLUMNPROPERTY(col.id, col.name, 'Scale'), 0), --小数
IsIdentity = Case When COLUMNPROPERTY(col.id, col.name, 'IsIdentity')=1 Then '√' Else '' End, --是否为自动编号列
IsPK = Case When Exists(Select 1 From sysobjects Where xtype = 'PK' And name In (
Select name From sysindexes Where indid In (
Select indid From sysindexkeys Where id = col.id And colid = col.colid
)
)
) Then '√' Else '' End, --是否为主键
AllowNull = Case When col.isnullable=1 Then '√' Else '' End, --是否允许为空
DefalutValue = isnull(com.text, '') --默认值
From syscolumns col
Left Join systypes type On col.xtype = type.xusertype
Inner Join sysobjects obj On col.id = obj.id And (obj.xtype = 'U' Or obj.xtype = 'V') And obj.name <> 'dtproperties'
Left Join syscomments com On col.cdefault = com.id
Where obj.name = t_LogData
5. 判断是数据库中是否存在某数据库
If exists (select * from master.dbo.sysdatabases where name =N'db_Logsystem')
Print 'exist'
Else
Print 'ok'
显示数据库中所有的表名:select * from sysobjects where xtype='u'
显示数据库中的所有用户表名:select * from sysobjects where xtype='u' and status>0
显示数据库中的所有视图名:select * from sysobjects where xtype='v'
显示数据库中的所有用户视图名:select * from sysobjects where xtype='v' and status>0
显示数据库中的所有存储过程名:select * from sysobjects where xtype='p'
显示数据库中的所有用户存储过程名:select * from sysobjects where xtype='p' and status >0
6. Set NOCOUNT ON|OFF
7. 在测试一条SQL语句时需要使用下面这条语句显示测试
SET STATISTICS IO on
SET STATISTICS TIME on
select * from sysobjects
执行结果:
SQL Server 执行时间:
CPU 时间 = 0 毫秒,耗费时间 = 0 毫秒。
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,耗费时间 = 0 毫秒。
(所影响的行数为 72 行)
表 'sysobjects'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,耗费时间 = 13 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,耗费时间 = 47 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,耗费时间 = 47 毫秒。
SQL Server 分析和编译时间:
CPU 时间 = 0 毫秒,耗费时间 = 0 毫秒。
SQL Server 执行时间:
CPU 时间 = 0 毫秒,耗费时间 = 0 毫秒。
|
8.通配符的一些使用方法:
通配符的一些用法:(关键字:like % [] -)
select * from tablename where column1 like '[A-M]%'
这样可以选择出column字段中首字母在A-M之间的记录
select * from tablename where column1 like '[ABC]%'
这样可以选择出column字段中首字母是A或者B或者C的记录
select * from tablename where column1 like '[A-CG]%'
这样可以选择出column字段中首字母在A-C之间的或者是G的记录
select * from tablename where column1 like '[^C]%'
这样可以选择出column字段中首字母不是C的记录
脱字符(关键字:like _)
通过使用下滑线字符(_),可以匹配任意单个字符
select * from tablename where column1 like 'M_crosoft'
匹配特殊字符:([ ] _ - %)
把他们都放到[]中就行了,比如:
select * from tablename where column1 like '%[%]%'
匹配发音(关键字:SOUNDEX DIFFERENCE)
如果不知道一个名字确切的发音,但是又多少知道一点,可以考虑使用SOUNDEX DIFFERENCE函数。
select * from tablename where DIFFERENCE(column1,'Laofei'>3)
DIFFERENCE返回0-4之间的数字,4是非常接近,0是差异非常大
要深入了解DIFFERENCE函数的工作原理,使用SOUNDEX函数返回DIFFERENCE函数所使用的音标码
select column1 as column,SOUNDEX(column1) 'sound like'
注意:
DIFFERENCE函数比较两个字符串的第一个字母和所有的辅音字母,该函数忽略任何元音字母(包括Y),除非元音字母是该字符串的第一个字母。
使用这两个函数在where中执行效果并不好,所以尽量少使用。