[mysql]执行计划和SQL优化

本文介绍了MySQL的执行顺序、优化策略,包括选择合适的字段属性、使用JOIN而非子查询、事务处理、锁定表、利用索引和避免全表扫描等,旨在提升数据库性能和SQL查询效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

书写顺序

必选
select from 
可选
where group by having order by

执行顺序

from:需要从哪个数据表检索数据 
where:过滤表中数据的条件 
group by:如何将上面过滤出的数据分组 
having:对上面已经分组的数据进行过滤的条件  
select:查看结果集中的哪个列,或列的计算结果 
order by :按照什么样的顺序来查看返回的数据 

from后面的表关联,是自右向左解析的 
而where条件的解析顺序是自下而上的
尽量把数据量大的表放在最右边来进行关联
能筛选出大量数据的条件放在where语句的最下面



SQL Select语句完整的执行顺序【从DBMS使用者角度】: 
  1、from子句组装来自不同数据源的数据; 
  2、where子句基于指定的条件对记录行进行筛选; 
  3、group by子句将数据划分为多个分组; 
  4、使用聚集函数进行计算; 
  5、使用having子句筛选分组; 
  6、计算所有的表达式; 
  7、使用order by对结果集进行排序。 

SQL Select语句的执行步骤【从DBMS实现者角度,这个对我们用户意义不大】: 
  1)语法分析,分析语句的语法是否符合规范,衡量语句中各表达式的意义。 
  2)语义分析,检查语句中涉及的所有数据库对象是否存在,且用户有相应的权限。 
  3)视图转换,将涉及视图的查询语句转换为相应的对基表查询语句。 
  4)表达式转换, 将复杂的 SQL 表达式转换为较简单的等效连接表达式。 
  5)选择优化器,不同的优化器一般产生不同的“执行计划” 
  6)选择连接方式, ORACLE 有三种连接方式,对多表连接 ORACLE 可选择适当的连接方式。 
  7)选择连接顺序, 对多表连接 ORACLE 选择哪一对表先连接,选择这两表中哪个表做为源数据表。 
  8)选择数据的搜索路径,根据以上条件选择合适的数据搜索路径,如是选用全表搜索还是利用索引或是其他的方式。 
  9)运行“执行计划”。 
  

查询语句

1. tcp连接,连接器,连接器会对该请求进行权限验证及连接资源分配
2. 发送语句,命令分发器
	是一条select语句? 是 开启查询缓存? 是 查询缓存中查找该SQL是否完全匹配? 是 验证当前用户是否具备查询权限? 是
	以上都通过,直接返回结果集给客户端
	(MySQL将缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括了以下因素,即查询本身、当前要查询的数据库、客户端协议的版本等一些其他可能影响返回结果的信息)
3. 缓存未命中,分析器
	如果语法不对,就会返回语法错误中断查询
4. 预处理器,检查数据表和数据列是否存在,解析别名看是否存在歧义等
5. 优化器进行优化(通过索引选择最快的查找方式),并生成执行计划
6. 执行器执行语句
	该用户是否具有查询权限? 是 执行器开始执行,将数据保存到结果集中,同时会逐步将数据缓存到查询缓存中,最终将结果集返回给客户端
(缓存到查询缓存受到几个参数的影响 1.query_cache_type 是否打开查询缓存,默认为OFF  2.query_cache_size:查询缓存使用的总内存空间,默认值为1M   3.query_cache_limit 对于大于该值的结果集不会被缓存,默认值1M,在8.0版本后该参数被移除了)(如果该SQL执行过程中超过了慢查询阀值,该SQL会被记录到慢查询日志中)

更新语句

1. TCP连接,连接器,连接器会对该请求进行权限验证及连接资源分配
2. 客户端发送一条语句,mysql收到该语句后,通过命令分发器判断其是否是一条更新语句,如果是,则直接发送给分析器做语法分析
3. 分析器阶段,MySQL需要知道到底要查哪些东西,如果语法不对,就会返回语法错误中断查询
4.分析器的工作完成后,将语句传递给预处理器,检查数据表和数据列是否存在,解析别名看是否存在歧义等

 5.语句解析完成后,MySQL就知道要查什么了,之后会将语句传递给优化器进行优化(通过索引选择最快的查找方式),并生成执行计划。

 6.执行器根据生成的执行计划去open table,此时会先去查看该表上是否有元数据(MDL)排他锁(如果有元数据共享锁则无影响),如果有元数据排他锁,则事物被阻塞,进入等待状态(时间由lock_wait_timeout决定,默认是一年。。。。),等元数据锁被释放,继续执行。如果无元数据锁或者是有元数据共享锁,则该事务在表上加元数据共享锁(因为元数据共享读锁之间是不冲突的,如果表上有元数据共享锁,我们执行alter table这样的DDL语句时,会进入等待状态,因为DDL语句需要在表上加元数据排他锁)

 7.进入引擎层(默认innodb),去innodb_buffer_pool里面的data dictionary得到表得相关信息

 8.根据表信息去innodb_buffer_pool里面的lock info查看是否有相关的锁信息,如果有则等待(因为要加排它锁),如果没有则加排它锁,更新lock info。

 9.取读取相关数据页到innodb_buffer_pool中(如果数据页本身就在缓存中,则不用从硬盘读取)

10.将页中的原始数据(快照)保存到undo log buffer中(undo log buffer会以相关参数定义的规则进行刷盘操作写入到undo tablespace中)

11.在innodb_buffer_pool中将相关页面更新,该页变成脏页(脏页会以相关参数定义的规则进行刷盘操作写入所属表空间中)

12.页面修改完成后,会把修改后的物理页面保存到redo log buffer中,(redo log buffer会以相关参数定义的规则进行刷盘操作写入到redo tablespace中)

13.如果开启binlog,则更新数据的逻辑语句也会记录在binlog_cache中(binlog会以相关参数定义的规则进行刷盘操作写入到binlog file 中)

14.如果该表上有二级索引并且本次操作会影响到二级索引,则会把相关的二级索引修改写入到innodb_buffer_pool中的change buffer里(change buffer 会以相关参数定义的规则进行刷盘操作写入所属表空间中)

15.前期的准备工作到此已经做完了,之后便是事务的commit或者rollback操作。一般情况下执行的是commit操作

16.执行commit操作后(mysql默认开启自动提交,如果手动开始事务begin,则需要显示提交commit),由于要保证redolog与binlog的一致性,redolog采用2阶段提交方式。

17.将undo log buffer及redo log buffer刷盘(innodb_flush_log_at_trx_commit=1),并将该事务的redolog标记为prepare状态。

18.将binlog_cache数据刷盘(sync_binlog=1)

19.如果开启了主从结构,此时会将binlog_cache中的信息通过io线程发送给从机,如果开启了半同步复制则需要等待从机落盘(relay log)并反馈。如果是异步复制则无需等待(默认是异步复制)

20.待binlog落盘完成,再将redolog中该事务信息标记为commit,释放相关锁资源。此时一个更新事务的操作已经完成,返回给客户端成功更新提示。

21.标记undolog中该事务修改页的原始快照信息为delete,当无其他事务引用该原始数据时(MVCC),再将其删除

22.如果此时触发了脏页刷盘操作,会先将脏页写入到double write buffer中(防止写入过程中出现断页,因为mysql页面默认为16K,linux操作系统最大为4K,如果写了8K时系统挂了,这个数据页将不完整,标记为损坏)然后再写到期所在表空间的相应位置。




explain select * from person;
-- 模拟优化器执行sql查询语句



id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	person		ALL					4	100	



id
查询序列号

1. id相同,执行顺序从上到下,select_type=simple
2. id不同,子查询,则id越大的优先级越高,优先被执行,select_type=subquery and primary
3. id相同和不相同的同时存在,id相同的为同一组,从上到下执行,id组内部的按照id值排序,越大的先执行
table=<derived2>指向id为2的行,表示衍生

select_type
查询的类型

1. simple,不包含子查询或union
2. primary,包含子部分,最外层标记,join等
3. subquery,select或者where里面包含了子查询
4. derived,from中包含了子查询,子查询的标记
5. union,第二个select出现在union之后,标记为union,若union包含在from子语句的子查询中,外层的select被标记为derived
6. union result,从union表获取结果的select,id为null

type
访问类型
从好到坏取值:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

至少达到range,最好到ref

1. system,表只有一行记录,等于系统表,const类型特例,一般不会出现
2. const,一次索引找到,用于比较primary key或unique索引,例如主键用于where条件
3. eq_ref,唯一性索引扫描,主键或唯一索引
4. ref,非唯一性索引扫描,可能返回多行
5. range,使用一个索引来选择行,key显示了使用的索引,where里面有between/大于小于/in等,不需要扫描全部索引
6. index,读全表的索引树
7. all,全表扫描,硬盘读取


psssible_keys
查询字段有索引,但是不一定被用到,列出来看一下而已

key
实际使用了哪个索引
null表示没有索引

key_len
索引中使用的字节数,最大可能长度,越小越好,根据表定义计算的

ref
索引的哪一列被使用了

row
估算找出记录所需要扫描的行数

extra
其他信息
1. Using filesort 
mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序”
索引列在索引的右边,select的时候使用order by右边的一列,那么mysql就会再实现一次排序

2. Using temporary
临时表,出现了order by 和 group by

3. Using index
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高

和Using where一起出现,表示索引用于执行索引键值的查找
单独出现,表明索引只是用来读取数据,而不是查找动作

覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。 
注意: 
a、如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用select * 
b、如果将所有字段都建索引会导致索引文件过大,反而降低crud性能

4. Using where
使用了where过滤

5. Using join buffer
使用了链接缓存

6. Impossible WHERE
不可能获取任何行

7. select tables optimized away
没有group by子句,min/max计算的时候,不用等到执行阶段计算

8. distinct
优化操作,找到了第一个值之后旧不再查找同样的值

数据库优化

  1. 合适的字段属性和长度

字段宽度尽量小,char代替varchar,mediumint代替bigint

字段尽量设置为notnull,避免比较null值

定义枚举值,比如性别,数值比文本检索快

  1. 使用join代替subqueries

不需要创建临时表

  1. union代替手动创建临时表

  2. 使用事务

    begin;
    insert into xxx values (xx,xx);
    commit;
    – 事务锁定数据库,方便回滚操作,避免脏数据

  3. 锁定表

    – 只锁定表,避免独占数据库
    locktable person write select name from person where age=18;

    update person set age = 20 where name = ‘xiaoming’;
    unlocktables

    – 保证sql执行过程中不会有其他插入或者删除操作

  4. 使用外键保证数据关联性和删除

  5. 使用索引

索引列选择join/where/order by的

大量重复值的,不能建索引

索引字段不要使用函数操作,否则不走索引

尽量不用like,可以用<>

尽量避免自动类型转换

8个方法

  1. 加索引
  2. 复合索引,最常用的放左边
  3. 索引尽量不要包含null值的列,为null则复合索引无效
  4. 短索引,选前几个唯一性比较大的做索引,不要全列索引
  5. 排序问题,查询只会使用一个索引,如果where里面用了,order by里面不会使用,尽量不要使用多个列排序,需要时创建复合索引
  6. like,用右百分号
  7. 列上不要使用函数
  8. 不使用not in和<>,都不走索引,not in可以使用not exists,id<>3 用 id>3 or id ❤️

sql优化

  1. 优先在where和order by上建索引

  2. where条件避免null值判断,会全表扫描

  3. where条件不要使用!= <>,会全表扫描

  4. where条件避免使用or连接,可以使用union all把两个连接起来

  5. in 和 not in,连续值使用between,exists代替in,
    select num from a where exists(select 1 from b where num=a.num);

  6. like,使用右%

  7. where不要带参数,不要带计算表达式
    select id from t where num = @num
    ->
    select id from t with(index(index_for_num)) where num = @num
    – 强制查询使用索引

  8. Where不要使用表达式
    select id from t where num/2 = 100;
    ->
    select id from t where num = 100*2;

  9. 避免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'
    
  10. 复合索引,如果没有用到第一个字段作为条件,索引是不生效的

  11. 大数据量join,先分页,再join

  12. 不要用select count(*) from xxx;

  13. 一个表的索引最好不要超过6个

  14. 建历史表的时候,数据量大用select into,减少log,数据量小,先create table再insert

  15. 避免使用游标,效率低

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值