SQL性能优化整理

思路:

SQL优化我们从以下三个方面进行:
1、数据库设计;
2、索引设计;
3、查询优化;

一、数据库设计

1.尽可能在数据库设计时,不要使用NULL,尽可能使用NOT NULL;

备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, NULL不占用空间。

2.尽可能使用VARCHAR/NVARCHAR 代替CHAR/NCHAR;

首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

3.尽可能使用数字型字段,若只含数值信息的字段尽量不要设计为字符型;

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

4.使用utf8mb4字符集;

二、索引设计

1.选择唯一性索引

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。

2.为经常需要排序、分组和联合操作的字段建立索引

经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。

3.为常作为查询条件的字段建立索引

如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。

4.限制索引的数目

索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间,一般不超过6个。

5.尽量使用数据量少的索引

如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。

6.尽量使用前缀来索引

如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。

7.删除不再使用或者很少使用的索引

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。

8 . 最左前缀匹配原则,非常重要的原则。

MySQL会一直向右匹配直到遇到范围查询(>、 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

9 .=和in可以乱序。

比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

10 . 尽量选择区分度高的列作为索引。

区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录

11 .索引列不能参与计算,保持列“干净”。

比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);

12 .尽量的扩展索引,不要新建索引。

比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。

13.理解单列索引和多列索引的使用

(1)创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。
(2)如果很可能仅对一个列多次执行搜索,则该列应该是复合索引中的第一列。如果您很可能对一个两列索引中的两个列执行单独的搜索,则应该创建另一个仅包含第二列的索引。
(3)包含多个列的主键始终会自动以复合索引的形式创建索引,其列的顺序是它们在表定义中出现的顺序,而不是在主键定义中指定的顺序。在考虑将来通过主键执行的搜索,确定哪一列应该排在最前面。请注意,创建复合索引应当包含少数几个列,并且这些列经常在select查询里使用。在复合索引里包含太多的列不仅不会给带来太多好处。而且由于使用相当多的内存来存储复合索引的列的值,其后果是内存溢出和性能降低。
(4)复合索引只对和索引中排序相同或相反的order by 语句优化。

三、查询优化

(1)首先需要了解SQL的执行顺序:(1)from (2) on (3) join (4) where (5)group by(开始使用select中的别名,后面的语句中都可以使用) (6) avg,sum.... (7)having (8) select (9) distinct (10) order by (11) limit;
(2)然后学会使用explain进行具体分析优化;
(3)了解mysql索引背后的数据结构及算法原理
(4)通过--log-slow-queries,--log-queries-not-using-indexes两个参数的开启,通过慢查询日志进行分析;

1.操作符
(1)采用OR、<>或!=操作符时,查询语句不会使用索引;
(2)采用NOT操作符时,查询语句不会使用索引;
(3)采用HAVING操作符时,查询语句不会使用索引;

HAVING 子句使你能够指定过滤条件,从而控制查询结果中哪些组可以出现在最终结果里面。
WHERE 子句对被选择的列施加条件,而 HAVING 子句则对 GROUP BY 子句所产生的组施加条件。

(4)避免在索引列上使用IS NULL和IS NOT NULL;
(5)避免在索引列上出现数据类型转换;
2.当列参与计算和标记时,索引也无法使用;
3.统一SQL语句的写法,大小写会影响SQL查询分析器的解析,会认为是两个语句;
4.不要一条SQL写得太长;
5.一条select语句尽量别超过3个嵌套;
6.尽量避免大的事务操作,只在必要的时候使用,提高系统并发能力,因为mysql执行事务时候会锁表,会阻碍其他连接访问;
7.尽量避免向客户端返回大数据量,多使用limit,若数据量过大,应该考虑相应需求是否合理;
8.拆分大的DELETE或INSERT语句,批量提交SQL语句;
9.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
10.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段;
11.Update 的时候,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志;
12.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描;
13.IN要慎用,多数时可以用exists替换;
14.在可以使用UNION ALL的语句里,尽量别使用UNION;

因为UNION会对两个表进行比较,会有大的消耗

15.避免使用模糊查询,因为查询语句不会使用索引;
16.不要以字符格式声明数字,要以数字格式声明字符值。(日期同样)否则会使索引无效,产生全表扫描;
17.OLTP系统SQL语句采用绑定变量;
select*from orderheader where changetime >'2010-10-20 00:00:01' 
select*from orderheader where changetime >'2010-09-22 00:00:01’

以上两句语句,查询优化器认为是不同的SQL语句,需要解析两次。如果采用绑定变量:

select*from orderheader where changetime >@chgtime

但注意绑定变量对大多数OLTP处理是适用的,但是也有例外。比如在where条件中的字段是“倾斜字段”的时候。“倾斜字段”指该列中的绝大多数的值都是相同的,

18.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差
19.避免使用耗费资源的操作,带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎 执行,耗费资源的排序(SORT)功能;

DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序

20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引);
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表;
22.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert;
23.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定
24.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好
25.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息;
26.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引;

本文参考:
索引的设计原则
Mysql常用30种SQL查询语句优化方法
高手详解SQL性能优化十条经验
高性能SQL语句权威指南
MYSQL 优化常用方法
优化SQL查询:如何写出高性能SQL语句
MySQL索引背后的数据结构及算法原理

推荐阅读更多精彩内容