百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

「每天一道面试题」MySQL索引优化与SQL优化

bigegpt 2024-08-04 11:38 4 浏览

MySQL索引优化与SQL优化

概述

日常在 CURD 的过程中,都避免不了跟数据库打交道,大多数业务都离不开数据库表的设计和 SQL 的编写,那如何让你编写的SQL语句性能更优呢?先来整体看下 MySQL 逻辑架构图:



MySQL 整体逻辑架构图可以分为 Server 和存储引擎层。

Server层

Server 层涵盖了 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),以及存储过程、触发器、视图等跨存储引擎的实现也在这一层来实现。

  • 连接器:负责跟客户端建立连接、获取权限、维持和管理连接。
  • 分析器:SQL 词法分析,SQL语法分析。
  • 优化器:索引选择,选择一个执行效率高的,生成执行计划。
  • 执行器:操作引擎,返回执行结果。
  • 查询缓存:执行 SQL 语句之前,先查缓存,缓存结果可能是以 key-value 对方式存储的,key 是查询的语句,value 是查询的结果。

存储引擎层

负责数据的存储和提取,是一种插件式的架构方式。支持 InnoDB、MyISAM、Memory 等多个存储引擎。MySQL 5.5.5 版本开始默认存储引擎是 InnoDB,也是目前常用的存储引擎。

数据准备

我们使用如下语句,创建测试的数据表:

CREATE TABLE `user_haicoder` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(24) NOT NULL DEFAULT '' COMMENT '用户姓名',
  `user_age` int(11) NOT NULL DEFAULT 0 COMMENT '用户年龄',
  `user_level` varchar(20) NOT NULL DEFAULT '' COMMENT '用户等级',
  `register_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  PRIMARY KEY (`id`),
  KEY `idx_userName_userAge_userLevel` (`user_name`,`user_age`,`user_level`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

我们使用如下语句插入数据:

INSERT INTO user_haicoder(user_name, user_age, user_level, register_time)VALUES('嗨客网', 30, 'A', NOW());
INSERT INTO user_haicoder(user_name, user_age, user_level, register_time)VALUES('haicoder', 31, 'B', NOW());
INSERT INTO user_haicoder(user_name, user_age, user_level, register_time)VALUES('hai', 31, 'C', NOW());

索引优化案例

全值匹配

按索引字段顺序匹配使用,我们执行如下 sql:

explain select * from user_haicoder where user_name = '嗨客网';

执行完毕后,如下图所示:


通过 explain 分析,type 为 ref,使用索引,效率高。key_len 为 74,根据 key_len 计算规则,如果字段类型为 varchar(n),并且是 utf-8 编码格式,则 key_len=3n+2,where 后使用了 user_name,则 key_len=3*24+2=74,证明索引用到了联合索引的第一个字段 user_name,从 ref 也可以看到一个 const。

使用联合索引两个字段时:

explain select * from user_haicoder where user_name = '嗨客网' and user_age = 30;

执行完毕后,如下图所示:



同样 type 为 ref,使用联合索引三个字段时:

explain select * from user_haicoder where user_name = '嗨客网'and user_age = 30 and user_level = 'A';

执行完毕后,如下图所示:



同样 type 为 ref。

最佳左前缀法则

如果建的是联合索引,要遵循最左前缀法则。要想使用索引,where 后面的条件需要从索引的最左前列开始并且不跳过索引中的列使用。我们执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网'and user_age = 30 and user_level = 'A';

执行完毕后,如下图所示:



按照索引字段顺序使用,三个字段都使用了索引。我们再次执行如下查询语句:

explain select * from user_haicoder where user_age = 30 and user_level = 'A';

执行完毕后,如下图所示:



直接跳过 user_name 使用索引字段,索引无效,未使用到索引。我们再次执行如下查询语句:

explain select * from user_haicoder where user_age = 30 and user_level = 'A' and user_name = '嗨客网';

执行完毕后,如下图所示:



where 后面查询条件顺序是 user_age、user_level、user_name 与我们建的索引顺序 user_name、user_age、user_level 不一致,为什么还是使用了索引,这是因为 MySql 底层优化器给咱们做了优化。但是,咱们最好还是按顺序使用索引。

不要在索引列上做任何操作

不要在索引列上做任何操作,比如计算、使用函数、自动或手动进行类型转换,会导致索引失效,从而使查询转向全表扫描。我们执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网';

执行完毕后,如下图所示:



wher 条件直接使用索引字段 user_name 用到了索引。我们再次执行如下查询语句:

explain select * from user_haicoder where right(user_name, 1) = '嗨';

执行完毕后,如下图所示:



where 条件使用计算后的索引字段 user_name,没有使用索引,索引失效。

存储引擎不能使用范围条件右边的索引列

我们执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网' and user_age = 30 and user_level = 'A';

执行完毕后,如下图所示:



三个列都使用 “=” 号,顺序使用三个字段,三个字段都使用了索引。我们再次执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网' and user_age > 30 and user_level = 'A';

执行完毕后,如下图所示:



将 user_age修 改为 ">" 之后,Extra 为 Using index condition,表明索引没有被完全使用,并且 key_len 由 140 降为 78,说明最后一个字段 user_level 没有使用索引。即范围之后索引全无效。

但是如果我们把 “>” 变为 ">=" 呢?我们再次执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网' and user_age >= 30 and user_level = 'A';

执行完毕后,如下图所示:



加上等号后,key_len 变为了 140,但是 Using index condition 确又表明索引没有被完全使用,只能说明在满足 user_age 为 “=” 号条件时全部索引使用,否则,范围之后的索引失效。

尽量使用覆盖索引

尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少 select * 语句。我们执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网' and user_age = 30 and user_level = 'A';

执行完毕后,如下图所示:



Extra 显示 "Using index condition",表示查询的列未被索引列覆盖,并且 where 筛选条件是索引的前导列,说明用到了索引,但是部分字段未被索引列覆盖,必须通过 “回表” 来实现,所以不是纯粹地用到了索引,也不是完全没用到索引。我们再次执行如下查询语句:

explain select user_name, user_age from user_haicoder where user_name = '嗨客网' and user_age  = 30 and user_level = 'A';

执行完毕后,如下图所示:



* 换成索引列,查询时使用了索引,用索引列覆盖查询的 *, 叫做覆盖索引。

不等于无法使用索引

mysql 在使用不等于(!= 或者 <>)的时候无法使用索引会导致全表扫描。我们执行如下查询语句:

explain select user_name, user_age from user_haicoder where user_name = '嗨客网';

执行完毕后,如下图所示:



使用了索引,现在我们再次执行如下查询语句:

explain select user_name, user_age from user_haicoder where user_name != '嗨客网';

执行完毕后,如下图所示:



Extra 显示 Using whre Using index,表示查询的列被索引列覆盖,但是 where 后面条件未使用索引,说明无法直接通过索引查找查询到符合条件的数据。

is null,is not null也无法使用索引

我们执行如下查询语句:

explain select * from user_haicoder where user_name = '嗨客网';

执行完毕后,如下图所示:



使用了索引。我们执行如下查询语句:

explain select user_name, user_age from user_haicoder where user_name is not null;

执行完毕后,如下图所示:



未使用索引。

like以通配符开头

like 以通配符开头(like '%aaa')mysql 索引失效会变成全表扫描操作。现在我们执行如下查询语句:

explain select * from user_haicoder where user_name like '%A';

执行完毕后,如下图所示:



% 开头,未使用索引。我们再次执行如下查询语句:

explain select * from user_haicoder where user_name like 'A%';

执行完毕后,如下图所示:



% 结尾,使用了索引。

union、in、or都能够命中索引,建议使用in

union 能够命中索引,并且 MySQL 耗费的 CPU 最少:

select * from user_haicoder where user_level='A'
union all
select * from user_haicoder where user_level='B';

in 能够命中索引,查询优化耗费的 CPU 比 union all 多,但可以忽略不计,一般情况下建议使用 in:

select * from user_haicoder where user_level in ('A', 'B');

or 新版的 MySQL 能够命中索引,查询优化耗费的 CPU 比 in 多,不建议频繁用 or:

select * from user_haicoder where user_level='A' or user_level='B';

order by和group by

如果有 order by、group by 的场景,请注意利用索引的有序性:

  1. order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。例如对于语句 where a=? and b=? order by c,可以建立联合索引(a,b,c)。
  2. 如果索引中有范围查找,那么索引有序性无法利用,如 WHERE a > 10 ORDER BY b;,索引 (a,b) 无法排序。

join查询

进行 join 联表查询的字段需要建立索引,join 最好不要超过三个表。需要 join 的字段,数据类型必须一致,多表关联查询时,保证被关联的字段需要有索引。

left join 是由左边决定的,左边的数据一定都有,所以右边是我们的关键点,建立索引要建右边的。当然如果索引在左边,可以用 right join。

单表索引建议控制在5个以内

索引不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引。一个表的索引数较好不要超过 6 个。

SQL语句优化

  1. 用具体的字段列表代替 "select *",不要返回用不到的任何字段。
  2. 如果明确知道只有一条结果返回,limit 1 能够提高效率。
  3. 利用延迟关联或者子查询优化超多分页场景,MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。示例如下,先快速定位需要获取的 id 段,然后再关联:select a.* from 表1 a,(select id from 表1 where 条件 limit 100000,20 ) b where a.id=b.id;
  4. 对于多张大数据量的表 JOIN,要先分页再 JOIN,否则逻辑读会很高,性能很差。
  5. 避免在 where 子句中使用 or 来连接查询条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。
  6. 对于连续的数值,能用 between 就不要用 in 了,尽量使用 exists 代替 in。
  7. 优化 Group by,使用 where 子句替换 Having 子句。避免使用 having 子句,having 只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序分组,如果能通过 where 子句提前过滤查询的数目,就可以减少这方面的开销。on、where、having 这三个都可以加条件的子句,on 是最先执行,where 次之,having 最后。提高 GROUP BY 语句的效率, 可以通过将不需要的记录在 GROUP BY 之前过滤掉。# 低效 SELECT JOB, AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = ‘PRESIDENT' OR JOB = ‘MANAGER' # 高效 SELECT JOB, AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT' OR JOB = ‘MANAGER' GROUP by JOB
  1. 使用 union all 替换 union当 SQL 语句需要 union 两个查询结果集合时,这两个结果集合会以 union all 的方式被合并,然后再输出最终结果前进行排序。如果用 union all 替代 union,这样排序就不是不要了,效率就会因此得到提高。需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录。
  2. 尽量使用数字型字段若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能。引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
  3. 写出统一的SQL语句对于以下两句 SQL 语句,很多人都认为是相同的。不过数据库查询优化器则认为是不同的,虽然只是大小写不同,但必须进行两次解析,生成 2 个执行计划。所以应该保证相同的查询语句在任何地方都一致,多一个空格都不行。select * from dual select * From dual
  4. where 子句中索引列使用参数,也会导致索引失效因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num -- 不能使用索引 select id from t with(index(索引名)) where num=@num --可以改为强制查询使用索引:
  5. 使用复合索引须遵守最左前缀原则复合索引必须使用到最左边字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
  6. 当需要全表删除且无需回滚时,使用 Truncate 替代 delete。
  7. 使用表的别名当在 SQL 语句中连接多个表时, 使用表的别名并把别名前缀用于每个 Column 上,这样可以减少解析的时间并减少那些由 Column 歧义引起的语法错误。
  8. 避免使用耗费资源的操作:带有 DISTINCT, UNION, MINUS, INTERSECT, ORDER BY 的 SQL 语句,会启动 SQL 引擎执行耗费资源的排序功能,DISTINCT 需要一次排序操作,而其他的至少需要执行两次排序。通常带有 UNION, MINUS, INTERSECT 的 SQL 语句都可以用其他方式重写,如果你的数据库的 SORT_AREA_SIZE 调配得好, 使用 UNION , MINUS, INTERSECT 也是可以考虑的, 毕竟它们的可读性很强。
  9. 尽量避免向客户端返回大数据量。
  10. Update 语句,如果只更改 1、2 个字段,不要 Update 全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
  11. 应尽可能的避免更新聚簇索引数据列,因为聚簇索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。
  12. 尽量使用表变量来代替临时表。
  13. 考虑使用 “临时表” 暂存中间结果。临时表并不是不可使用,适当地使用它们可以使某些查询更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。将临时结果暂存在临时表,后面的查询就在临时表中查询了,这可以避免程序中多次扫描主表,也大大减少了程序执行中 “共享锁” 阻塞 “更新锁”,减少了阻塞,提高了并发性能。但是,对于一次性事件,较好使用导出表。
  14. 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先 create table,然后 insert。
  15. 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
  16. 避免频繁创建和删除临时表,以减少系统表资源的消耗。
  17. 尽量避免使用游标,因为游标的效率较差。与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括 “合计” 的例程通常要比使用游标执行的速度快。
  18. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。
  19. 尽量避免大事务操作,提高系统并发能力。
  20. 在运行代码中,尽量使用 PreparedStatement 来查询,不要用 Statement。



相关推荐

科氪 | 华硕天选6系列正式发布,搭载满功耗RTX 5060实际到手5999元起

5月19日晚,华硕旗下潮玩新次元游戏本天选6系列正式发布。作为Z世代青年的潮酷游戏装备,天选6系列再一次印证了其出色的综合实力。搭载满功耗RTX5060笔记本电脑GPU的天选6Pro以及天选6皆...

最新历史最低价显卡汇总!低端高端一网打尽

文|宋金戈责编|吕东兴总编|唐迪近期开展的618大促,各个品类尤其是数码科技好物的产品优惠都诚意满满,而作为消费者最关心的大类之一,显卡在最近同样表现不俗——华硕、技嘉等多个品牌,从1660s...

DNF:南山必胜客“败诉”,TX被DD373实锤,谁说玩家只有使用权?

TX和DD373的官司终于落下帷幕,南山必胜客终于中断了传奇。TX这一次被判定败诉。其中关于游戏账号和虚拟财产的问题,最受玩家关注。之前TX所说的:游戏账号不是玩家私有财产,游戏币更不是,只有使用权!...

Switch最新SX OS破解系统发布:可运行国行卡带xci镜像了

本周,TX团队(Team-Xecuter)发布了SXOS自制系统的最新版本,v2.9.5Beta,该版本支持了任天堂最新的V10.0.0和v10.0.1固件,同时改善了兼容性,优化了本地联机的稳定...

神舟战神TX8R5 QHD游戏本电脑今晚开售:2.5K屏+RTX4060,6999元

IT之家4月9日消息,神舟战神TX8R5QHD游戏本电脑今晚12点正式开售,仅有16GB+512GB一个版本可选,售价6999元。神舟战神TX8R5QHD搭载13代酷...

开创第三代手游商业模式《武极天下》推无商城玩法

翻阅游戏论坛与贴吧,大家对网络游戏的一些“坑爹”的设定抱怨不已,不是游戏职业设定不合理,就是游戏太烧钱,在游戏中被碾压的抬不起头,最后从玩游戏变成了被游戏玩,俨然游戏本质完全变味。由巨人移动自主研发并...

真满血Gen5电竞存储神装!佰维X570 Pro天启高速固态硬盘测评

2025一开年,对于存储行业来说,就是速度狂飙的开局。各固态硬盘品牌PCIe5.0SSD纷纷亮相,开启了新一轮的性能PK。在2024年异常活跃的佰维科技,也于CES期间推出了佰维X570PRO...

Kingston FURY Renegade G5评测 最速PCIe5.0固态硬盘诞生

【ZOL中关村在线原创评测】说起KingstonFURYRenegade叛逆者的大名,相信PCDIY玩家都知道这是金士顿高端存储的代名词,尤其是内存产品给人印象尤为深刻。实际上它还有固态硬盘系列...

《武极天下》力挺苹果TestFlight创业界先河

关于革旧鼎新,有人说就像是摸石头过河,在摸索中前行。一场正真的创新改革会带来什么?在数字行业,苹果公司的每一个产品都代表着一个时代的开启,它独立的操作系统、顺应用户需求的体验设计,在十几年时间内收获了...

闪迪至尊超极速移动固态硬盘USB4版评测 超越SSD的三防猛兽

移动固态硬盘,凭借TB级的容量和远超传统移动机械硬盘几倍、十几倍的速度,当仁不让地成为高效移动存储的代名词,为4K/8K超高清内容创作、海量文件备份带来了超乎想象的效率提升。国际知名品牌SanDisk...

RTX5070笔记本有哪些:游戏笔记本电脑哪个牌子好?

RTX5070游戏本,个人其实不是很喜欢,对于新款显卡,要不就考虑RTX5060,要不就考虑12GB显存的RTX5070Ti这样好一些,从RTX5070显卡的游戏本的量,也能看出来,估计5060会更多...

破军天下安装指南详解 游戏安装方法介绍

破军天下游戏怎么正确安装?破军天下安装方法是什么?对于破军天下中的安装问题,今天安趣网小编就为各位玩家小伙伴来好好的讲解下,让我们一起来看看,破军天下怎么正确安装游戏的吧!希望大家喜欢!安卓系统(An...

从高性能轻薄本到硬核电竞本 华硕天选新品解锁全场景体验

来源:环球网【环球网科技综合报道】5月19日,华硕天选多款笔记本新品正式发布。其中,14英寸轻薄全能本标杆华硕天选Air2025正式发布,显卡升级至GeForceRTX5060笔记本电脑GPU...

笔记本无线网卡有哪几种 有必要对其升级吗?

无线网卡是很多朋友购买笔记本时容易忽视的部分,相比较处理器、内存、显卡和硬盘,无线网卡几乎不可选,而且很少有厂商提到自家产品采用的无线网卡型号。大家可能遇到过这种情况:为什么手机可以连接到5Ghz,而...

下班了才发现,你的手机里藏着5个定时炸弹!

各位打工人累了一天终于可以躺平刷手机了吧?且慢!工信部最新通知,有些App比加班还可怕!它们可能正在偷偷收集你的信息,比你的老板还要了解你!今天就让我们一起来扒一扒这些“职场内鬼“,看看你的手机里有没...