文章目录
- 先修:为什么用B+树存放索引?
- 索引优化规则
- 一、SQL优化
- 二、定位慢查询SQL
- 三、查看具体SQL执行时间
- 四、执行计划显示的extra信息
- 一些常识
- 一、MySQL的层次
- 二、delete加limit
- 三、IP地址用int存
- 四、语句执行顺序
先修:为什么用B+树存放索引?
MySQL中InnoDB引擎的索引是B+树,具体要去了解一下为什么不用其他的数据结构
链表:显然不合适,不方便查找
BST:当数据是递增的时候,退化成了链表
AVL:能保持树的平衡,但条件严格,高度不能超过2,需要不停左旋右旋,是一种插入慢,检索快的数据结构
红黑树:算是BST和AVL的折中版本,但如果数据量过,一侧的树会很深
B树:一个磁盘块16KB中能存的数据较少,存放的是index和data
B+树:只有叶子结点才会有数据data,并且是按顺序排的,存放的数据更多
索引列如果是varchar,优化的时候可以进行截取,以减少在磁盘中索引占据的位置,索引长度应尽量短。
*.frm:数据表结构相关信息存储的文件
*.idb:索引和数据存储的文件
注意:*.idb 这个文件本身就是 B+Tree 的文件,叶子节点包含完整的数据记录。
索引优化规则
一、SQL优化
组合索引如果带了>、<、between、like,则会失效,就是被计算了,还有情况是第一个字符是%,也会失效,还有函数、隐式转换,int变varhcar。
归结起来就是:
1. 使用最左前缀规则(组合索引里面的)
index(a,b,c)
where语句 | 索引使用状态 |
where a=3 | 使用了a |
where a=3 and b=5 | 使用了a,b |
where a=3 and b=5 and c=4 | 使用了a,b,c |
where b=3 或者where b=3 and c=4 | 没使用 |
where a=3 and c=5 | 使用了a,没有c,因为b断掉了 |
where a=3 and b>5 and c=4 | 使用了a,b,c不能又在范围之后,b断掉了 |
where a=3 and b like’K%’ and c=4 | 使用了a,b,c |
where a=3 and b like’%K’ and c=4 | 使用了a,bc因为like里面的%在第一位断掉 |
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引。
2. 索引列不能参与计算
假设salary上有一个索引,那么salary*12>10000不可取,要写成salary>10000/12
3. 模糊查询不能利用索引
那么 like ‘%引’ 不可取,查看业务逻辑,进行修正,第一个不可以是%
因为%号,模糊的条件,会进行逐个查找,这是全表扫描
4. 索引不能创建过多
5. 索引不能频繁更新
null不走索引,走的是全文扫描,所以尽量避免比null,可以设定默认值为0
少用select * 尽量使用具体的id,以及多使用别名.id ,因为这样可以使用到覆盖索引,而且大概率不会出现歧义
6. 数据量少的时候放弃索引查询会更快
7. 保证从内存中读取数据,把数据保存在内存中
推荐将数据全然保存在 innodb_buffer_pool_size ,即按存储量规划 innodb_buffer_pool_size 的容量。这样你能够全然从内存中读取数据。最大限度降低磁盘IO操作。
SHOW GLOBAL STATUS LIKE 'innodb_buffer_pool_pages_%';
8. not in和<>查询以及子查询
最好把这类查询自行改写成关联查询。
子查询执行效率不高,使用关联查询(join)代替子查询。
效率不高的原因:查询的过程中需要建立一个临时表,查询完毕,再撤销临时表。消耗性能。临时表都不会使用索引。
--修改前:
SELECT id,name,email FROM customer
WHERE id
NOT IN(SELECT id FROM payment)
--修改后:
SELECT a.id,a.name,a.email
FROM customer a
LEFT JOIN payment b ON a.id=b.id
WHERE b.id IS NULL
9. 少用or连接条件
应尽量避免在 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
10. 给被驱动表添加索引,驱动表加不加都行
优化器满足小表驱动大表
如:student是驱动表,book是被驱动表 :
EXPLAIN SELECT * FROM student LEFT JOIN book ON student.card = book.card;
11. limit相关操作
这个太好了,我就搬过来了,
如果是全表扫描,加上limit 1。找到数据就不会再继续查找了,加快查找效率
如果是唯一索引,找到数据也不会继续查找了,limit 1 就不管用了。
12. 用union all替换union
当SQL语句需要union两个查询结果集合时,即使检索结果中不会有重复的记录,如果使用union这两个结果集同样会尝试进行合并,然后在输出最终结果前进行排序,因此如果可以判断检索结果中不会有重复的记录时候,应该用union all,这样效率就会因此得到提高。
二、定位慢查询SQL
slow_query_log:是否启动慢查询日志,默认不启动,on 启动;
slow_query_log_file:指定慢查询日志的存储路径及文件,默认保存在 MySQL 的数据目录中;
long_query_time:指定记录慢查询日志 SQL 执行时间的阈值,单位秒,默认10,对于一个繁忙的系统,改为0.001比较合适;
log_queries_not_using_indexes:是否记录未使用索引的 SQL;
set global slow_query_log = on;
set global long_query_time = 1;
和二进制日志不同,慢查询日志会记录所有符合条件的 SQL,包括查询语句、数据修改语句、已经回滚的 SQL。需要使用一些工具来进行分析。
三、查看具体SQL执行时间
set profiling = 1 开启显示数据查询时间
show profiles 查看粗略的,去掉s,可以查看详细的时间
查看正在执行的线程,并按Time倒排序,查看执行时间过长的线程。
select * from processlist where Command != 'Sleep' order by Time desc;
四、执行计划显示的extra信息
explain 一个sql语句之后查看执行计划的extra额外信息这一栏:
null:回表:带着数据又回去查了一遍
using index:覆盖索引:
将被查询的字段,建立到组合索引(如果只有一个字段,普通索引也可以)里去。如以下这句sql,我在name字段建立了一个索引
explain select id,name from student
这是因为索引叶子节点存储了主键id,而name也是索引,所以查询为覆盖索引。
当我们添加一个不是索引的字段时,那么就无法从索引树上查询,必须回表查询出这个字段
explain select id,name,age from student
using index condition :索引下推:
必须是查范围值,如果是常量比较,不会触发的,比如这样a>10会触发,如果是a=10,则不触发
type 字段的返回值,性能从最优到最差:
system -> const -> eq_ref -> ref -> fulltext -> ref_or_null -> index_merge -> unique_subquery -> index_subquery -> range -> index -> all
index 和 all 表示本次查询走的是全表扫描。如果 type 值是这两个,表明 SQL 是需要优化的。
有个key_len的列,计算方法:int占4字节;不为空占1字节;varchar根据括号里的来占字节,如果是utf-8则乘以3,变长数据类型2字节
一些常识
一、MySQL的层次
客户端-server-存储三层
server里面有连接器(进行连接的操作)、语法器(判断语法是否有误,生成AST抽象语法树)、优化器(进行SQL语句的优化)、执行器。
语句的操作,其实质是IO,只要能降低IO,就能在一方面起到调优的功效。
二、delete加limit
假设删除10000条数据,最佳方法是循环5次,一次删500条,
delete from table where age>30 order by age limit 500
当delete后只有order by而没有limit时,order会被优化器优化掉,因为无意义。
再说一下为什么建议加order by,
1.不加排序会随机删除(或者说无法删除你预想的那行数据),如我本想删除第一行id=1的这行数据,但我执行delete xx limit 1;删除成功后发现,第一行还在,说明删除了其他行;
2.为什么delete语句加上 ‘order by 索引列’ 会更快,是因为order by id会触发索引查询,而普通删除则需全表扫描一下。
三、IP地址用int存
select inet_aton(‘192.168.1.1’)
select inet_ntoa(3232235777)
字符集选择:utf-8mb4
四、语句执行顺序
简单的没有子查询嵌套的话,顺序是这样:
---->from 获取数据表,需要从哪个表取数据
—>where 筛选出合格的数据,得到一个数据集
—>group by 将上面的数据集按条件进行分组
---->having 将分好组的数据按条件进行过滤
---->select 针对每一组的数据进行select,返回结果集
---->order by 进行排序
PS:from后面的表示自右向左进行解析的,where后面的是自下而上进行解析的。所以,在写SQL的时候,尽量把数据量小的表放在最右边进行关联,把能筛选出小量数据的条件放在where语句的最左边,来达到用小表匹配大表