为(wéi)了(le)获(huò)得稳定(dìng)的(de)执行(háng)性能,SQL语句越简单(dān)越(yuè)好。对复杂的SQL语句,要设法对之进行简化,本文给大家介(jiè)绍优化SQL语句提高数据库性能。
现在数据越来越复杂和庞大,很多(duō)时候影响程序运行性能(néng)不理想的原(yuán)因(yīn)中除了一部(bù)分是因为(wéi)应用(yòng)程序的负载确(què)实超过了(le)服务器的(de)实际处理能力外(wài),更多的是因为系统存在大量的SQL语句需要优化。
一、问题的提出
在项目(mù)实际使用中,数据是一(yī)个长期累计的过程(chéng),随着数(shù)据库中数(shù)据的增加,系统的响(xiǎng)应速度就(jiù)成为目前系统需要(yào)解决的最(zuì)主要的问(wèn)题(tí)之一。系统优化(huà)中一个(gè)很(hěn)重要的方(fāng)面就是SQL语句(jù)的优化。对于海量(liàng)数据,劣质SQL语句(jù)和优(yōu)质SQL语句之间的速度差(chà)别可以(yǐ)达到成(chéng)千上百倍,因此(cǐ)高质量的SQL语句(jù),更(gèng)能(néng)提高系统的可用性。
二、SQL语句编(biān)写注意(yì)问题(tí)
下面就某些(xiē)SQL语句的where子句编写中(zhōng)需要注意的(de)问题作详(xiáng)细介绍。在这(zhè)些where子句中,即使某些列存在(zài)索引,但是由于编写了劣质的SQL,系统在运行该(gāi)SQL语句时(shí)也(yě)不能使(shǐ)用该索引,而同样使(shǐ)用全表扫描,这就造成了响应速(sù)度的极大降(jiàng)低。
1. 操作符(fú)优化
(a) IN 操(cāo)作符
在使(shǐ)用中(zhōng)尽量用(yòng)EXISTS替代IN、用NOT EXISTS替代(dài)NOT IN 。
在许多基(jī)于基础表的查询中,为了满(mǎn)足(zú)一个条件,往往(wǎng)需要对另一个表进行联接。在这种情况下, 使用EXISTS(或(huò)NOT EXISTS)通(tōng)常将提高查询的效率。。在子查(chá)询中,NOT IN子句将执(zhí)行一个内部的排序(xù)和(hé)合(hé)并。 无(wú)论在哪种情况下,NOT IN都是最(zuì)低效(xiào)的 (因为(wéi)它对子查询(xún)中的表执行了一个全表(biǎo)遍历)。。为(wéi)了(le)避(bì)免使用NOT IN ,我(wǒ)们(men)可以把它(tā)改(gǎi)写成外连接(Outer Joins)或NOT EXISTS。
例子:
(推荐)select* from dt_article where exists(select id from dt_article_category wheredt_article_category。id=dt_article。category_id andtitle='公司新闻(wén)')
(不(bú)推荐)select* from dt_article where category_id in (select id from dt_article_categorywhere title='公司新闻')
(b) IS NULL 或IS NOT NULL操(cāo)作(判(pàn)断字段是否为空)
判断字段是否为空一般是不会(huì)应用索引的,因为索引是(shì)不索引空值的。不能用null作索引,任何包含null值的列都将不(bú)会(huì)被包含在索引中。即(jí)使索引有多列这样的(de)情(qíng)况下(xià),只要这些列中(zhōng)有一(yī)列含有null,该列就会从索引中排除。也就是说(shuō)如果(guǒ)某列存在空值,即使对该(gāi)列建索引也不会提高(gāo)性(xìng)能。任何在(zài)where子句中使用(yòng)is null或is not null的语句优化器是不允(yǔn)许使用(yòng)索(suǒ)引的。
例(lì)子:
(推荐)select* from dt_article where title>'';
(不推荐)select* from dt_article where title is null;
(c) > 及 < 操(cāo)作符(大于或小(xiǎo)于操作符)
(推荐)select * from dt_article where id>=101;
(不推荐(jiàn))select * from dt_article where id>100;
两者的区别在(zài)于, 前者(zhě)将直接跳到(dào)第一个(gè)id等于101的记(jì)录而(ér)后者将(jiāng)首先定位到id=100的记录并且向前扫描到第一个id大于100的(de)记录。
(d)LIKE操作符
LIKE操作符可以(yǐ)应用(yòng)通配符查询,里面的通配符组合可能达到几乎是任(rèn)意的查询,但是如果(guǒ)用得不好则(zé)会产生性能上的问题,如like '%福瑞希%'这种查(chá)询不会(huì)引(yǐn)用(yòng)索引(yǐn),而like'福瑞希%'则会引用范(fàn)围索引。
一个实际(jì)例子:用dt_article表中内容可(kě)来查询, content like'%福(fú)瑞希%'这个条件会产(chǎn)生全(quán)表扫描,如果改成contentlike '福瑞希%'则会利用content的(de)索(suǒ)引进行(háng)范围的查询,性(xìng)能(néng)肯定(dìng)大(dà)大提高。
在很多情况下可能(néng)无法避免(miǎn)这(zhè)种情况,但是一定要心中有底,通配符(fú)如此使用(yòng)会降低查询(xún)速度。然而(ér)当通配符出现(xiàn)在字(zì)符(fú)串其(qí)他位(wèi)置时,优(yōu)化(huà)器就能利用索引(yǐn)。
(e) UNION操作符
当SQL语句需要UNION两个查询结果集合时,这两个结果集(jí)合会以UNION-ALL的方式(shì)被合并, 然(rán)后(hòu)在输(shū)出最终结果(guǒ)前(qián)进行去重和排序。 假如用UNION ALL替代UNION, 这样排序就不是必要了。 效率就会因此得(dé)到提高。 需要注重(chóng)的是,UNION ALL 将重复输出(chū)两个结(jié)果集(jí)合中相(xiàng)同记录(lù)。 因此各位还是要从业务需求分析使用(yòng)UNIONALL的可行性。 UNION 将对结果集合去重排序,这个操(cāo)作会使用到SORT_AREA_SIZE这块内存。 对于这块(kuài)内存(cún)的优化也是(shì)相当重要的。
(f) NOT
我们要(yào)避免在索引列上使用(yòng)NOT, NOT会产生在(zài)和(hé)在(zài)索引列(liè)上使(shǐ)用函数(shù)相同的影响。 当查询列碰到”NOT,他就(jiù)会停止使用索(suǒ)引转而执行全表扫描。
(g) OR
通(tōng)常情况下, 用UNION替换WHERE子句中的OR将会起(qǐ)到较(jiào)好的效果。 对索引列使用OR将造成全(quán)表(biǎo)扫描(miáo)。 注重, 以上规则只针对(duì)多个(gè)索(suǒ)引列有效。 假如(rú)有column没有被索(suǒ)引, 查询效率可能会因为你没有选择OR而降低。 在(zài)下面的例(lì)子中(zhōng), title和category_id上都建有索引(yǐn)。
(推荐)select * from dt_article where title='清洗空气' union all select * from dt_article where category_id=92
(不推荐(jiàn))select * from dt_article where title='清洗(xǐ)空气' or category_id=92 假如你坚持要(yào)用OR, 那就需(xū)要返回记(jì)录(lù)最少(shǎo)的索引(yǐn)列写在最前面。
另(lìng)外(wài)在一些情况(kuàng)下,也可(kě)以(yǐ)使用IN来替代OR, 这是一条简单易记(jì)的规则,但是实际的(de)执行效(xiào)果(guǒ)还须检验。
(推荐)select * from dt_article where category_id in (89,92)
(不(bú)推荐)select * from dt_article where category_id=92 or category_id=89
(h) DISTINCT
当提交一个包(bāo)含(hán)一对多(duō)表(biǎo)信息的查询时,避免在SELECT子(zǐ)句中使(shǐ)用(yòng)DISTINCT。 一般可以考(kǎo)虑用EXIST替换(huàn), EXISTS 使查询(xún)更(gèng)为迅速,因为RDBMS核心模块将(jiāng)在子查询的条(tiáo)件(jiàn)一旦(dàn)满足后,马上返回(huí)结果。
2. SQL书写(xiě)的影响
(a) WHERE后面的(de)条件(jiàn)顺(shùn)序影响
WHERE子句后面的(de)条件顺(shùn)序对大数据(jù)量表的查询会(huì)产生直接的影响。如:
select * from dt_article where category_id=92 and is_hot=1
select * from dt_article where is_hot=1 and category_id=92
以上(shàng)两(liǎng)个SQL中category_id(电压等(děng)级)及is_hot(销户标志)两个字(zì)段(duàn)都(dōu)没进行索引(yǐn),所以执行的(de)时候(hòu)都是全(quán)表(biǎo)扫描,第一条SQL的is_hot=1在(zài)记(jì)录集内比率为99%,而category_id=92的比率只为1%,在进行第一条SQL的(de)时候99%条(tiáo)记录都进行category_id及is_hot的比较,而在进行第二条SQL的时候1%条记录都进行(háng)category_id及is_hot的比较(jiào),以此可以得出第二条SQL的(de)CPU占用(yòng)率明显比第(dì)一(yī)条低。
WHERE解析(xī)是(shì)采(cǎi)用自下而上的顺序解析WHERE子(zǐ)句,根据这个原理,表之间(jiān)的连接必须写(xiě)在(zài)其他WHERE条件(jiàn)之前, 那(nà)些(xiē)可(kě)以过滤(lǜ)掉最大数量记录的条件(jiàn)必(bì)须写在WHERE子句的(de)末尾。
3. 更多方面SQL优化资料分享
(1) 选择最有效率的表名顺序(xù)(只在基于(yú)规则的优化器中有效):
ORACLE 的解析器按照从右到左的顺序处(chù)理FROM子句中的(de)表名(míng),FROM子句(jù)中写在最(zuì)后(hòu)的(de)表(基(jī)础(chǔ)表 driving table)将(jiāng)被最先处理(lǐ),在FROM子句中包含(hán)多(duō)个(gè)表的情况下,你必须选择记录条数(shù)最少的表(biǎo)作为基础表。如果有3个(gè)以上的表连接(jiē)查询(xún), 那就需(xū)要选择交叉表(intersectiontable)作(zuò)为基础表, 交叉表(biǎo)是指(zhǐ)那个被其他表所引用的表.
(2) SELECT子句中避免使用 ‘ * ‘:
ORACLE在解析的过程中, 会将'*' 依次转换(huàn)成所有的列名, 这个工作是通过查询(xún)数据字典完成的, 这意味(wèi)着将耗费更多的时间。
(3) 减少访(fǎng)问数据库的(de)次数:
ORACLE在内部执行了许多(duō)工作(zuò): 解析SQL语句, 估算索(suǒ)引(yǐn)的利用率, 绑定变量 , 读数(shù)据(jù)块等。
(4) 整合简单,无(wú)关联的数据库访问(wèn):
如(rú)果(guǒ)你有几个简单的数据库查询语(yǔ)句(jù),你可以把(bǎ)它们整合到一个查询中(即使它们之间没有关(guān)系) 。
(5) 用TRUNCATE替代DELETE:
当删除表(biǎo)中的记录时,在(zài)通常情况下(xià), 回(huí)滚段(rollbacksegments ) 用(yòng)来存(cún)放可(kě)以被恢复的(de)信(xìn)息. 如果你没有COMMIT事务,ORACLE会将数据(jù)恢复到删除之前的状态(tài)(准确地(dì)说是恢复到执行删除命令(lìng)之(zhī)前的状况) 而当运用TRUNCATE时, 回滚段(duàn)不(bú)再存放任何可(kě)被(bèi)恢复的信息.当命令运行后,数据不能被恢复.因(yīn)此很少的资源被调用(yòng),执(zhí)行时间(jiān)也(yě)会很(hěn)短(duǎn). (译者按: TRUNCATE只在删除全表适(shì)用(yòng),TRUNCATE是DDL不是DML) 。
(6) 尽量多使用COMMIT:
只要有可能,在(zài)程序中尽量多使(shǐ)用COMMIT, 这(zhè)样程序(xù)的性(xìng)能得(dé)到提高,需求也会(huì)因为COMMIT所释(shì)放的资源而减少,COMMIT所释(shì)放的资源:
a. 回滚段上用于恢复数据的信息.
b. 被程序语(yǔ)句获(huò)得的(de)锁
c. redo log buffer 中的空间
(7) 通过内部函数(shù)提高SQL效率(lǜ):
复杂的SQL往往(wǎng)牺牲了执行(háng)效率(lǜ). 能够掌握上面的运用函数解决问题(tí)的方法在实际工(gōng)作中是非(fēi)常有意义的。
(8) 使(shǐ)用表的别名(Alias):
当在SQL语(yǔ)句中连接多个表时(shí), 请使(shǐ)用(yòng)表的别名并把别名前缀于每个Column上.这样一来,就可以减(jiǎn)少(shǎo)解析的时(shí)间并减(jiǎn)少那些由Column歧义引起的语法(fǎ)错误(wù)。
(9) 总是使用索引的第一(yī)个列:
如果(guǒ)索引是建立在(zài)多个(gè)列上, 只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该(gāi)索引(yǐn). 这也是一条简单而重(chóng)要(yào)的规(guī)则(zé),当仅引用索引(yǐn)的第二个列时,优化器使用了全表扫描而忽略(luè)了索引。
(10) 避(bì)免使用耗费资源的操作(zuò):
带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能. DISTINCT需要一次排序操作, 而(ér)其他的至少需要执行(háng)两次排序(xù). 通常, 带有(yǒu)UNION, MINUS , INTERSECT的SQL语(yǔ)句都可以用其他方式重写. 如果你的数据库的SORT_AREA_SIZE调配得(dé)好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强。