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