针对大数据量的表的sql查询缓慢的优化方法

在开发过程中,我感觉最难受的不是让你去开发一个新的功能,而是去优化一个现有的功能,好多时候都是在优化查询速度,在这里整理总结了一些方法,可供借鉴:

一.因为sql的不规范而引起的引擎放弃使用索引从而进行全盘扫描:

  • 在where子句中使用!=或者<>操作符

  • 在where子句中对字段进行判空处理,比如 select t.username,t.id from tb_user t where t.age=null;
    处理方法:
    可以将本来需要判空的字段设置为0;查询时改成 select t.username,t.id from tb_user t where t.age=0;

  • .在where子句中利用or作为连接条件,如果需要选为union all 来连接,例如:
    select id from t where num=10 or num=20
    改为:
    select id from t where num=10
    union all
    select id from t where num=20

  • 模糊查询时候前置百分号%,必要时可以考虑全文检索

  • 对于连续数值的查询尽量选用between而不要选择in和not in

  • 在where中使用参数,因为局部变量的解析是在sql的运行过程中,但是优化程序的访问计划的选择必须在编译时选择,但是此时变量的值还是未知的,并不能作为索引的选择输入项,如:
    select id from t where num=@num
    可以改为强制查询使用索引:
    select id from t with(index(索引名)) where num=@num

  • 在where子句中进行函数操作和表达式操作也会引起全盘扫描,如:
    select id from t where substring(name,1,3)=’abc’–name以abc开头的id
    select id from t where datediff(day,createdate,’2005-11-30′)=0–’2005-11-30′生成的id
    应改为:
    select id from t where name like ‘abc%’
    select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′

  • 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

  • 尽量多使用COMMIT,只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少,COMMIT所释放的资源:回滚段上用于恢复数据的信息、 被程序语句获得的锁、 redo log buffer 中的空间。Oracle为管理上述3种资源中的内部花费。

二.索引的选择需要慎重考虑

  • 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
  • ||'是字符连接函数。就象其他函数那样, 停用了索引。
  • 相同的索引列不能互相比较,这将会启用全表扫描。
  • 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使 用,并且应尽可能的让字段顺序与索引顺序相一致。
  • 需要明确的是索引并不是越多越好,一个表的索引数不建议超过6个,同时需要注意udate和insert时候可能会重建索引
    ,索引虽然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率。因为索引是有序的,如果插入的值是在原顺序的中间,则会引起索引的重排序,开销比较大,建议在插入大数据量的操作时候,首先使用索引失效,然后插入完成之后再使索引生效,上述这种情况是在表不能提供查询的同时也提供插入,可以考虑减少单个索引叶子块索引键值,这样树的高度可能会变的很大,空间也会变大,查找IO也会变大只是权衡之计,最好还是实现读写分离
  • 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。补充:如果一个页面已经写满了数据,你要插入一行,如果是非聚集索引,sql会随便找个地方保存,把地址记录进索引,但是如果是聚集索引,会把数据插入到这个页面,而后面的数据同时会往后移动(用页面拆分的办法),看上去速度要慢,但是聚集索引在搜索时,速度会比非聚集索引快,因为他们是物理排序的,其中主键约束默认是clustered的而唯一索引是noclustered(非聚集索引)
  • 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段 sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
  • 如果检索数据量超过30%的表中记录数,使用索引将没有显著的效率提高。 在特定情况下,使用索引也许会比全表扫描慢,但这是同一个数量级上的区别。而通常情况下,使用索引比全表扫描要块几倍乃至几千倍!

三.临时表、游标、触发器和存储过程的设置和优化

  • 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。这是因为游标是单行循环处理,逐行遍历,需要明确的是关系型数据库是面向集合的,但是游标则是面向单行数据处理的,游标的结果集实际上是由select语句产生,可以想象成每一条数据都是select查询出,并且根据有表达并发选项的设置,游标可以获取共享模式的滚动锁以保护提取。当需要滚动锁时,直到下一次提取或关闭游标(以先发生者为准)时才释放滚动锁。但是,如果指定 HOLDLOCK,则直到事务结束才释放滚动锁。

  • 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

  • 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

  • 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

  • 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引),临时表是利用了硬盘(tempdb数据库) ,表名变量是占用内存,因此小数据量当然是内存中的表变量更快。**当大数据量时,就不能用表变量了,太耗内存了。大数据量时适合用临时表。**表变量缺省放在内存,速度快,所以在触发器,存储过程里如果数据量不大,应该用表变量。临时表缺省使用硬盘,一般来说速度比较慢,那是不是就不用临时表呢?也不是,在数据量比较大的时候,如果使用表变量,会把内存耗尽,然后使用 TEMPDB的空间,这样主要还是使用硬盘空间,但同时把内存基本耗尽,增加了内存调入调出的机会,反而降低速度。这种情况建议先给TEMPDB一次分配合适的空间,然后使用临时表。临时表相对而言表变量主要是多了I/O时间,但少了对内存资源的占用。数据量较大的时候,由于对内存资源的消耗较少,使用临时表比表变量有更好的性能。建议:触发器、自定义函数用表变量;存储过程看情况,大部分用表变量;特殊的应用,大数据量的场合用临时表。表变量有明确的作用域,在定义表变量的函数、存储过程或批处理结束时,会自动清除表变量。在存储过程中使用表变量与使用临时表相比,减少了存储过程的重新编译量。涉及表变量的事务只在表变量更新期间存在。这样就减少了表变量对锁定和记录资源的需求。表变量需要事先知道表结构,普通临时表,只在当前会话中可用与表变量相同into一下就可以了,方便;全局临时表:可在多个会话中使用存在于temp中需显示的drop。(不知道表结构情况下临时表方便一些)全局临时表的功能是表变量没法达到的。表变量不必删除,也就不会有命名冲突,临时表特别是全局临时表用的时候必须解决命名冲突。应避免频繁创建和删除临时表,减少系统表资源的消耗。在新建临时表时,如果一次性插入数据量很大,那么可以使用select into代替create table,避免log,提高速度;如果数据量不大,为了缓和系统表的资源,建议先create table,然后insert。如果临时表的数据量较大,需要建立索引,那么应该将创建临时表和建立索引的过程放在单独一个子存储过程中,这样才能保证系统能够很好的使用到该临时表的索引。如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先truncate table,然后drop table,这样可以避免系统表的较长时间锁定。慎用大的临时表与其他大表的连接查询和修改,减低系统表负担,因为这种操作会在一条语句中多次使用tempdb的系统表。

  • 避免频繁创建和删除临时表,以减少系统表资源的消耗。

  • 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

  • 如果索引是建立在多个列上,只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引。这也是一条简单而重要的规则,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略 了索引。
    四.字段的选择问题

  • 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。关于数据类型和效率的关系请参考我的另一篇博客

  • 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

  • **用>=替代>:**高效:SELECT * FROM EMP WHERE DEPTNO >=4 低效: SELECT * FROM EMP WHERE DEPTNO >3两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记 录。

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值