SQL调优

 先介绍一个知乎看到关于sql调优的思路,写的很好,都是思路性的内容,没有具体的操作,启发思考,如果想看具体的内容。直接往下翻

作者:张延俊
链接:http://www.zhihu.com/question/29619558/answer/45805380
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

千言万语会成一句话:学会用数据库的方式来思考如何执行sql,那么什么是好的sql,首先要明白数据库是如何执行一个sql,一个事务的。

数据库执行sql的大致流程
粗略流程,所有关系型数据库都是这几步,具体前后顺序根据不同dbms不同配置下略有小差别,以下过程都需要耗时耗资源
1.应用程序与数据库服务器建立链接
2.sql发送到数据库,数据库验证是否有执行的权限
3.进入语法解析器,进行词法与语法分析
4.进入优化器生成执行计划,部分dbms会检查是否有可重用的执行计划
5.根据执行计划依次扫描相关表中的行,不在数据缓冲区的走io
6.同时对于被扫描的行可能加锁,同时也可能会被其他sql阻塞
7.扫描的行足够放入查询缓存则开始运算或直接返回,不够则生成临时表,可能消耗io
8.对sql结果进行计算(可能)
9.将计算完成的结果全部写入网络io(可能)
10.如果事务完成则同步事务日志并释放锁,具体方式取决于dbms和当前配置
11.关闭连接(可选)

如何优化
这么多步骤,每一步都有优化策略,我尽量用简单的语言来描述
1.应用程序与数据库服务器建立链接
引入数据库连接池,避免每次都与数据库建立连接,提高效率

2.sql发送到数据库,数据库验证是否有执行的权限
没撒好说的

3.进入语法解析器,进行词法与语法分析
也没撒好说的,想要数据库在这里少用点资源就把sql写的简单点,但是差别不大

4.进入优化器生成执行计划,部分dbms会检查是否有可重用的执行计划
哦也,最复杂的部分来了,任何数据库如何生成执行计划都可以写一本几百页的书,我就简单说说,复杂的我也说不出来,哈哈哈
关系型数据库选择走什么执行计划都是基于消耗最小化的思路来的,简单来说就是走什么索引,按什么顺序走表,被扫到的数据行最少。如果你的表结构很复杂,有各种混搭的索引,你的join很多,那执行计划分析的时间就会拉长。所以sql对应的表索引简单,join或子查询少就快,复杂了优化器也会得选择困难症。

5.根据执行计划依次扫描相关表中的行,不在数据缓冲区的走io
存储引擎扫描表的性能消耗参考下面的list,消耗从大到小
全表扫描>全索引扫描>部分索引扫描>索引查找>唯一索引/主键查找>常量/null
要走索引对于sql语句也有要求,不能在谓词上作任何运算,扫描行数一般不能超过表的17%左右,这对你数据分布又有要求,比如你查select xxx from human where sex ='man',五五开,还是走扫描。这里我就不展开了,推荐题主一本书《Relational Database Index Design and the Optimizers》

6.同时对于被扫描的行可能加锁,同时也可能会被其他sql阻塞
如果扫描的行多,sql执行的时间长,被阻塞的概率就高,阻塞别人的概率也高,然后大家一起等,数据库就hung住了

7.扫描的行足够放入查询缓存则开始运算或直接返回,不够则生成临时表,可能消耗io
一次取的尽量少,这不单指返回服务端的行数,应该从嵌套最深的一个子查询开始算

8.对sql结果进行计算(可能)
少用各种复杂的函数啊,count啊,order by啊等等

9.将计算完成的结果全部写入网络io(可能)
请尽量少返回一点数据,如果不行请多次分批

10.如果事务完成则同步事务日志并释放锁,具体方式取决于dbms和当前配置
这里举两个代表性栗子:
sql渣:
for i in (1-1000):
start transaction;
insert into table values (1);
commit;
end for
sql赞:
start transaction;
for i in (1-1000):
insert into table values (1);
end for
commit;
sql赞爆:
insert into table values (1)()...........()(1000);
首先,sql语法是我临时自创的,这个不是关键,关键在sql渣先生是1000个事务插1000行,日志flush1000次。sql赞先生是一个事务插1000行,事务日志flush1次。sql赞爆最nice。
这个例子我想表达的意思是如果你要用sql做一件事,那就要尽量让这件事占用的事务总时间最少。

第二个例子
sql渣:
update table where id > 0 and id < 1000000;

sql赞:
update table where id > 0 and id < 1000;
update table where id >= 1000 and id < 2000;
update table where id >= 2000 and id < 3000;
....
....
这个例子我想表达的意思是如果你要用sql做一件很大的事,那就尽量让大事化成很多小事。
两个例子好好体会下,一点不矛盾哦。补充一下,这里每个update都是单独事务

11.关闭连接(可选)
同1,别每次都关,关了也许还要重连。不关的话记得commit就好了,千万要记得commit啊!

最后,题主作为一个应届生的话,目前不了解数据库实现细节是很正常的,但是要学会一种思路:如果我是一个数据库,我会怎么执行一个sql,我喜欢怎么样的sql?
能写出多好的sql取决于你多了解数据库。
 
===============================具体的操作=================================
 
上面的知乎大神讲解了优化的思路,那么我来总结下具体的操作内容如下:

数据库调整

1,加索引:  WHERE子句和ORDER BY 句子;

2,添加复合索引的注意,尽量索引的顺序和查询的条件一致,尽量将索引的第一个作为条件,进行查询;

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

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

可以在num上设置默认值0,确保表中num列没有null值,

句子编写注意

1,尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

SELECT id FROM  t WHERE num IS NULL

标注:对数据库设计的思考,最好不要用NULL类型做默认(详见下文)

2,尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。

3,尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。可改用 UNION 例如:

SELECT id FROM t WHERE num=10 or Name = 'admin'

可以这样查询:

SELECT id FROM t WHERE num = 10 UNION  ALL SELECT id FROM t  WHERE Name='admin'

4.in 和 not in 也要慎用,否则会导致全表扫描,如:

SELECT id FROM t WHERE num IN (1,2,3)

对于连续的数值,能用 BETWEEN 就不要用 IN 了:

SELECT id FROM t WHERE num IN BETWEEN 1 AND 3

很多时候用 exists 代替 in 是一个好的选择:

SELECT num FROM a WHERE num IN ( SELCT num FROM b);

用下面的语句替换:

SELECT num FROM a WHERE e exists (SELECT 1 FROM b WHERE num = a.num )

5,这样的查询,like‘%abc%,也会导致查询变慢,可以考虑全文索引。

6,变量,表达式,函数都会导致检索全表。

变量:

select id from t where num=@num     

select id from t with ( index(索引名)) where num = @num

表达式:

select id from t where num/2=100

select id from t where num=100*2

函数:

select id from t where substring(name,1,3)=’abc’

select id from t where name like 'abc%'

14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。

高级应用

23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

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

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

29.尽量避免大事务操作,提高系统并发能力。

30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

实际案例分析:拆分大的 DELETE 或INSERT 语句,批量提交SQL语句

如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。

Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。

如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你的WEB服务崩溃,还可能会让你的整台服务器马上挂了。

所以,如果你有一个大的处理,你一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)条件是一个好的方法。下面是一个mysql示例:

while(1){//每次只做1000条mysql_query(“delete from logs where log_date <= ’2012-11-01’ limit 1000”);if(mysql_affected_rows() == 0){//删除完成,退出!break;
}//每次暂停一段时间,释放表让其他进程/线程访问。usleep(50000)

}

内容整理自:数据优化大总结之百万级数据库优化方案

打赏支持我写出更多好文章,谢谢!
打赏作者
+

(^_^)打个赏喝个咖啡(^_^)

微信支付
支付宝支付
原文地址:https://www.cnblogs.com/yangf2016/p/5609066.html