[灵魂拷问]MySQL面试一百问

目录

前言

<font color="red">本文主要受众为开发人员,因此不涉及到MySQL的服务部署等操做,且内容较多,你们准备好耐心和瓜子矿泉水.</font>mysql

前一阵系统的学习了一下MySQL,也有一些实际操做经验,偶然看到一篇和MySQL相关的面试文章,发现其中的一些问题本身也回答很差,虽然知识点大部分都知道,可是没法将知识串联起来.面试

所以决定搞一个MySQL灵魂100问,试着用回答问题的方式,让本身对知识点的理解更加深刻一点.redis

此文不会事无巨细的从select的用法开始讲解mysql,主要针对的是开发人员须要知道的一些MySQL的知识点,主要包括索引,事务,优化等方面,以在面试中高频的问句形式给出答案.若是您有其余的MySQL面试题,以为题目尚有意思或者难度,能够评论题目或者发送邮件至huyanshi2580@gmail.com,我会将其收录进此文并标注您的姓名.sql

本文大部份内容,在其余文章中作过详细的解释,尤为是索引以及事务两块内容,分别在于下面的文章中,有兴趣加深理解的朋友们能够移步.数据库

MySQL索引原理极其优化后端

mysql的事务极其隔离级别缓存

索引相关

关于MySQL的索引,曾经进行过一次总结,文章连接在这里 Mysql索引原理及其优化.安全

1. 什么是索引?网络

索引是一种数据结构,能够帮助咱们快速的进行数据的查找.数据结构

2. 索引是个什么样的数据结构呢?

索引的数据结构和具体存储引擎的实现有关, 在MySQL中使用较多的索引有Hash索引,B+树索引等,而咱们常用的InnoDB存储引擎的默认索引实现为:B+树索引.

3. Hash索引和B+树全部有什么区别或者说优劣呢?

首先要知道Hash索引和B+树索引的底层实现原理:

hash索引底层就是hash表,进行查找时,调用一次hash函数就能够获取到相应的键值,以后进行回表查询得到实际数据.B+树底层实现是多路平衡查找树.对于每一次的查询都是从根节点出发,查找到叶子节点方能够得到所查键值,而后根据查询判断是否须要回表查询数据.

那么能够看出他们有如下的不一样:

  • hash索引进行等值查询更快(通常状况下),可是却没法进行范围查询.

由于在hash索引中通过hash函数创建索引以后,索引的顺序与原顺序没法保持一致,不能支持范围查询.而B+树的的全部节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也相似),自然支持范围.

  • hash索引不支持使用索引进行排序,原理同上.
  • hash索引不支持模糊查询以及多列索引的最左前缀匹配.原理也是由于hash函数的不可预测.AAAAAAAAB的索引没有相关性.
  • hash索引任什么时候候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候能够只经过索引完成查询.
  • hash索引虽然在等值查询上较快,可是不稳定.性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差.而B+树的查询效率比较稳定,对于全部的查询都是从根节点到叶子节点,且树的高度较低.

所以,在大多数状况下,直接选择B+树索引能够得到稳定且较好的查询速度.而不须要使用hash索引.

4. 上面提到了B+树在知足聚簇索引和覆盖索引的时候不须要回表查询数据,什么是聚簇索引?

在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引. 在InnoDB中,只有主键索引是聚簇索引,若是没有主键,则挑选一个惟一键创建聚簇索引.若是没有惟一键,则隐式的生成一个键来创建聚簇索引.

当查询使用聚簇索引时,在对应的叶子节点,能够获取到整行数据,所以不用再次进行回表查询.

5. 非聚簇索引必定会回表查询吗?

不必定,这涉及到查询语句所要求的字段是否所有命中了索引,若是所有命中了索引,那么就没必要再进行回表查询.

举个简单的例子,假设咱们在员工表的年龄上创建了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询.

6. 在创建索引的时候,都有哪些须要考虑的因素呢?

创建索引的时候通常要考虑到字段的使用频率,常常做为条件进行查询的字段比较适合.若是须要创建联合索引的话,还须要考虑联合索引中的顺序.此外也要考虑其余方面,好比防止过多的全部对表形成太大的压力.这些都和实际的表结构以及查询方式有关.

7. 联合索引是什么?为何须要注意联合索引中的顺序?

MySQL可使用多个字段同时创建一个索引,叫作联合索引.在联合索引中,若是想要命中索引,须要按照创建索引时的字段顺序挨个使用,不然没法命中索引.

具体缘由为:

MySQL使用索引时须要索引有序,假设如今创建了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,若是name相同,则按照age排序,若是age的值也相等,则按照school进行排序.

当进行查询时,此时索引仅仅按照name严格有序,所以必须首先使用name字段进行等值查询,以后对于匹配到的列而言,其按照age字段严格有序,此时可使用age字段用作索引查找,,,以此类推.所以在创建联合索引的时候应该注意索引列的顺序,通常状况下,将查询需求频繁或者字段选择性高的列放在前面.此外能够根据特例的查询或者表结构进行单独的调整.

8. 建立的索引有没有被使用到?或者说怎么才能够知道这条语句运行很慢的缘由?

MySQL提供了explain命令来查看语句的执行计划,MySQL在执行某个语句以前,会将该语句过一遍查询优化器,以后会拿到对语句的分析,也就是执行计划,其中包含了许多信息.
能够经过其中和索引有关的信息来分析是否命中了索引,例如possilbe_key,key,key_len等字段,分别说明了此语句可能会使用的索引,实际使用的索引以及使用的索引长度.

9. 那么在哪些状况下会发生针对该列建立了索引可是在查询的时候并无使用呢?

  • 使用不等于查询,
  • 列参与了数学运算或者函数
  • 在字符串like时左边是通配符.相似于'%aaa'.
  • 当mysql分析全表扫描比使用索引快的时候不使用索引.
  • 当使用联合索引,前面一个条件为范围查询,后面的即便符合最左前缀原则,也没法使用索引.

以上状况,MySQL没法使用索引.

事务相关

1. 什么是事务?

理解什么是事务最经典的就是转帐的栗子,相信你们也都了解,这里就再也不说一边了.

事务是一系列的操做,他们要符合ACID特性.最多见的理解就是:事务中的操做要么所有成功,要么所有失败.可是只是这样还不够的.

2. ACID是什么?能够详细说一下吗?

A=Atomicity

原子性,就是上面说的,要么所有成功,要么所有失败.不可能只执行一部分操做.

C=Consistency

系统(数据库)老是从一个一致性的状态转移到另外一个一致性的状态,不会存在中间状态.

I=Isolation

隔离性: 一般来讲:一个事务在彻底提交以前,对其余事务是不可见的.注意前面的一般来讲加了红色,意味着有例外状况.

D=Durability

持久性,一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果.

3. 同时有多个事务在进行会怎么样呢?

多事务的并发进行通常会形成如下几个问题:

  • 脏读: A事务读取到了B事务未提交的内容,而B事务后面进行了回滚.
  • 不可重复读: 当设置A事务只能读取B事务已经提交的部分,会形成在A事务内的两次查询,结果居然不同,由于在此期间B事务进行了提交操做.
  • 幻读: A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据.形成"幻觉".

4. 怎么解决这些问题呢?MySQL的事务隔离级别了解吗?

MySQL的四种隔离级别以下:

  • 未提交读(READ UNCOMMITTED)

这就是上面所说的例外状况了,这个隔离级别下,其余事务能够看到本事务没有提交的部分修改.所以会形成脏读的问题(读取到了其余事务未提交的部分,而以后该事务进行了回滚).

这个级别的性能没有足够大的优点,可是又有不少的问题,所以不多使用.

  • 已提交读(READ COMMITTED)

其余事务只能读取到本事务已经提交的部分.这个隔离级别有 不可重复读的问题,在同一个事务内的两次读取,拿到的结果居然不同,由于另一个事务对数据进行了修改.

  • REPEATABLE READ(可重复读)

可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),可是仍然有一个新问题,就是 幻读,当你读取id> 10 的数据行时,对涉及到的全部行加上了读锁,此时例外一个事务新插入了一条id=11的数据,由于是新插入的,因此不会触发上面的锁的排斥,那么进行本事务进行下一次的查询时会发现有一条id=11的数据,而上次的查询操做并无获取到,再进行插入就会有主键冲突的问题.

  • SERIALIZABLE(可串行化)

这是最高的隔离级别,能够解决上面提到的全部问题,由于他强制将因此的操做串行执行,这会致使并发性能极速降低,所以也不是很经常使用.

5. Innodb使用的是哪一种隔离级别呢?

InnoDB默认使用的是可重复读隔离级别.

6. 对MySQL的锁了解吗?

当数据库有并发事务的时候,可能会产生数据的不一致,这时候须要一些机制来保证访问的次序,锁机制就是这样的一个机制.

就像酒店的房间,若是你们随意进出,就会出现多人抢夺同一个房间的状况,而在房间上装上锁,申请到钥匙的人才能够入住而且将房间锁起来,其余人只有等他使用完毕才能够再次使用.

7. MySQL都有哪些锁呢?像上面那样子进行锁定岂不是有点阻碍并发效率了?

从锁的类别上来说,有共享锁和排他锁.

共享锁: 又叫作读锁. 当用户要进行数据的读取时,对数据加上共享锁.共享锁能够同时加上多个.

排他锁: 又叫作写锁. 当用户要进行数据的写入时,对数据加上排他锁.排他锁只能够加一个,他和其余的排他锁,共享锁都相斥.

用上面的例子来讲就是用户的行为有两种,一种是来看房,多个用户一块儿看房是能够接受的. 一种是真正的入住一晚,在这期间,不管是想入住的仍是想看房的都不能够.

锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁.

他们的加锁开销从大大小,并发能力也是从大到小.

表结构设计

1. 为何要尽可能设定一个主键?

主键是数据库确保数据行在整张表惟一性的保障,即便业务上本张表没有主键,也建议添加一个自增加的ID列做为主键.设定了主键以后,在后续的删改查的时候可能更加快速以及确保操做数据范围安全.

2. 主键使用自增ID仍是UUID?

推荐使用自增ID,不要使用UUID.

由于在InnoDB存储引擎中,主键索引是做为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及所有的数据(按照顺序),若是主键索引是自增ID,那么只须要不断向后排列便可,若是是UUID,因为到来的ID与原来的大小不肯定,会形成很是多的数据插入,数据移动,而后致使产生不少的内存碎片,进而形成插入性能的降低.

总之,在数据量大一些的状况下,用自增主键性能会好一些.

图片来源于《高性能MySQL》: 其中默认后缀为使用自增ID,_uuid为使用UUID为主键的测试,测试了插入100w行和300w行的性能.

2019-07-18-15-00-18

关于主键是聚簇索引,若是没有主键,InnoDB会选择一个惟一键来做为聚簇索引,若是没有惟一键,会生成一个隐式的主键.

If you define a PRIMARY KEY on your table, InnoDB uses it as the clustered index.

If you do not define a PRIMARY KEY for your table, MySQL picks the first UNIQUE index that has only NOT NULL columns as the primary key and InnoDB uses it as the clustered index.

3. 字段为何要求定义为not null?

MySQL官网这样介绍:

NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.

null值会占用更多的字节,且会在程序中形成不少与预期不符的状况.

4. 若是要存储用户的密码散列,应该使用什么字段进行存储?

密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样能够节省空间且提升检索效率.

存储引擎相关

1. MySQL支持哪些存储引擎?

MySQL支持多种存储引擎,好比InnoDB,MyISAM,Memory,Archive等等.在大多数的状况下,直接选择使用InnoDB引擎都是最合适的,InnoDB也是MySQL的默认存储引擎.

  1. InnoDB和MyISAM有什么区别?
  • InnoDB支持事物,而MyISAM不支持事物
  • InnoDB支持行级锁,而MyISAM支持表级锁
  • InnoDB支持MVCC, 而MyISAM不支持
  • InnoDB支持外键,而MyISAM不支持
  • InnoDB不支持全文索引,而MyISAM支持。

零散问题

1. MySQL中的varchar和char有什么区别.

char是一个定长字段,假如申请了char(10)的空间,那么不管实际存储多少内容.该字段都占用10个字符,而varchar是变长的,也就是说申请的只是最大长度,占用的空间为实际字符长度+1,最后一个字符存储使用了多长的空间.

在检索效率上来说,char > varchar,所以在使用中,若是肯定某个字段的值的长度,可使用char,不然应该尽可能使用varchar.例如存储用户MD5加密后的密码,则应该使用char.

2. varchar(10)和int(10)表明什么含义?

varchar的10表明了申请的空间长度,也是能够存储的数据的最大长度,而int的10只是表明了展现的长度,不足10位以0填充.也就是说,int(1)和int(10)所能存储的数字大小以及占用的空间都是相同的,只是在展现时按照长度展现.

3. MySQL的binlog有有几种录入格式?分别有什么区别?

有三种格式,statement,row和mixed.

  • statement模式下,记录单元为语句.即每个sql形成的影响会记录.因为sql的执行是有上下文的,所以在保存的时候须要保存相关的信息,同时还有一些使用了函数之类的语句没法被记录复制.
  • row级别下,记录单元为每一行的改动,基本是能够所有记下来可是因为不少操做,会致使大量行的改动(好比alter table),所以这种模式的文件保存的信息太多,日志量太大.
  • mixed. 一种折中的方案,普通操做使用statement记录,当没法使用statement的时候使用row.

此外,新版的MySQL中对row级别也作了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录.

4. 超大分页怎么处理?

超大的分页通常从两个方向上来解决.

  • 数据库层面,这也是咱们主要集中关注的(虽然收效没那么大),相似于select * from table where age > 20 limit 1000000,10这种查询其实也是有能够优化的余地的. 这条语句须要load1000000数据而后基本上所有丢弃,只取10条固然比较慢. 当时咱们能够修改成select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,可是因为索引覆盖,要查询的全部字段都在索引中,因此速度会很快. 同时若是ID连续的好,咱们还能够select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,可是核心思想都同样,就是减小load的数据.
  • 从需求的角度减小这种请求....主要是不作相似的需求(直接跳转到几百万页以后的具体某一页.只容许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.

解决超大分页,其实主要是靠缓存,可预测性的提早查到内容,缓存至redis等k-V数据库中,直接返回便可.

在阿里巴巴《Java开发手册》中,对超大分页的解决办法是相似于上面提到的第一种.

2019-07-18-11-46-19

5. 关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

在业务系统中,除了使用主键进行的查询,其余的我都会在测试库上测试其耗时,慢查询的统计主要由运维在作,会按期将业务中的慢查询反馈给咱们.

慢查询的优化首先要搞明白慢的缘由是什么? 是查询条件没有命中索引?是load了不须要的数据列?仍是数据量太大?

因此优化也是针对这三个方向来的,

  • 首先分析语句,看看是否load了额外的数据,多是查询了多余的行而且抛弃掉了,多是加载了许多结果中并不须要的列,对语句进行分析以及重写.
  • 分析语句的执行计划,而后得到其使用索引的状况,以后修改语句或者修改索引,使得语句能够尽量的命中索引.
  • 若是对语句的优化已经没法进行,能够考虑表中的数据量是否太大,若是是的话能够进行横向或者纵向的分表.

6. 上面提到横向分表和纵向分表,能够分别举一个适合他们的例子吗?

横向分表是按行分表.假设咱们有一张用户表,主键是自增ID且同时是用户的ID.数据量较大,有1亿多条,那么此时放在一张表里的查询效果就不太理想.咱们能够根据主键ID进行分表,不管是按尾号分,或者按ID的区间分都是能够的. 假设按照尾号0-99分为100个表,那么每张表中的数据就仅有100w.这时的查询效率无疑是能够知足要求的.

纵向分表是按列分表.假设咱们如今有一张文章表.包含字段id-摘要-内容.而系统中的展现形式是刷新出一个列表,列表中仅包含标题和摘要,当用户点击某篇文章进入详情时才须要正文内容.此时,若是数据量大,将内容这个很大且不常用的列放在一块儿会拖慢原表的查询速度.咱们能够将上面的表分为两张.id-摘要,id-内容.当用户点击详情,那主键再来取一次内容便可.而增长的存储量只是很小的主键字段.代价很小.

固然,分表其实和业务的关联度很高,在分表以前必定要作好调研以及benchmark.不要按照本身的猜测盲目操做.

7. 什么是存储过程?有哪些优缺点?

存储过程是一些预编译的SQL语句。
一、更加直白的理解:存储过程能够说是一个记录集,它是由一些T-SQL语句组成的代码块,这些T-SQL语句代码像一个方法同样实现一些功能(对单表或多表的增删改查),而后再给这个代码块取一个名字,在用到这个功能的时候调用他就好了。
二、存储过程是一个预编译的代码块,执行效率比较高,一个存储过程替代大量T_SQL语句 ,能够下降网络通讯量,提升通讯速率,能够必定程度上确保数据安全

可是,在互联网项目中,实际上是不太推荐存储过程的,比较出名的就是阿里的《Java开发手册》中禁止使用存储过程,我我的的理解是,在互联网项目中,迭代太快,项目的生命周期也比较短,人员流动相比于传统的项目也更加频繁,在这样的状况下,存储过程的管理确实是没有那么方便,同时,复用性也没有写在服务层那么好.

8. 说一说三个范式

第一范式: 每一个列都不能够再拆分.
第二范式: 非主键列彻底依赖于主键,而不能是依赖于主键的一部分.
第三范式: 非主键列只依赖于主键,不依赖于其余非主键.

在设计数据库结构的时候,要尽可能遵照三范式,若是不遵照,必须有足够的理由.好比性能. 事实上咱们常常会为了性能而妥协数据库的设计.

9. MyBatis中的#和$有什么区别?

乱入了一个奇怪的问题.....我只是想单独记录一下这个问题,由于出现频率过高了.

# 会将传入的内容当作字符串,而$会直接将传入值拼接在sql语句中.

因此#能够在必定程度上预防sql注入攻击.


联系我

最后,欢迎关注个人我的公众号【 呼延十 】,会不按期更新不少后端工程师的学习笔记。
也欢迎直接公众号私信或者邮箱联系我,必定知无不言,言无不尽。

完。








<h4>ChangeLog</h4>
2019-05-19 完成



以上皆为我的所思所得,若有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文连接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见我的博客------>呼延十