MySQL 学习四 SQL优化
Posted 刘大飞
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 学习四 SQL优化相关的知识,希望对你有一定的参考价值。
MySQL逻辑架构:
第一层:客户端层,连接处理,授权认证,安全等功能。
第二层:核心层,查询解析,分析,优化,缓存,内置函数(时间,数学,加密),存储过程,触发器,视图
第三层:存储引擎。负责mysql中数据的存储和提取。
MySQL查询过程
- 客户端/服务端通信协议:需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常,因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用 SELECT * 以及加上 LIMIT 限制的原因之一。
- 查询缓存: 缓存命中的情况下,查询不会被解析,不会生成执行计划,更不会被执行。所以两个查询在任何字符上的不同(例如:空格、注释),都会导致缓存不会命中。如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果都不会被缓存。查询缓存何时失效呢?MySQL 的查询缓存系统会跟踪查询中涉及的每个表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。正因为如此,在任何的写操作时,MySQL 必须将对应表的所有缓存都设置为失效。
缓存对系统的额外消耗也不仅仅在写操作,读操作也不例外:
-
任何的查询语句在开始之前都必须经过检查,即使这条 SQL 语句永远不会命中缓存。
-
如果查询结果可以被缓存,那么执行完成后,会将结果存入缓存,也会带来额外的系统消耗。
-
基于此,我们要知道并不是什么情况下查询缓存都会提高系统性能,缓存和失效都会带来额外消耗,只有当缓存带来的资源节约大于其本 身消耗的资源时,才会给系统带来性能提升。
最后的忠告是不要轻易打开查询缓存,特别是写密集型应用。如果你实在是忍不住,可以将 query_cache_type 设置为 DEMAND。
这时只有加入 SQL_CACHE 的查询才会走缓存,其他查询则不会,这样可以非常自由地控制哪些查询需要被缓存。
- 语法解析和预处理
解析语法,生成解析树。进行合法校验。
- 查询优化: SQL解析,预处理,优化生成执行计划。
- 查询执行引擎: 生成执行计划,并执行查询。
- 返回结果给客户端:同时缓存结果。
(不要听信你看到的关于优化的“绝对真理”,包括本文所讨论的内容,而应该是在实际的业务场景下通过测试来验证你关于执行计划以及响应时间的假设。)
1 学习使用EXPLAIN
2 创建正确的索引
数据库的索引像书的索引一样,他们的位置信息被保存,并且包含数据库的主要信息。可以使用EXPLAIN来查找
缺失的索引。
3 拒绝默认的设置:有三个关于MySQL性能优化的设置:
innodb_buffer_pool_size:数据和索引被用作缓存的缓冲池。当数据库服务器有大量的系统内存时,可以用。
这个设置不要过大,也不要频繁的引起交换。
innodb_log_file_size:单个InnoDB日志文件大小。
max_connections:最大连接数
4 将数据库载入内存
将频繁访问的数据放入内存(比如30%的数据放入内存)
5 SSD存储
6 横向扩展??
纵向扩展
横向扩展
7 追求可视化
数据库受到流量负荷的影响,应用程序等导致的错误,为了快速、有效的解决问题,需要有监控机制。
常用的监测工具: MySQL企业监控器 / Monyog / Percona
8 Scheme设计与数据类型优化
选择数据类型只要遵循小而简单的原则就好,越小的数据类型通常会更快,占用更少的磁盘、内存,处理时需要的 CPU 周期也更少。比如,整型就比字符操作代价低,因而会使用整型来存储 ip 地址,使用 DATETIME 来存储时间,而不是使用字符串。
- 在列上创建索引的话,应该把NULL改为NOT NULL: 否则索引的效率会大打折扣。
- 对整数类型指定宽度,没有任何作用
- UNSIGNED表示不允许负值:
- 没有太大必要使用DECIMAL数据类型。就算在需要存储财务数据时,任然可以使用BIGINT。比如精确到万分之一,那么可以将数据乘以一百万后使用BIGINT存储。这样可以避免浮点数计算不准确和DECIMAL精确计算代价高的问题。
- TIMESTAMP使用4个字节存储,DATETIME使用8个字节存储。但是TIMESTAMP只能表示1970-2038年,并且TIMESTAMP的值因时区不同而不同
- 大多数情况下,没有必要使用枚举类型,缺点是:枚举的字符串列表是固定的,添加和删除必须使用ALTER TABLE
- schema的列不要太多。
- 大表ALTER TABLE非常耗时。
9 创建高性能索引
索引:通常说的索引时B-Tree索引。InnoDB用的是B+Tree.
平衡二叉树: 如果想二叉树的查询性能高,需要二叉树是平衡二叉树。
为什么MYSQL不用平衡二叉树,而是用B+Tree树?随着数据库中数据的增加,索引本身大小随之增加,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。这样的话,索引查找过程中就要产生磁盘 I/O 消耗,相对于内存存取,I/O 存取的消耗要高几个数量级。可以想象一下一棵几百万节点的二叉树的深度是多少?如果将这么大深度的一颗二叉树放磁盘上,每读取一个节点,需要一次磁盘的 I/O 读取,整个查找的耗时显然是不能够接受的。那么如何减少查找过程中的 I/O 存取次数?一种行之有效的解决方法是减少树的深度,将二叉树变为 m 叉树(多路搜索树),而 B+Tree 就是一种多路搜索树。
B+Tree树特征:
- 所有关键字都存在叶子节点: 每个节点设置为页的整数倍(预读取时,可以读一页)
- 叶子节点由指针连接:这样便于区间查找。
10 MySQL不使用索引的情况:非独立的列
“独立的列” :索引列不能是表达式的一部分,也不能是函数的参数。
11 前缀索引
列很长的情况下,索引开始的部分字符,有效节约索引空间(需要前面的部分有一定的区分度)
12 多列索引和索引顺序:
在多数情况下,在多个列上建立独立的索引并不能提高查询性能,理由非常简单,MySQL 不知道选择哪个索引的查询效率更好(因为数据库中有多个索引的B+Tree, MySQL只能选择一个树做索引)。
多个列之间用AND时:联合索引优于独立索引。
多个列之间用OR、uniton时:往往会不走索引。
这种情况下,建立包含多个列的联合索引更加高效。
创建联合索引时, 把选择性更高的放在前面(因为这样,通过第一个过滤条件就能过滤掉大读书数据)。
13 避免多个范围条件
只能用其中一个索引。
14 覆盖索引
如果一个索引包含或者说覆盖所有需要查询的字段的值(就是select 后面的列),那么就没有必要再回表查询,这就称为覆盖索引。
15 使用索引扫描来排序:
- 对结果集进行排序的操作:
- 按照索引顺序扫描得出的结果自然是有序的
扫描索引本身很快,因为只需要从一条索引记录移动到相邻的下一条记录。但如果索引本身不能覆盖所有需要查询的列,那么就不得不每扫描一条索引记录就回表查询一次对应的行。
在设计索引时,如果一个索引既能够满足排序,又满足查询,是最好的!!。只有当索引的列顺序和 ORDER BY 子句的顺序完全一致,并且所有列的排序方向也一样时,才能够使用索引来对结果做排序。
如果查询需要关联多张表,则只有 ORDER BY 子句引用的字段全部为第一张表时,才能使用索引做排序。
ORDER BY 子句和查询的限制是一样的,都要满足最左前缀的要求。
16 避免冗余和重复索引
17 删除长期未使用的索引
18 查询优化
- count:两个作用,其一是统计某个列值的数量,其二是统计行数。统计列值时,要求列值是非空的,它不会统计 NULL。如果确认括号中的表达式不可能为空时,实际上就是在统计行数。如果要统计行数,直接使用 COUNT(*),意义清晰,且性能更好。通常来说,执行 COUNT() 都需要扫描大量的行才能获取到精确的数据,因此很难优化。
- 优化关联查询:表之间通过冗余字段关联,比使用Join有更好的性能。
在关联查询的情况下:Group By中的表达式只涉及到一个表中的列。这样才有可能使用索引优化。
A和B表用c类关联时,不需要在A上建立索引,在B上建索引就OK(因为,A表无论是否有索引,都要遍历,B表和C表则需要走索引去寻找匹配的记录)。
19 优化LIMIT分页
LIMIT 10000 20 这样的查询,MySQL 需要查询 10020 条记录然后只返回 20 条记录,前面的 10000 条都将被抛弃,这样的代价非常高。
优化这种查询一个最简单的办法就是尽可能的使用覆盖索引扫描,而不是查询所有的列。
20 优化UNION
除非确实需要服务器去重,否则就一定要使用 UNION ALL,如果没有 ALL 关键字,MySQL 会给临时表加上 DISTINCT 选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。
21 假设有联合索引 (user_name, sex, age), 以下三个查询,其实谓词的顺序不重要,都会用到联合索引的,这是因为MySQL做了优化。
但是联合索引的顺序却很重要,看下面22和23
select * from test where user_name=’test1’ and sex>0 and age =10
select * from test where sex>0 and user_name=’test1’ and age =10
select * from test where age =10 and user_name=\'test1\' and sex>0
22 最左原则
mysql建立多列索引(联合索引)有最左前缀的原则,即最左优先,如:
如果有一个2列的索引(col1,col2),则已经对(col1)、(col1,col2)上建立了索引;
如果有一个3列索引(col1,col2,col3),则已经对(col1)、(col1,col2)、(col1,col2,col3)上建立了索引;
23 联合索引总结
- "一个顶三个"。建了一个(a,b,c)的复合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销!
- 覆盖索引(索引能覆盖所有要查询的列,不用回表)。同样的有复合索引(a,b,c),如果有如下的sql: select a,b,c from table where a=1 and b = 1。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。减少io操作,特别的随机io其实是dba主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一
(一直在思考,对于select 后面的字段是否需要建立索引, 对于上面的a,b,c索引,不需要回表;对于a,b索引,还需要回表。)
- 索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是复合索引,通过索引筛选出1000w *10% *10% *10%=1w,然后再排序、分页,哪个更高效,一眼便知 !!!!!!!!!
24 like
%xx -- 不走索引; XX% -- 走索引;
25 OR
OR操作导致,不容易优化。
26 避免 select *
27 知道何时使用临时表
防止对大表查询两次。还可以使用临时表,大幅减少连接大表所需的处理能力。
如果你必须将一个表连接到大表,该大表上又有条件,只需将大表中所需的那部分数据提取到临时表中,然后再与该临时表连接,就可以提升查询性能(这个在实际项目中用到过,在表join之前,先把大表过滤掉尽可能多的行)。
28 预暂存数据
如果你有一个报表或存储过程(或一组)要对大表执行类似的连接操作,通过提前连接表,并将它们持久化存储到一个表中来预暂存数据,就可以对你大有帮助。
29 批量删除和更新
30 避免嵌套视图
31 不要使用逆向搜索
SELECT * FROMCustomers WHERE RegionID <> 3
索引与该查询结合使用,因为它是逆向搜索,需要借助表扫描来逐行比较。
优化方法:SELECT * FROM Customers WHERE RegionID<3 UNION ALL SELECT * FROM Customers WHERE RegionID >3
32 索引的使用原则
- 避免对索引字段进行计算操作
- 避免在索引字段上使用not, <>, !=
- 避免在索引列上使用IS NULL 和 IS NOT NULL
- 避免在索引列上出现数据类型的转换
- 避免在索引字段上使用函数(尽量在应用程序中实现)
- 避免在索引的列中使用空值。
33 使用UNION all, 尽量避免UNION(Union需要排序,然后去重)
34 避免对索引列加函数修饰
where trunc(create_date)=trunc(:date1)
本来create_date建索引了,但是加上trunc后,索引失效,改成如下:
where create_date>=trunc(:date1) and create_date<trunc(:date1)+1< pre="">
where create_date between trunc(:date1) and trunc(:date1)+1-1/(24*60*60)
35 where子句中使用in, not in, or having.
使用exist , not exist代替in, not in
这个需要实践的检验,待续
36 排序
带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎 执行,
耗费资源的排序(SORT)功能。 DISTINCT需要一次排序,其他的至少两次排序。
这个深有感触,MySQL的order by效率及其低下。
37 应尽量避开where子句中进行null值判断:
select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0
38 并不是所有索引对查询都有效:
SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
39 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率
40 应尽可能的避免更新索引数据列,因为索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新索引数据列,那么需要考虑是否应将该索引建为索引。
41 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
42 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
43 应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: 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
44 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where num/2=100 应改为: select id from t where num=100*2
45 很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where
exists(select 1 from b where num=a.num)
46 尽量使用 TINYINT
、 SMALLINT
、 MEDIUM_INT
作为整数类型而非 INT
,如果非负则加上 UNSIGNED
47 VARCHAR
的长度只分配真正需要的空间
48 使用枚举或整数代替字符串类型
49OR
改写成 IN
: OR
的效率是n级别, IN
的效率是log(n)级别,in的个数建议控制在200以内
以上是关于MySQL 学习四 SQL优化的主要内容,如果未能解决你的问题,请参考以下文章