面试官:可以聊聊有关数据库的优化吗?
Posted LuckyWangxs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官:可以聊聊有关数据库的优化吗?相关的知识,希望对你有一定的参考价值。
刁钻面试官
推荐:在准备面试的同学可以看看这个系列
面试干货3——基于JDK1.8的HashMap(与Hashtable的区别、数据结构、内存泄漏…)
面试干货4——你对Java类加载器(自定义类加载器)有了解吗?
面试干货6——输入网址按下回车后发生了什么?三次握手与四次挥手原来如此简单!
问题1:你怎么看待数据库优化这件事?
高并发环境下,数据库应该是我们关心的重点,不管是缓存技术还是负载均衡,说到底,都是为了减轻数据库的压力,那么在数据库优化方面,我认为有以下几个点,他们是从主到次的顺序:
- 以SQL优化、索引优化为主,解决慢SQL问题,最大程度地利用好索引
- 其次从数据库表结构入手、分库与分表,对数据量级进行处理
- 最大化利用机器配置,比如设置使用机器内存的大小
- 如果以上三点无法满足需求,那么再考虑硬件方面的问题,比如提升机器配置,再不行就多用几台服务器,这种成本较高,其性价比相对来说是最低的
1. 追问:请详细说说应该如何优化?
哼哼,心里露出来奸笑,你这种路数,我已摸透,早就准备地滚瓜烂熟了。意料之中的问题,面试官也不可能就问那么粗糙,没办法看出你的水平,我是这么答的:
那么我就主要说下SQL的优化吧,大概就三点
- 利用好索引,避免全表扫描
- 优化子查询
- 减少无效数据查询
首先我们要清楚我们写的SQL到了数据库实际的执行顺序是怎样的
<1>
FROM
数据库首先是看这个关键字,然后将其后的所有表进行笛卡尔积,变成一张虚表
<2>ON
看连接查询后的条件,将虚表进行过滤
<3>JOIN
包括LEFT JOIN、RIGHT JOIN
将主表剩余数据加到过滤后的虚表中
<4>WHERE
对虚表进行条件过滤
<5>GROUP BY
对上述结果进行分组
<6>HAVING
对分组后的结果进行聚合筛选,即如果有用到聚合函数,筛选需要用此关键字
<7>SELECT
选出要查字段,但这些字段必须包含在GROUP BY中
<8>DISTINCT
对查出的结果去重
<9>ORDER BY
排序
<10>LIMIT
行数限制
索引就像书的目录,有助于我们快速定位,但是索引也需要占据空间,检索也需要消耗资源,如果数据量少的情况下使用索引,那么必将得不偿失。想知道如何利用好索引就必须知道索引在什么时候有效,什么时候失效,下面说下索引失效的情况
1. 遇到null
select * from t where name is NULL;
假设字段name为索引,因为索引如果放空值,索引不知道把空值放在什么位置,所以上述SQL索引失效,导致进行全表扫描,可以选择设置特定默认值进行优化,比如name默认值为空字符串,优化后的SQL为
select * from t where name = '';
此SQL会走索引。
划重点: 一般情况下不建议默认值为NULL,因为NULL列在数据库表的行中都会多出一个位来标记是否为null,会占额外存储空间,这是官方的说法,而且,以NULL为默认值会有许多坑,比如COUNT(name)无法统计name字段为NULL的行,SUM也无法计算,此外就是上述导致索引失效的情况
2. 模糊查询
select * from t where name like '%wang%'
这种SQL会导致索引失效进而对全表进行扫描,可以选择后模糊查询进行优化,如下
select * from t where name like 'wang%'
该SQL索引不失效,这是由于违背了B+Tree索引最左匹配原则导致的。
3. 联合索引顺序问题
假设某表有字段w、q、a、b、c,现在建立联合索引index(a,b,c),那么有些情况是用不到索引的
select * from t where a = 1; # 走索引
select * from t where a = 1 and b = 2; # 走索引
select * from t where a = 1 and b = 2 and c = 3; # 走索引
select * from t where a = 1 and c = 3; # a走索引 c不走索引
select * from t where b = 2 and c = 3; # 不走索引
select * from t where c = 3; # 不走索引
select * from t where c = 3 and b = 2 and a = 1; # 走索引
当建立了联合索引后,满足前三个sql那种条件的才走索引,这也是最左匹配原则,即B+树的建立是以最左边的条件进行建立的,且建立出来的树是先按a字段组织排序的,然后字段a相同的,再按b字段组织排序,字段b相同的再按c字段组织排序,所以如果条件没将复合索引的最左字段放在前面,就没法检索了, 执行器就不知道从哪里开始了。看到最后一条SQL可能你有疑问,为啥第一个条件不是a也走索引了,是因为优化器做了优化,像三个条件全是判断是否相等的,优化器会调整条件的顺序为abc
4. 避免使用or,否则索引失效导致,进行全表扫描
select * from t where a = 1 or b = 2;
可以用union来代替or
select * from t where a = 1
union
select * from t where b = 2
5. 等号前面拒绝使用函数或者表达式
select * from t where a / 3 = 9
如此一来,索引会失效,进行全表扫描,所以尽量把表达式放到等号后面,优化如下
select * from t a = 9 * 3;
6. 避免写1=1
在开发中,经常会写1=1为了方便拼接查询条件。这种方式会导致索引失效(在mysql5.0
之前,执行器不会将1=1优化,5.0之后,会优化,将1=1删除),现在普遍用的是mybatis,所以要好好利用 < where > 标签
select * from t where 1=1 and a = 1;
7. 避免使用<>、!=
尽量避免使用不等于,如果必须使用,那尽量选一些没有建立索引的字段。
8. 类型隐式转换导致索引失效
select * from t a = 1;
如果a字段是VARCHAR类型,在查询的时候条件写成NUMBER类型,这就涉及到类型的隐式转换,最终导致无法正确走索引
以上列举了一些导致索引失效的场景,在编写SQL时注意这些便是对SQL的优化。此外,SQL优化还包括对子查询的一些优化、连接查询的优化以及,如下:
子查询优化: 如果在select后有子查询,那么这个SQL查询出多少行,子查询就会执行多少次,不仅如此,嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,但连接查询不会创建临时表,所以尽量使用INNER JOIN来代替子查询,以此来提高SQL性能。
连接查询: 在多表关联连接查询的时候,尽量将数据量小的表放在前头,在MySQL中,from后面的表是从左到右依次扫描的,小表扫描快,先扫描,大表没准只需扫描部分数据,就返回结果了
2. 追问:主键也为索引,那么主键该怎么选定类型呢?
推荐使用自增id作为主键。原因如下:
1.速度更快 自增的id一定是数字类型,相对于字符串而言,数字的比较速度快于字符串(字符串需要一个字母一个字母地进行 比较),在B+树中,值的插入、查找、 删除都要进行比较,这样的话用数字作为主键比用字符串做为主键比较速度会更快,那么增删改查就会更具有优势。
2.避免造成过多磁盘碎片 UUID是没有规则的,在B+树中的插入位置是非常随机的,而自增id每次都往B+树的最右边进行插入,这样B+树的分裂操作更简单,而且底层分配空间是分配一段连续的空间,这样每次都像往数组的最后插入数据,可以更合理的利用连续分配的空间,以免造成碎片过多。
3.节省磁盘空间 索引也是非常占用磁盘空间的,对字段小的列建索引更合理,比如对int型的列建索引就比对varchar(50)的列建索引更加节省磁盘空间。
3. 追问:可以用select * 吗?为什么?
不可以,我上面用select *
单纯为了方便。首先呢,这么写肯定可读性是非常差的,有一天,你的代码交接到了别的同事手里,别的同事在在捋代码的时候需要知道你的SQL查了哪些字段,点进去一看,一个*
代替了所有,直接就是一句MMP。
其实以下才是不用select *
的主要原因:
<1> 增加数据传输时间与网络开销
使用select *
取出全部列,当查询大量数据时,不必要的列会浪费大量资源,增加网络带宽,即使是服务与数据库部署在同一机器,请求数据库也是要走tcp协议,数据量越多,数据传输时间越长
<2> 增加IO操作
查询数据时,当数据长度超过728字节时,数据库会先把这样的数据序列化到另外一个地方,因此,读取这样的数据会增加一次IO操作。所以尽量不要用*
来取全部字段,只取我们需要的
<3> 可能会失去MySQL优化器"覆盖索引"策略的优化(InnoDB)
首先说一下什么叫覆盖索引。
在以InnoDB为引擎的表中,索引与数据是存放在一个文件中的,即为聚集索引,而MyISAM中,数据与索引分两个文件存储,索引只标记的数据地址,为非聚集索引,其没有覆盖索引。假设表t(id, name, age),id为主键,那么会有主键索引,另外在name上建立索引,这样,就会建立两棵B+Tree,第一棵以id组织起来的树,且每个叶子节点存的值为主键以及其他字段,像这样(1, ‘zs’, 18);第二棵树以name字段组织起来,且每个叶子节点存的值为当前字段与主键,像这样(‘zs’, 1),一般情况下,如果我们要以name字段作为查询条件,查询的字段为age,那么要先从第二棵树检索到id,然后再拿id从第一棵树中检索数据,如果我要的字段只是name,这样正好直接从第二棵树检索就行,减少了一次检索,这就叫覆盖索引。
当我们用select *
的时候,可能就会失去这种覆盖索引的优化机会
问题2:可以说说MySQL的数据删除方式吗?
有三种,分别是DELETE、Drop、Truncate,他们三个关键字都是删除数据用的,只不过应用的实际业务场景不同。
1. 追问:它们的区别是什么?
- 语言类型不同
DELETE是对表中数据的操作,是DML操作语言,而Drop与Truncate是对表的操作,属于DDL操作语言 - 执行速度不同
Drop > Truncate > DELETE - 原理不同
DELETE:
它是一行一行删除的,可以触发事务,这里的删除并不是真正意义上的删除,而是对某行数据进行标记,不可读的状态,也不会释放存储空间,当有新数据插入时会覆盖当前存储空间;在删除时也会生成删除日志存储下来,以备回滚使用,如果删除大量数据,会生成大量日志,很占存储空间。
Truncate:
会快速清空一张表,不会执行事务,一旦执行立马生效,无法找回,会释放磁盘空间,对于自增字段,会重置为1。其与Drop类似,会先drop表然后在create表,所以比drop慢一些。
Drop:
是删除表,顺带着数据一起删除,不会执行事务,一旦执行立马生效,并且无法找回,此删除是真正意义上的删除,会释放磁盘空间。
温馨提示: 除了DELETE,其他的请谨慎使用,除非你已经做好了进山里躲躲的准备了~
问题3:遇到慢SQL你是如何排查并解决的?
通过执行计划查看SQL慢的原因,然后调整。
EXPLAIN select id from t_test;
possible_keys:代表理论上会用到的索引
key:代表实际上用到的索引,若没有使用索引则显示为Null
rows:代表扫描行数
select_type:代表查询语句的复杂从程度,不包含任何子查询或UNION则为SIMPLE,查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY等等,可以自行了解下
如果有条慢SQL,首先执行SQL计划,看下实际用到的索引以及扫描的行数,如果没有用到索引,就要考虑是否存在索引失效的情况,看下扫描行数是否与得出结果相差甚远,如果相差巨大,也是索引没利用好,先解决索引问题,然后再检查是否有子查询等等,就按照上述SQL优化的方式一项一项检查即可。
1. 追问:如果上述问题都没出现,但还是慢,该怎么办?
这可能就要考虑非SQL优化层面的问题了,还记得开头说过的数据库优化按性价比排的优先级的第二点吗?如果SQL优化工作做完还是慢,那么就要从数据量级方面入手,分库分表。
好啦,本期文章就到这里了,各位,坐等offer吧
2. 追问:能详细说说分库分表吗?
先说分表吧,分表包括垂直分表与水平分表,垂直分表就相当于把一张表竖着劈开,直白点,就是字段拆分,单独放到一张表,再用某个字段将两个表关联;水平分表即把表横着切开,直白点就是多搞几张表,结构一样,按照某种逻辑将数据分配到各个表,就跟服务器负载均衡一个道理。
分库其实跟分表是一个道理,垂直分库,就跟垂直分表一样,竖着切,只不过垂直分表是将字段按照业务层面或者其他层面拿出来单独放到一张表,而垂直分库则是将一些业务相近的表或者功能类似的表提出来,单独放到一个库里,每个分库要有不同的表,不能有交集;水平分库即创建多个相同的数据库,数据库中的表都一模一样,只是表中的数据不同,没有交集。
以上是关于面试官:可以聊聊有关数据库的优化吗?的主要内容,如果未能解决你的问题,请参考以下文章