SQL语句优化原则
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
数据库性能是整个应用程序性能的重要部分。数据库优化涉及的内容非常广泛,各类数据库都提供众多的性能指标和大量的优化工 具。 下面我们简单介绍一下优化的基本概念。一个运行良好的数据库至少应具有以下特点: 合理的物理结构及硬件能力 1、适量的数据冗余,提高数据安全性。 2、平衡磁盘IO,增强数据读写能力。 而足够的硬件能力的作用自然不言而喻。一旦建立好数据库系统并开始运行,数据库的物理结构就不能改变。 合理的系统参数 性能优良的sql语句 同时,提高sql语句的执行效率,是提高整个数据库性能的最立竿见影且价格低廉的方法之一。因为几乎所有的数据库都会不可避免的运行一些效率低下的 sql语句。对数据库性能的调整,往往都是从sql语句调优开始的。 下面我们简单介绍数据库是怎么执行sql语句的。 sql语句是唯一从应用程序发送到数据库实例的命令。数据库实例所做的全部工作就是接受、解释和执行sql 语句。 在绝大多数情况下,我们并不需要关心sql语句是怎么执行的,这是因为在当前流行的数据库软件中都无一例外的采用了高性能的优化器,而这些优化器在 绝大多数的情况下都能将用户某些不合理的sql语句结构转换成更合理的形式,从而有效提高sql的执行效率。 优化器的优化原理有两种: 基于成本的优化 基于规则的优化 /*执行计划的概念:简单的说执行计划就是指在执行sql语句前对代码进行编译时数据库实例为sql选择的执行路径。如一个sql对A、B、C三个 表进行联合查询,数据库会首先以某种方式对这3个表种符合条件的记录进行查询(全表扫描或其他),再将A、B、C表中符合条件的记录读入内存,将A、B表 的记录相比较后得出的结果集与C表相比较,最后得到符合的结果集。但实际情况比这复杂的多。*/ oracle可以选择两种优化模式之中的一种,而其他数据库则是固定的(DB2不清楚,关于DB2的资料太少了),基本都是基于规则的优化。 然而优化器毕竟不是智能的。很多时候,它不可避免的受到sql语句结构的影响。而SQL优化的实质就是在结果正确的前提下,用优化器可以识别的语 句,充份利用索引,减少表扫描的I/O次数,尽量避免表扫描的发生。 sql的书写原则 以下介绍的标准sql的书写原则是本文的核心,这些原则适用于绝大部分数据库。介绍这些原则的时候,我会举一些简单的例子来说明,但这些例子在实际 生产中可能没有什么实际意义。 1、使用索引 总的来说索引可以分为聚集索引和非聚集索引。一个表中只能有一个聚集索引。对一个表建立一个聚集索引后,数据库会调整表记录的顺序,使表按照索引的 顺序重新排列。而非聚集索引则不会改变表的结构。 使用的最常见的索引就是主键索引。实际上在表中指定一列为主键的时候,就在这列上建立了唯一值索引并强制该列的值唯一,这就成了主键索引。 在下面几种情况下,应该建立索引: 1. 有大量重复值、且经常有范围查询(between,>,<,>=,<=)和order by、group by发生的列,可以考虑使用聚集索引; 2. 在使用最频繁的列且常常需要使用函数的时候,可以考虑使用函数索引。 3. 在使用最频繁的列且常常需要对两个或多个列进行联合查询的时候,可以考虑使用组合索引。 4. 在重复率较低的列上建立唯一索引。 索引虽有助于提高性能,但不是索引越多越好,恰好相反过多的索引会导致系统低效。因为用户在表中每加进一个索引,维护索引集合就要做相应的更新工 作,同时每次有数据改变的时候就需要维护索引。 因此尽管使用索引非常有必要,但是在以下情况下,我们并不赞成使用索引。 1、 大量对表数据的修改(插入过删除) 2、 数据量很小 关于怎么使用索引,就是多使用存在索引的列作为查询条件。但是使用了带索引的列,也不一定就使用了索引,后面会提到相应的知识。 2、和任何高级语言一样,sql也是越简单越好。 select TOP $size$ BLOG_ID AS blogID, BLOG_DOMINO AS blogDomino, BLOG_TEMPLATE_ID AS blogTemplateID from blog where BLOG_ID NOT IN (select BLOG_ID from blog where BLOG_ID>=$size$+$fromID$) AND LOCKED=1 orDER BY BLOG_ID DESC; 通过查看执行计划,我们可以看到执行查询的步骤: 1、子查询中使用blog.pk_blog(主键索引)查询BLOG中满足blog>=$size$+$fromID$的记录,这步占总成本 的46%。 2、使用blog.pk_blog(主键索引),查询BLOG中满足LOCKED=1的记录。这步占总成本的44%。 3、使用Merge join。这步占总成本的10%。总成本为0.23。 我们可以看到两次对blog表的查询都使用了blog_id上的主键索引,同时表连接消耗的资源也比较少。应该说,对于子查询结构,这个查询的效率 并不低。但是,这和下面的语句完全是等价的: select TOP $size$ BLOG_ID AS blogID, BLOG_DOMINO AS blogDomino, BLOG_TEMPLATE_ID AS blogTemplateID from blog where BLOG_ID<$size$+$fromID$ AND LOCKED=1 orDER BY BLOG_ID DESC; 总成本为0.0035。 优化器不是人工智能,在第一个查询中指定使用子查询结构,优化器就无法突破这个限制,尽管两个查询是相同的,优化器还是不能将第一种查询结构等价的 转换成第二种。所以查询语句使用的结构对优化器影响重大,我们在进行多表查询等复杂情况的时候的时候(如可能需要使用联接查询、子查询、嵌套查询、 group by等),应多进行一些考虑。一个原则是结构要尽量简单,这样在编译效率、执行效率和程序可读性方面都有好处。 3、谨慎使用is null和is not null 4、in和exists select ProductName from Northwind.dbo.Products where UnitPrice in (select UnitPrice from Northwind.dbo.Products where ProductName = ‘Sir Rodney’’s Scones’) select ProductName from Northwind.dbo.Products b where exists (select UnitPrice from Northwind.dbo.Products a where a.ProductName = ‘Sir Rodney’’s Scones’ and a.UnitPrice=b.UnitPrice) 查看执行计划可以看到,上面两个语句的执行路径和执行成本都完全一样。 5、in和or select blog_name from blog where blog_name like ‘AB%’ or blog_name like ‘CD%’ or blog_name like ‘EF%’; select blog_name from blog where blog_name in (‘AB%’,’CD%’,‘EF%’); 6、查询字段要需要多少,查询多少 7、将行和操作数减到最少 where KeyColumn < ‘TestValue’ AND KeyColumn > ‘TestValue’8、尽量少用格式转换,防止出现隐含的格式转换。 某个表中有一个时间字段time date型,现在开发人员需要写一个sql:查询出在2004-04-21到2005-04-21之间,注册了多少blog新用户。我们比较下面两个语 句: select * from blog where to_char(create_time,yyyymmdd) between ’20040421’and ’20050421’; ――将time转换成字符型,和’20040421’及’20050421’做比较。 select * from blog where create_time<=to_date(‘2004-04-21’,’yyyy-mm-dd’) and create_time<=to_date(‘2005-04-21’,’yyyy-mm-dd’); ――将20040421及20050421转换成date型,和create_time做比较。 (PL/SQL) 第二个语句的性能明显优于第一个。这是因为第一个sql数据库必须将每一行记录的create_time都转换一次,而第一个只需要对常量做一次转 换。(第二个语句的性能明显优于第一个的另一个重要原因就是第一个查询不能使用create_time上的索引)。 隐含的格式转换既是数据库在必要时会自动将一个数据类型转换成另一种可兼容的数据类型: 最常见的情况,一个字段sort_id varchar(10)型,现在要查询出所有sort_id为3的用户。 select * from blog where sort_id=3; 如果用户没有查看表结构,很可能认为sort_id是int型而写出上面的语句。同时,数据库不会报错,这是因为编译引擎自动做了一次数据类型转 换。这就是一个隐藏转换的典型例子。在查询大量数据的时候,这样不必要的转换会降低sql的效率。 9、带通配符(%)的like语句 select * from blog where blog_name like ‘%我%’; 这里由于通配符(%)在搜寻词首出现,所以Oracle将不使用blog_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有 底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用: select * from blog where blog_name like ‘c%’; 对于sqlserver,这种情况没有影响。 10、ORDER BY和GROPU BY select * from blog order by blog_name; 由于聚集索引一个表中只能有一个,因此在无法使用聚集索引的情况下,使用其他索引也能提高查询效率。 11、禁止进行列运算 select * from blog where blog_id=3; 观察执行计划,发现使用了索引blog.PK_blog,整个查询消耗了cpu time:0.00008;I/O成本:0.0032。预计成本:0.0032。而如果改成: select * from blog where cast(blog_id as char)=’3’; ――将blog_id转换为char型后与’3’相比较。 观察执行计划,发现使用了索引blog.IX_plogs_blog_id(这里使用这个索引的原因是由于无法使用 blog.PK_blog,sqlserver退而求其次,使用了建立在plogs_blog_id字段上的索引),整个查询消耗了cpu time:0.264;I/O成本:0.345。预计成本:0.61。性能下降了200倍! 同样,应将类似:where blog_id*1000>28847;的语句改为:where blog_id>28.847; 12、使用表提示 由于使用表提示有时反而会降低执行效率,因此,只有当我们发现一些sql在低效运行且认为有必要干预sql执行计划的时候(可能已经严重影响了应用 程序的运行)才使用。同时需要通过反复调试来达到最佳的效果。 使用表提示的方法:Oracle中使用hint提示,sqlserver中使用option子句。 13、慎用游标 14、关于多表连接和子查询 (T-sql。使用的是northwind示例数据库中的表) 子查询和不包括子查询但语义上等效的语句在性能方面通常没有区别。但是,在一些必须检查存在性的情况中,使用联接会产生更好的性能。否则,为确保消 除重复值,必须为外部查询的每个结果都处理嵌套查询。所以在这些情况下,联接方式会产生更好的效果。如下面两个等效的sql: select distinct a.blog_name from blog a where exists( select b.blog_id from blog_note b where a.blog_id=b.blog_id And b.create_time<’2005-04-25’ ); select distinct a.blog_name from blog a,blog_note b where a.blog_id=b.blog_id And b.create_time<’2005-04-25’; 分析结果: 查询执行计划可以看到执行成本:语句一:使用exists子查询,总成本0.0567语句二:联接查询,总成本0.0535。 15、增加执行计划的重复使用率 第一个用户: select * from blog where blog_id=1; 第二个用户查询的是: select * from blog where blog_id=455; sql文本的变化导致数据库认为这是两个不同的查询,无法重复使用执行计划。为了提高执行计划的重复使用率,建议: 1、 使用对象(如表和视图)的完全合法名称。如: select * from blogctynnd.dbo.blog; 2、 oracle强烈建议在应用程序中大量使用“绑定变量”,如下: 将:select * from blog where blog_id=1; 改为: SQL>declarevid int default 1;res varchar(4);beginexecute immediate ’select * from blog where blog_id =:x’ into res using vid;dbms_output.put_line(res); –oracle系统包,用于输出。end;/(PL/SQL。这一招的用意很明显就是 固定sql文本,把编译器骗过去,有点瞒天过海的意思。其他数据库使用绑定变量的方法也是一样的,只是使用的语句稍有不同。) 对于sqlserver来说使用绑定变量意义不大,因为sqlserver数据库引擎可以识别出上面给出的例子,绑定变量具有一定的优势,但并不明 显。 另外需要说明的是在优化器内部对执行计划会存在一个衰减列表,到一定时间后执行计划还没有被重复使用,就会被清除出这个列表,这时再执行sql语句 就必须重新编译了。 其实SQL的性能优化是一个复杂的过程,上述这些只是在应用层次的一种体现,深入研究还会涉及数据库层的资源配置、网络层的流量控制以及操作系统层 的总体设计。另外,不同的数据库使用的优化原则不同,导致对同样的sql语句,做出完全不同的执行计划。即使是同样的数据库、同样的语句,在数据库运行的 不同时期执行效率也会发生很大的变化。因此,对数据库的优化更多的时候是对数据库的观察和调试,而不是教条式的简单修改。有兴趣的同事可以使用下面的方式 查看sql语句的执行计划,而执行计划反映了此刻sql语句的执行过程,使用的资源等等,是sql语句效率高低的量化数据。 oracle: sql>@rdbms/admin/utlxplan.sql――运行utlxplan.sql脚本,会自动创建一个plan_table。 这步只需要做一次。建立后每次进入sqlplus时: sql>set autotrace on 这时每次执行sql,都会显示出相应的执行计划。 Sqlserver: 在sqlserver中查看执行计划非常简单:进入查询分析器,在窗体中输入T-sql语句。点击【查询预计的执行计划】(该按钮在切换数据库下拉 菜单的左边。或ctrl+l)。这时执行计划就会显示在窗体下端。 其他数据库查看执行计划的方式如有需要可以上网查找。欢迎多交流。 后记 对于现有的每一种数据库来说,标准SQL的功能显然太弱了。因此,每一种数据库都对标准SQL进行了性能扩展。对于oracle是PL/SQL。 Sqlserver是T-sql。 在本文中列举的例子,并不一定在你的数据库中也能成功执行。同时,本文中的某些原则,可能对于特定数据库、特定优化模式下的sql性能并不能起到明 显改善的作用。 比如在使用通配符的例子中: select * from employee where last_name like ‘%cliton%’; 对于oracle,将无法使用last_name上的索引,导致这个查询会发生全表扫描。而对于sqlserver来说,仍然可以正常使用索引。 尽管如此,在开发过程中对一些小细节的注意,不仅可以保证在大多数数据库中有较高的执行效率,还可以使sql语句在今后数据库运行的过程中或某些特 殊情况下(如系统迁移),也能长期保持稳定的状态。 该文章在 2023/3/7 23:59:39 编辑过 |
关键字查询
相关文章
正在查询... |