MySQL - SQL 优化
1. 在 MySQL 中,如何定位慢查询?
1.1 发现慢查询
现象:页面加载过慢、接口压力测试响应时间过长(超过 1s)
可能出现慢查询的场景:
- 聚合查询
- 多表查询
- 表数据过大查询
- 深度分页查询
1.2 通过现象定位到问题是出在 MySQL 的慢查询(查看慢日志法)
找到配置文件:
Windows:my.ini
Linux:/etc/my.cnf
添加或修改两个属性:
- slow_query_log=1(1 为 true,开启慢日志)
- long_query_time=2(单位为秒,超过 2 秒的将记录在慢日志)
选择性添加或修改,slow_query_log_file 属性,慢日志名
- Linux 在
/var/lib/mysql/localhost-slow.log
- Windows 见配置文件默认值
重启 MySQL:
-
Windows
-
net stop MySQL
-
net start MySQL
-
-
Linux
-
sudo systemctl stop mysql
-
sudo systemctl start mysql
-
发现问题,找到对应的慢日志,定位到问题是出在 MySQL 的慢查询:
1.3 回答问题
- 介绍一下当时产生问题的场景(我们当时的一个接口测试的时候非常的慢,压力测试的结果大概 5 秒钟);
- 而我们在调试阶段,开启了 MySQL 的慢日志记录,我们设置的值为 2 秒,一旦 sql 执行超过 2 秒就会记录在慢日志中,我们发现问题后查询了 MySQL 的慢日志,最终定位到问题是出在 MySQL 的慢查询;
2. 那这个 SQL 语句执行很慢,是如何分析的呢?
可以采用 MySQL 自带的分析工具 explain
- 通过 key 和 key_len 查询是否命中索引,也可以判断索引本身存在是否失效的情况;
- 通过 type 字段查看 sql 是否有进一步的优化空间,是否村咋全索引扫描或者全盘扫描;
- 通过 Extra 建议判断是否出现了回表的情况,如果出现了,可以尝试添加索引或者修改返回字段来修复;
possible_key 当前 sql 可能会使用到的索引
key 当前 sql 实际命中的索引
key_len 索引占用的大小
Extra 额外的优化建议
type 这条 sql 的连接类型,性能由好到差:NULL、system、const、eq_ref、ref、range、index、all
- NULL:没有使用到表
- system:查询 MySQL 系统内置的表
- const:根据主键索引查询
- eq_ref:主键索引查询或者唯一索引查询
- ref:索引查询
- range:分为查询
- index:索引树(全索引)扫描
- all:全盘扫描
3. 了解过索引吗?(什么是索引)
3.1 索引是什么
索引(index)是帮助 MySQL 高效获取 数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构**(B+ 树)**,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
3.2 什么是 B+ 树
3.3 回答
了解过索引吗?
- 索引(index)是帮助 MySQL 高效获取数据的有序的数据结构;
- 提高数据检索的效率,降低数据库的 IO 成本,因为不需要全表扫描;
- 通过索引列对数据进行排序,降低数据排序的成本,降低了 CPU 的消耗;
索引列:以表中哪个列来创建索引;
索引的底层数据结构了解过吗?
InnoDB是MySQL数据库管理系统中的一种事务性存储引擎。
MySQL 的 InnoDB 引擎采用的 B+ 树的数据结构来作为索引的存储结构;
- MySQL 的索引的底层数据结构是 B+ 树;
- 阶数更多,路径更短;磁盘读写代价低,非叶子节点只存储指针,叶子节点存储数据;
- B+ 树便于扫库和区间的查询,叶子节点是一个双向链表;
4. 什么是聚簇索引,什么是非聚簇索引?
4.1 什么是聚集索引,什么是二级索引(非聚集索引)?
如果没有主键,则会使用隐藏字段:DB_ROW_ID,隐藏主键
4.2 什么是回表查询?
了解什么是聚集索引,什么是二级索引(非聚集索引)后,再进行理解:
如果没有主键,则会使用隐藏字段:DB_ROW_ID,隐藏主键进行回表查询(如果不给隐藏主键创建索引,那么回表查询是没有走索引的,效率低下)
4.3 回答
- 聚簇索引(聚集索引):数据存放到索引中,B+ 树的叶子节点保存了整行数据,有且只有一个;
- 非聚簇索引(二级索引,非聚集索引):数据不全部存放到索引中,B+ 树的叶子节点保存了索引列以及对应的主键,可以有多个;
- 通过二级索引找到对应的主键值,再到聚集索引中查找整行数据,这个过程就是回表;(如果没有聚集索引,回表查询就不是通过索引查询了,而是全表查询,非常低性能)
5. 知道什么叫覆盖索引吗?
覆盖索引就是查询使用了索引,并且需要返回的列在该索引中已经全部覆盖到了;
- 聚集索引一定是,二级索引不需要回表查询也是;
回答:
- 聚集索引指的是查询使用了索引,返回的列,在索引中全部都能找到;
- 使用 id 查询,直接走聚集索引查询,一次索引扫描,直接返回全部数据,性能高;
- 如果所需列在索引中不存在,就会触发回表查询,所以尽量避免使用
select *
6. MySQL 超大分页怎么处理?
在数据量比较大的时候,如果进行 limit 分页查询,在查询的时候,越往后,分页查询效率越低。
6.1 超大分页场景
例如:
select * from user order by nickname limit 0, 10;
select * from user order by nickname limit 9000000, 10;
上面那个 0 毫秒不到,而下面那个甚至可以达到 10 秒以上!
因为,在执行的时候,需要加载 9000010 条记录(每条都是 raw),再选取 9000000 - 9000010 的记录,其他记录丢弃,查询排序的非常大。
而 nickname 在这里不是覆盖索引,所以加载 9000010 条记录时,性能很低。
- order by 子句使用索引需要:
order by
子句中的字段必须创建了索引,索引查询的字段覆盖需要查询的字段;order by
子句中的字段要符合最左前缀法则(对于复合索引);
- 像这种非覆盖索引,回表的性能还不如全表查询呢,所以不走索引在这里是好事;
6.2 超大分页 SQL优化
但是我们知道这条 sql 中是覆盖索引:
select id from user ordery by nickname limit 9000000, 10;
那么我们再拿这 10 个 id 去表中查询即可。
因此 sql 可以优化成这样(覆盖索引 + 子查询):
select * fromuser u, (select id from user order by nickname limit 9000000, 10) a
where u.id = a.id;
6.3 回答
-
问题在于在数据量比较大时,limit 分页查询,需要对数据进行排序,效率低。
-
可以用到索引(有序性)查询,而如果不是覆盖索引,那么可以用覆盖索引 + 子查询进行优化!
7. 索引创建的原则有哪些?
先陈述自己实际开发中怎么用索引的,用了什么索引,如主键索引、唯一索引、复合索引…
再说原则:
- 数据量较大,且查询比较频繁的表;(10w+ 就可以创建索引增加用户体验了)
- 常常作为查询条件、排序操作、分组操作的字段;
- 尽量使用联合索引(多列索引),减少单列索引,这样可以让查询更可能是覆盖索引;
- 要控制索引的数量,并不是越多越好,增删改都是需要维护的;
- 字段内容区分度高,尽量建立唯一索引,区分度越高性能越好;
- 字符串类型字段,内容较长,可以使用前缀索引;
- 如果索引列不能存储 NULL 值,在创建表的时候使用 NOT NULL 约束,这有利于让优化器选择哪个索引进行更有效的查询;
8. 什么情况下索引会失效?
8.1 复合索引
顺序见 Seq_in_index
,即 name、status、address
8.2 违反最左前缀法则
SQL 的查询条件 / 排序 / 分组从索引的最左前列开始,才会走索引:
正向例子:
反向例子:
跳过某一列,则只有部分最左前缀索引生效:
8.3 范围查询右边的列,不能使用索引
下面那个,name 和 status 走索引,status 右边的字段 address 没用到索引。
8.4 不要再索引列上进行运算操作,索引会失效
8.5 字符串不加单引号,造成索引失效
复杂行为往往导致索引失效~
8.6 模糊查询有可能会导致索引失效
头部模糊匹配,索引失效。如果仅仅是尾部模糊匹配,索引不会失效。
8.7 回答
同理,先陈述自己的遭遇,如某个场景创建了索引,性能还是很慢,explain 去查看 sql 语句的执行计划,发现索引失效了。
对于复合索引:
- 违反最左前缀法则;
- 范围查询右边的列;
- 在索引列上进行运算操作;
- 字符串不加单引号,导致 MySQL 优化器进行类型转化;
- 头部模糊查询;
通常情况下,可以使用 explain 查看 sql 的执行计划来判断索引是否失效。
9. 谈一谈你对 SQL 优化的经验
从三个方面:
- 表的设计优化
- 索引优化(参考优化创建原则和索引失效)
- SQL 语句优化
9.1 表的设计优化(参考阿里开发手册《嵩山版》)
- 比如设置合适的数值(tinyint、int、bigint)要根据实际情况选择;
- 比如设置合适的字符串类型(char,varchar)char定长效率高,varchar可变长度,效率稍低;
9.2 SQL 语句的优化
- select 语句务必指明字段的名称(避免使用
select *
); - SQL 语句要避免造成索引失效的写法;
- 尽量用 union all 代替 union,union 会多一次过滤,效率低(union 会将重复的过滤掉,如果知道没有重复的就可以用 union all)
这个就不能用 union all,并不是存在绝对地去优化,而是看情况决定~
-
避免在 where 子句对字段进行表达式/函数操作;
-
join 优化,能用 inner join 就不用 left join 或者 right join,如果必须使用,一定要以小表为驱动;
-
小表:数据量较小的表,大表:数据量较大的表,
-
这样的好处就是以小表连接大表,连接次数较小,on 子句查询的次数较小,并且由于是查询大表,所以如果是有索引,索引效果更明显!
-
内连接会对两个表进行优化,优先把小表放外边,把大表放里面;
-
left join 或者 right join 则不会重新调整顺序;
-