version:1.0
文章目录
- 基础篇
- 🙎♂️面试官: 非关系型数据库和关系型数据库的区别?
- 🙎♂️面试官: MySQL 数据库两种存储引擎的区别?
- 事务篇
- 🙎♂️面试官: 事务的四大特性了解么?
- 🙎♂️面试官:之前提到了 undo log 和 redo log,你了解吗?
- 🙎♂️面试官: MySQL 事务隔离级别?默认是什么级别?
- 🙎♂️面试官: MVCC了解吗?
- 索引篇
- 🙎♂️面试官:了解过索引吗?/ 为什么索引能提高查询速度?
- 🙎♂️面试官:聚集索引和非聚集索引的区别?非聚集索引一定回表查询吗?
- 🙎♂️面试官:MySQL的超大分页怎么优化?
- 🙎♂️面试官:索引这么多优点,为什么不对表中的每一个列创建一个索引呢?/使用索引一定能提高查询性能吗? / 索引创建的原则?
- 🙎♂️面试官:优化索引的方法有哪些?
- 🙎♂️面试官:Hash 索引和 B+树索引优劣分析
- 🙎♂️面试官:B+树做索引比红黑树/B树好在哪里? / 为什么用B + 树做索引?
- 🙎♂️面试官:最左前缀匹配原则了解么?为什么是最左前缀匹配呢?
- 🙎♂️面试官:索引失效的情况了解吗?
- 🙎♂️面试官:count(*)和count(1)有什么区别?哪个性能好?
- 进阶篇
- 🙎♂️面试官: 一条 SQL 语句在 MySQL 中如何执行的?
- 🙎♂️面试官:explain命令了解吗?
- 🙎♂️面试官:大表优化的思路、SQL调优的思路?
- 🙎♂️面试官:项目中用过分库分表吗?
基础篇
🙎♂️面试官: 非关系型数据库和关系型数据库的区别?
🙋♂答:
- 存储数据的类型不同。
关系型数据库使用二维的表来进行存储;非关系型数据库有多种数据存储方式,比如Redis使用K-V存储。 - 查询效率不同。
关系型数据库的数据是持久化了的。只能和硬盘进行I/O;而非关系型数据库可以存储在硬盘上,也可以存储在内存中,从内存读取数据要比硬盘快得多。在处理大量数据时,可以考虑使用非关系型数据库来提升效率。
🙎♂️面试官: MySQL 数据库两种存储引擎的区别?
🙋♂答:
- 对事务的支持:InnoDB存储引擎支持事务,MyISAM不支持事务。也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB。
- 并发性能:InnoDB锁的细粒度比MyISAM高,InnoDB有行级锁,MyISAM最高是表级锁。这意味这MyISAM在写操作时会锁住全表,其他写操作必须等待。
- 执行
SELECT COUNT(*) FROM TABLE
效率:MyISAM引擎中直接保存了表的行数,当执行SELECT COUNT(*) FROM TABLE 时,就不需要进行全表扫描。
综上所述:如果数据库读的操作远远多于写的操作,并且对事务无要求,可以考虑使用MyISAM;如果对于并发要求高,事务支持的场景,应该使用InnoDB。
事务篇
🙎♂️面试官: 事务的四大特性了解么?
🙋♂答:
- 原子性(Atomicity):同一个事务中的所有操作,要么全部完成,要么全部回滚。那么MySQL主要通过undo log 来实现原子性的。
- 在事务还没提交之前,MSQL会先记录更新前的数据到undo log日志中,当事务回滚时,可以利用undo log 日志文件进行回滚。
- 一致性(Consistency):事务的执行不会破坏数据的完整性约束。
- 什么叫完整性约束?其实就是要符合业务逻辑。比如完整性约束要求A + B的和为100,那么在事务执行后,应该依然满足完整性约束。
- 隔离性(Isolation):隔离性针对多个事务,多个事务同时使用相同的数据时,不会相互干扰。
- 因为每个事务都有一个完整的数据空间,对其他并发事务是隔离的。举个例子就是消费者购买商品这个事务,是不影响其他消费者购买的。
- 持久性(Durability):事务一旦提交,对数据的修改就是永久的。即使系统故障,也不应该丢失数据。那么MySQL主要通过redo log 来实现持久性的。
- redo log记录了数据页的修改。 在事务提交时,先将redo log 持久化到磁盘。MySQL重启后,可以根据redo log的内容,将所有数据恢复到最新状态,保证了持久性。
🙎♂️面试官:之前提到了 undo log 和 redo log,你了解吗?
🙋♂答:
undo,也就是记录还没有做变更之前的数据。在事务提交之前,undo log会先记录当前数据的状态。当事务回滚时,可以使用undo log来进行回滚。
undo ,还有一个作用就是用来实现MVCC,MySQL在执行快照读的时候,会根据事务的Read View提供的信息,通过undo log 的版本链寻找满足条件的可见的记录。
redo,也就是记录已经修改之后的数据,在事务提交之后,redo log 会记录当前数据的状态,如果服务器发生崩溃,可以使用redo log + WAL技术,将所有数据恢复到最新的状态。
- WAL:预先日志持久化,也就是MySQL的写操作不会先写到硬盘,而是会先记录在redo log中。在合适的时间再刷新到磁盘上。
🙎♂️面试官: MySQL 事务隔离级别?默认是什么级别?
🙋♂答:
- 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读已提交(read committed,RC),指一个事务提交之后,它做的变更才能被其他事务看到;避免了脏读。
- 可重复读(repeatable read,RR),指一个事务执行中,读取到的数据前后一致。是MySQL InnoDB 引擎的默认隔离级别。避免了脏读和不可重复读。
- 串行化(serializable ),同时只能有一个事务执行,其他事务必须等到前一个事务执行完才可以执行。脏读、不可重复读和幻读现象都不可能会发生。
🙎♂️面试官: MVCC了解吗?
🙋♂答:
MVCC全称是多版本并发控制 (Multi-Version Concurrency Control),只有在InnoDB存储引擎下存在。可以维护一个数据的多个版本,使得在RR和RC隔离级别下的读写没有冲突(读取的数据依据版本链进行控制,所以读取数据时也可以写入数据),从而提高MySQL的并发性能。
MVCC的实现依赖于隐藏字段,Read View、undo log版本链。
- 隐藏字段
trx_id
,保存事务id,表示这个数据是哪个事务生成的。roll_pointer
,是一个指针,指向上一个版本的记录。每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo log 中,于是就可以通过这个指针找到修改前的记录。
- undo log
- undo log 会记录更新前的数据,MySQL 在执行快照读的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。
- Read View
- Read View是一个数据快照,在快照读SQL执行时,会根据Read View来提取记录。
- RC隔离级别下,每次SELECT都会生成一个Read View。
- RR隔离级别下,只会在启动事务时生成一个Read View。
- Read View 维护的四个字段
creator_trx_id
:创建该 Read View 的事务 id。m_ids
:创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表。- " 活跃事务 "指的就是,启动了但还没提交的事务。
min_trx_id
:创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。max_trx_id
:创建 Read View 时,当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1。
- 版本链数据访问规则
- trx_id < min_trx_id:可以访问该版本。
- trx_id > max_trx_id:不可以访问该版本。
- min_trx_id <= trx_id <= max_trx_id 并且在 m_ids 列表中,不可以访问该版本(表示事务未提交)。不在m_ids列表中,可以访问该版本(表示事务已提交)。
索引篇
🙎♂️面试官:了解过索引吗?/ 为什么索引能提高查询速度?
🙋♂答:
索引是一种数据结构,这种数据结构以一种特殊的方式引用数据,可以**帮助MySQL高效地获取数据,**减少磁盘IO的成本。
如果不使用索引,MySQL直接走全表扫描,就要一条一条去对比符合条件的记录,这样查询到想要的数据可能会进行很多次磁盘I/O,是很浪费时间的;如果使用索引,比如MySQL使用B + 树作为索引,B + 树就是很高效的数据结构,可以有效减少磁盘I/O的次数。
B+Tree 存储千万级数据只需要 3-4 层高度就可以满足,从千万级的表(1000 * 1000 * 16)查询目标数据最多需要 3-4 次磁盘 I/O。
🙎♂️面试官:聚集索引和非聚集索引的区别?非聚集索引一定回表查询吗?
🙋♂答:
- 叶子节点存放的数据不同。 聚簇索引的叶子节点存放的是实际数据,所有完整的用户记录都存放在叶子节点;聚簇索引的叶子节点存放主键值,不是实际数据。
- 一般情况下会使用主键作为聚簇索引,由于一张表只能有一个聚簇索引,为了实现非主键字段的快速搜索,就引出了二级索引(非聚簇索引/辅助索引)。
第二个问题:
回表,就是先检索二级索引,找到叶子节点并获取主键值,通过聚簇索引查询到对应的叶子节点,也就是要查两个 B+Tree 才能查到数据。
显而易见的,非聚簇索引不一定需要回表操作。索引覆盖,覆盖索引是指 SQL 中 查询 的所有字段,都能直接从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作,也就是只需要查一个 B+Tree就能找到数据。
举例:
select id from product where product_no = '0002';
# using index:使用了覆盖索引
🙎♂️面试官:MySQL的超大分页怎么优化?
🙋♂答:
在数据量很大时,需要使用LIMIT进行分页查询,需要对数据进行排序,效率很低,可以使用覆盖索引 + 子查询的方法解决。先分页查询获取id,并且对id进行排序处理,这样就会走索引,筛选出id集合后,再和表进行关联查询,就可以提升效率。
举例SELECT * FROM TABLES LIMIT 9000000 ,10 ;
,MySQL会排序前9000010记录,丢弃之前的记录,只要10条,这样查询的效率很低。
可以考虑使用覆盖索引来进行优化。改为:
SELECT * FROM
TABLE t,
(SELECT id FROM TABLE ORDER BY id LIMIT 9000000 , 10) a
WHERE t.id = a.id;
🙎♂️面试官:索引这么多优点,为什么不对表中的每一个列创建一个索引呢?/使用索引一定能提高查询性能吗? / 索引创建的原则?
🙋♂答:
- 索引也需要占用磁盘空间。并且创建、维护索引要耗费时间,这种时间随着数据量的增加而增大。所以要根据情况来创建索引。
- 对于数据量较大的表,使用索引。
- 选择字段有唯一性限制,区分度高的字段作为索引,比如商品编码。
- 经常用于 WHERE 查询条件、GROUP BY 和 ORDER BY 的字段。
- 如果是字符串类型,并且字段长度较长,可以建立前缀索引,减少索引占用的空间。
- 索引有可能失效。
- 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。因为索引是有序的,前缀模糊匹配无法走索引。
- 对索引列进行表达式计算、使用函数,这些情况下都会造成索引失效。
🙎♂️面试官:优化索引的方法有哪些?
🙋♂答:
- 使用前缀索引:使用字段中字符串的前几个字符建立索引,可以减小索引字段大小,节省空间。可以增加一个索引页存储前缀索引值,从而提高索引查询速度。
- 前缀索引存在局限性,order by 就无法使用前缀索引,因为前缀字符无法排序。
- 使用覆盖索引:争取 SQL 中查询的所有字段,都能从二级索引中查询得到记录,避免回表的操作。
- 主键索引最好是自增的:如果我们使用主键自增,就不需要频繁改变B + 树的结构,直接按顺序插入。
- 如果我们使用非自增主键,每次主键值都是随机的,插入新的数据时,有可能产生数据页分裂,导致索引结构不紧凑,从而影响查询效率。
- 索引列最好设置为NOT NULL(非空)约束。
- 因为NULL 值是一个没意义的值,行格式会至少使用1字节空间存储NULL 值列表,会占用物理空间;并且如果查询的结果包括具有 NULL 值的行,这可能会导致结果的不准确性或者需要额外的处理。
🙎♂️面试官:Hash 索引和 B+树索引优劣分析
🙋♂答:
优势等值查询:在查询速度上,如果是等值查询,那么Hash索引明显有绝对优势,因为只需要经过一次 Hash 算法即可找到相应的键值,复杂度为O(1)。
劣势:
- 无法范围查询: Hash 索引是无序的,如果是范围查询检索,这时候 Hash 索引就无法起到作用,即使原先是有序的键值,经过 Hash 算法后,也会变成不连续的了。
而 B + 树将数据按照顺序存储在叶子节点中,并且叶子节点之间使用双向链表进行连接,向左或者向右查询的效率都很高。 - 无法使用联合索引: 并且Hash索引不支持联合索引查询。
- 存在哈希碰撞问题: 在有大量重复键值情况下,哈希索引的效率极低。
综上:大多数场景下,都会有组合查询,范围查询、排序、分组、模糊查询等查询特征,Hash 索引无法满足要求,建议数据库使用B+树索引。在离散型高,数据基数大,且等值查询时候,Hash索引有优势。
🙎♂️面试官:B+树做索引比红黑树/B树好在哪里? / 为什么用B + 树做索引?
🙋♂答:
- 红黑树
红黑树(Red Black Tree)是一种自平衡的二叉查找树,它与平衡二叉树相同的地方在于都是为了维护查找树的平衡而构建的数据结构,它的主要特征是在二叉查找树的每个节点上添加了一个属性表示颜色,颜色有两种,红与黑。
当数据量特别大的时候,会导致红黑树的高度变高,磁盘IO操作次数就会变多,影响整体数据查询效率。 - B树
B树每个节点都有数据。但是在数据量相同的情况下,B + 树的非叶子节点可以存储更多的索引,所以B + 树可以比B树更矮,磁盘IO次数会减少。
B树没有冗余的节点。 在进行插入,删除根节点的时候,B树会发生复杂的变化,会影响效率,而B + 树有冗余的节点。
🙎♂️面试官:最左前缀匹配原则了解么?为什么是最左前缀匹配呢?
🙋♂答:
联合索引按照最左匹配原则,如果创建了一个 (a, b, c) 联合索引,查询条件存在a就可以匹配上联合索引,比如where a=1;
联合索引的最左匹配原则会一直向右匹配直到遇到范围查询(>、<)就会停止往下使用联合索引。也就是范围查询的字段可以用到联合索引,但是在范围查询字段之后的字段无法用到联合索引。注意,对于 >=、<=、BETWEEN(类似于>=、<=)、like 前缀匹配,这类范围查询,并不会停止使用索引,两个字段都会用到联合索引查询,但是只是 = 的部分用到了。
比如联合索引
(a,b)
,a全局有序(1,2,2,3,4,5),b是全局无序的(12,7,8,2,3,1)。因此,直接执行where b = 2这种查询没办法利用联合索引。利用索引的前提是,索引里的key是有序的。只有在 a 相同的情况才,b 才是有序的,比如 a 等于 2 的时候,b 的值为(7,8),这时就是有序的,这个有序状态是局部的,因此,执行
where a = 2 and b = 7
是 a 和 b 字段能用到联合索引的,也就是联合索引生效了。
🙎♂️面试官:索引失效的情况了解吗?
🙋♂答:
- 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。因为索引是有序的,前缀模糊匹配无法走索引。
- 对索引列进行表达式计算、使用函数,这些情况下都会造成索引失效。
- 索引列发生隐式类型转换。 MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字(CAST 函数),然后再进行比较。如果字符串是索引列,索引列就发生了改变,所以索引失效。
- 联合索引没有遵循最左匹配原则,就会导致索引失效。
- 在 WHERE 子句中,OR 语句的两个条件有一个不是索引,就会导致索引失效。
🙎♂️面试官:count(*)和count(1)有什么区别?哪个性能好?
🙋♂答:
性能:count(*) = count(1) > count(主键字段) > count(字段)。
- count(1):
- 如果表中只有主键索引,没有二级索引,InnoDB遍历时会遍历聚簇索引,将读取到的记录返回给server层,但是不会读取记录中的任何字段的值。因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。如果表中有二级索引,InnoDB就会遍历二级索引。
- count(*)等同于count(1)
- count(主键字段) :
- 如果表中只有主键索引,没有二级索引,InnoDB在遍历时就会遍历聚簇索引,将读取到的记录返回给server层(server层维护了一个count的变量),然后读取记录中的主键值,如果不为NULL,就将count变量 + 1。如果表中有二级索引,InnoDB就会遍历二级索引,通过二级索引获取主键值,进一步统计个数。
- count(字段):会采用全表扫描的方式来计数,所以它的执行效率是比较差的。
进阶篇
🙎♂️面试官: 一条 SQL 语句在 MySQL 中如何执行的?
🙋♂答:
-
连接器:与客户端进行TCP三次握手,建立连接。并且进行用户密码校验、权限校验。
连接器的工作完成后,客户端就可以向 MySQL 服务发送 SQL 语句了,MySQL 服务收到 SQL 语句后,就会解析出 SQL 语句的第一个字段,看看是什么类型的语句。如果是SELECT语句,会先查询缓存。(一般的查询缓存的命中率很低,在MySQL8.0版本中删掉了) -
解析器:进行词法分析、语法分析。构建语法树,并且判断SQL语句是否符合SQL标准。
之后就进入执行SQL的流程了。主要有三个阶段。 -
预处理器:检查 SQL 查询语句中的表或者字段是否存在;将
select *
中的*
符号,扩展为表上的所有列;(SELECT * 意为查询所有列) -
优化器:负责将 SQL 查询语句的执行方案确定下来,选择查询成本最小的执行计划,比如在表里面有多个索引的时候,优化器会基于查询成本的考虑,来决定选择使用哪个索引。
-
执行器:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;在执行的过程中,执行器就会和存储引擎交互了,交互是以记录为单位的。
🙎♂️面试官:explain命令了解吗?
🙋♂答:
EXPLAIN 命令可以对 SELECT 语句进行分析, 并输出 SELECT 执行的详细信息, 以供开发人员针对性优化。我们一般关注如下几个字段:
possible_keys
:当前sql可能会使用到的索引。key
:表示实际用到的索引。key_len
:用于执行查询的索引的最大长度。- INT类型:4个字节。
- 如果允许存在NULL,NULL值列表占用1字节。
- varchar(50):在单字节字符集下50个字节,utf-8这种多字节字符集,最多存储50个字符。
type
:sql的访问类型。- const:根据主键索引查询。
- ref:使用普通的索引作为查询条件,查询结果可能找到多个符合条件的行。
- range:进行范围查询。
- index:按照索引进行全表扫描,而不是直接扫描行数据。
- ALL:走全表扫描,这种情况就需要使用索引来优化了。
extra
:表示一些附加信息。- using index :用到了覆盖索引,并且不需要回表。
- using where:表示使用了where子句进行了条件过滤。
- using index condition:表示索引下推优化 (index condition pushdown,ICP), 是针对联合索引的一种优化。可以在联合索引遍历过程中,对联合索引中包含的字段先做判断,直接过滤掉不满足条件的记录,从而减少回表次数。
🙎♂️面试官:大表优化的思路、SQL调优的思路?
🙋♂答:
- 表的设计优化:
- 设置合适的数值进行存储,比如tinyint、int、bigint。
- varchar 的长度只分配真正需要的空间。
- 正确创建索引:
- 比如有唯一性限制的字段适合创建索引,但如果字段中出现大量重复数据,就不适合创建索引。
- 经常用于 WHERE 查询条件的字段,这样能够提高整个表的查询速度。如果查询条件不是一个字段,可以使用CREATE INDEX等语句建立联合索引。
- 经常用于 GROUP BY 和 ORDER BY 的字段,这样在查询的时候就不需要再去做一次排序了,因为我们都已经知道了建立索引之后在 B+Tree 中的记录都是排序好的。
- 对SQL语句进行优化:
- 避免使用
select *
,因为select * 在预处理器中拓展为表上的所有列,这样就不会走覆盖索引优化,并且不需要的列在网络中的传输也需要占用时间。 - 正确使用索引,避免索引失效的情况,避免全表扫描。
- 列表数据不要拿全表,要使用
LIMIT
来分页,每页数量也不要太大。 - 使用union all 来代替 union,因为union会有一个过滤重复数据的操作。
- 多表查询尽量使用内连接,如果非要left join 或者 right join,尽量以小表为驱动。
- 避免使用
- 采用主从架构,读写分离。主库负责写操作,并将数据同步给从库,读操作都在从库中完成。(主从复制与读写分离主要解决了访问的压力,但是无法解决海量数据的存储问题)
🙎♂️面试官:项目中用过分库分表吗?
🙋♂答:
当业务量到达一定规模,比如单表1000万数据或者超过20G大小时,会考虑进行分库分表。
首先可以考虑水平分库,将一个库的数据拆分到多个库中,解决海量数据存储和高并发的问题。但是要使用中间件来解决问题。
也可以考虑垂直分库,按照一定的业务逻辑进行拆分,比如用户相关的表(user)存放在一个数据库中,与帖子有关的表(discuss_post、comment)放在另一个数据库中。
垂直切分:
- 分库:一个数据库的表太多。此时就会按照一定业务逻辑进行垂直切,比如用户相关的表放在一个数据库里,帖子相关的表放在一个数据库里。注意此时不同的数据库应该存放在不同的服务器上,此时磁盘空间、内存、TPS等等都会得到解决。
- 分表:表中的字段多,一般将不常用的、 数据较大、长度较长的拆分到“扩展表“。一般情况加表的字段可能有几百列,此时是按照字段进行数竖直切。注意垂直分是列多的情况。
水平切分:
- 分库:水平分库理论上切分起来是比较麻烦的,它是指将一个数据库的数据分摊到多个数据库中,每个服务器具有相应的库与表,只是表中数据集合不同。 库多了,I/O性能自然就会提升。
- 分表:单表的数据量太大。按照某种规则(RANGE,HASH取模等),将单张表的数据切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。(表的数据量少了,单次执行SQL语句的执行效率就会变高。)
完。