跳过正文
  1. 全部/
  2. 笔记/
  3. 面试题/

Mysql

目录

SQL语法
#

1. count主键和count非主键结果会不同吗?
#

分析​ count()函数是返回表中某个列的非NULL值的数量。​

  • 由于主键的列不能存NULL值,所以 count(主键) 返回的结果,可以表示数据库表中所有行数据的数量。​
  • 由于非主键的列可以存NULL值,那么count(非主键)返回表中非主键列的非NULL值的数量。​

回答​ 主键是不能存NULL值的,所以 count 主键代表统计表中所有行数据的数量。​ 而非主键是可以存NULL值的,所以 count 非主键统计的是表中这个列的非NULL值的数量。

2. MySQL内连接、外连接有什么区别?
#

分析​ 内连接(INNER JOIN):内连接返回两个表中匹配的行,即只返回两个表中共有的数据。 外连接(OUTER JOIN):外连接则返回两个表中匹配和不匹配的行。MySQL 外连接主要有左外连接(LEFT JOIN)、右外连接(RIGHT JOIN)两种。​

  • 左连接(LEFT JOIN):SELECT * FROM A LEFT JOIN B ON A.A_id = B.B_id,将返回左表 A 中的所有行和右表 B 中与之匹配的行。如果 B 表中没有匹配的行,则 B 表相关的列使用 NULL 值填充。
  • 右连接(RIGHT JOIN):SELECT * FROM A RIGHT JOIN B ON A.A_id = B.B_id,将返回右表 B 中的所有行和左表 A 中与之匹配的行。如果 A 表中没有匹配的行,则 A 表相关的列使用 NULL 值填充。

回答 ​ 内连接和外连接都是用于连表查询。​

  • 内连接是只返回两个表匹配的数据行,外连接可以返回两个表匹配和不匹配的数据行,外连接主要分为左连接和右连接。​
  • 左连接返回左表中的所有行和右表中匹配的行,如果右表中没有匹配的行,则用 NULL 值填充。​
  • 右连接返回右表中的所有行和左表中匹配的行,如果左表中没有匹配的行,则用 NULL 值填充。

3. WHERE 与 HAVING
#

在外连接中,使用 on 和 where 过滤条件的区别在于:​

  • on 用于指定连接两个表的条件,通常用于指定两个表之间的关联条件,即连接条件,在连接时进行过滤。​
  • where 用于指定过滤条件,对连接后的结果集进行进一步筛选。

4. having与where的区别?​
#

WHERE与HAVING的根本区别在于:​

  • WHERE子句在GROUP BY分组和聚合函数之前对数据行进行过滤,where 子句无法使用聚合函数。​
  • HAVING子句对GROUP BY分组和聚合函数之后的数据行进行过滤,having 子句可以使用聚合函数。

5. EXISTS 和 IN的区别是什么?
#

内部工作原理区别:​ ​IN: 我先把名单拿出来,再判断你在不在名单里 EXISTS: 我逐个检查是否存在对应记录

性能区别:​

  • 如果查询的两个表大小相当,那么用 in 和 exists 性能差别不大。​
  • 如果查询的两个表中一个是小表,一个是大表,IN适合于外表大而内表小的情况,EXISTS适合于外表小而内表大的情况。EXISTS 找到一条就停止不需要把整个结果集都加载出来。

6. MySql 约束有哪些
#

主要有六大约束:​

主键约束(PRIMARY KEY):主键起的作用是唯一标识一条记录,不能重复,不能为空,即 UNIQUE+NOT NULL。一个数据表的主键只能有一个。主键可以是一个字段,也可以由多个字段复合组成,一般我们会把数据库表中ID字段设置为主键,每个表中只能有一个PRIMARY KEY约束,但是可以有多个UNIQUE约束。​

外键约束(FOREIGN KEY ):外键确保了表与表之间引用的完整性。一个表中的外键对应另一张表的主键。外键可以是重复的,也可以为空。​

唯一性约束(UNIQUE):唯一性约束表明了字段在表中的数值是唯一的,即使我们已经有了主键,还可以对其他字段进行唯一性约束。比如我们在 player 表中给 player_name 设置唯一性约束,就表明任何两个球员的姓名不能相同。需要注意的是,唯一性约束和普通索引(NORMAL INDEX)之间是有区别的。唯一性约束相当于创建了一个约束和普通索引,目的是保证字段的正确性,而普通索引只是提升数据检索的速度,并不对字段的唯一性进行约束。​

非空约束(NOT NULL ):对字段定义了 NOT NULL,即表明该字段不应为空,必须有取值。​

默认约束(DEFAULT):表明了字段的默认值。如果在插入数据的时候,这个字段没有取值,就设置为默认值。比如我们将身高 height 字段的取值默认设置为 0.00,即DEFAULT 0.00。​

检查约束(CHECK):用来检查特定字段取值范围的有效性,CHECK 约束的结果不能为 FALSE,比如我们可以对身高 height 的数值进行 CHECK 约束,必须≥0,且<3,即CHECK(height>=0 AND height<3)。​

注意:MySQL只支持前 5 钟约束,不支持检查约束

7. delete、drop、truncate有什么区别?
#

delete 是删除表中的数据,我们可以选择删除部分数据或者全部数据,delete 删除的数据是可以回滚的,delete 操作并不是真的把数据删除掉了,而是给数据打上删除标记,目的是为了空间复用,所以 delete 删除表数据,磁盘文件的大小是不会缩减的。​

drop 是删除表结构和表中所有的数据,truncate 是只删除表中所有的记录,表结构并不会被删除,drop 和 truncate 删除的数据都是不可以回滚的,并且删除表会立刻释放磁盘空间 。​

从删除表的性能来看,drop>truncate>delete。

8. 联合查询中 union 和 union all的区别是什么?
#

  • UNION:在合并结果集后会自动剔除重复的行。​

  • UNION ALL:则会保留所有的重复行,不会进行去重操作。

9. 数据库三大范式是什么?
#

追问 1:范式设计是为了解决什么问题? 追问 2:范式设计有什么缺点?

回答 1NF:字段原子性,不可再分。

2NF:非主键字段必须完全依赖主键,消除部分依赖。

3NF:非主键字段不能依赖其他非主键字段,消除传递依赖。

追问1回答 数据冗余、数据不一致性、数据更新异常和插入异常

  • 数据冗余是指数据库中存储了大量重复的数据
  • 更新异常是指当我们尝试更新一份数据时,可能需要在多个地方进行修改
  • 插入异常是指当我们尝试插入一份新的数据时,可能因为数据的组织和关联方式的问题,而无法进行插入。
  • 使得数据的一致性和完整性得到保证。

追问 2 回答 范式化将数据分解为多个表,那么查询数据的时候,就需要进行更多的表连接操作,在应用中,进行表关联的成本是很高,也不适合分库分表的场景,所以有时候实际应用,设计表的时候会反范式的,比如说可以通过字段冗余的设计,避免联表查询。

10. count(*)性能比count(1)好吗?
#

count(*)=count(1)>count(主键)>count(字段) count(*) 其实等于 count(0),也就是说,当你使用 count(*) 时,MySQL 会将 * 参数转化为参数 0 来处理。

存储引擎
#

11. 说一说执行一条查询 SQL 语句的全过程
#

  1. 连接器建立连接并认证
  2. 解析器进行词法和语法分析
  3. 预处理器检查表和字段
  4. 优化器生成执行计划
  5. 执行器调用存储引擎
  6. 存储引擎读取数据

12. MySQL 存储引擎有哪些?
#

MySQL 常见的存储引擎有 InnoDB、MyISAM、Memory。​

  • 我比较熟悉的是 InnoDB 引擎,它是 MySQL 默认的存储引擎,支持事务和行级锁,具有事务提交、回滚和崩溃恢复功能。​

  • MyISAM 引擎我没有用过,但是我在学习的时候有了解过,它是不支持事务和行级锁的,而且由于只支持表锁,锁的粒度比较大,更新性能比较差,我认为它比较适合读多写少的场景。​

  • Memory 引擎我了解不多,大概知道它是将数据存储在内存中,所以数据的读写还是比较快的,但是数据不具备持久性,我觉得适用于临时存储数据的场景。

13. MyISAM 和 InnoDB 存储引擎有什么区别?
#

InnoDB 引擎数据存储的方式采用的是索引组织表,在索引组织表中,数据即索引,索引即数据,因此表数据和索引数据都存储在同一个文件中。 MyISAM 引擎数据存储的方式采用的是堆表,在堆表的组织结构中,数据和索引分开存储,因此表数据和索引数据会分别放在两个不同的文件中存储。

  • 在索引组织表将索引和数据保存在同一个B+树中,相比非聚簇索引每次查询都需要回表,因此从聚簇索引中获取数据比非聚簇索引更快,查询数据会更快​

  • 在索引组织表中,如果记录发生了修改,则其他索引无须进行维护,除非记录的主键发生了修改,而当堆表的数据发生改变且位置发生了变更,那么所有索引中的地址都要更新,这非常影响性能。

InnoDB 引擎支持行级锁和事务,而 MyISAM 引擎都不支持,只支持表锁。

14. MySQL为什么选择InnoDB作为默认引擎?
#

InnoDB引擎在事务支持、并发性能、崩溃恢复等方面具有优势,因此被MySQL选择为默认的存储引擎。​

  • 事务支持:InnoDB引擎提供了对事务的支持,可以进行ACID(原子性、一致性、隔离性、持久性)属性的操作。Myisam存储引擎是不支持事务的。​

  • 并发性能:InnoDB引擎采用了行级锁定的机制,可以提供更好的并发性能,Myisam存储引擎只支持表锁,锁的粒度比较大。​

  • 崩溃恢复:InnoDB引引擎通过 redolog 日志实现了崩溃恢复,可以在数据库发生异常情况(如断电)时,通过日志文件进行恢复,保证数据的持久性和一致性。Myisam是不支持崩溃恢复的。

15. 用 count(、*) 哪个存储引擎会更快?
#

没有 where 查询条件的话,用 MyISAM 引擎会比较快,因为 MyISAM 引擎的每张表会用一个变量存储表的总记录个数,执行 count 函数的时候,直接读这个变量就行了

16. NULL 值是如何存储的?​
#

Pasted image 20260520215127.png

MySQL 行格式中会用「NULL值列表」来标记值为 NULL 的列,每个列对应一个二进制位,如果列的值为 NULL,就会标记二进制位为 1,否则为 0,所以NULL 值并不会存储在行格式中的真实数据部分。

17. char 和 varchar 有什么区别?哪个性能更好?
#

CHAR 是定长字符串,VARCHAR 是变长字符串。

区别:

  • CHAR 固定长度,存储时会补空格
  • VARCHAR 按实际长度存储,更节省空间

性能上:

  • CHAR 读取略快
  • VARCHAR 更节省磁盘和缓存

18. 假如说一个字段是varchar(10),但它其实只有6个字节,那他在内存中占的存储空间是多少?在文件中占的存储空间是多少?
#

6 字节,并且还会额外用 1-2 字节存储「可变长字符串长度」的空间。​

19. 如果硬件内存特别大,MySQL 缓存能否替代 redis?
#

MySQL 的 Buffer Pool 和 Redis 本质不是同一类东西。即使机器内存很大,Buffer Pool 也只是把磁盘页(page)缓存到内存中,本质仍然是 InnoDB 存储引擎的一部分,服务对象是表和索引数据,而不是业务层的 key-value 或对象缓存。

Redis 是专门的内存 KV 存储系统,访问路径更短,没有 SQL 解析、优化器、事务和 MVCC 这些数据库开销,因此在纯读写延迟上通常比 MySQL 快一个数量级以上。

即使 MySQL 全部数据都命中 Buffer Pool,它仍然需要走 SQL 执行链路,包括解析、执行计划、索引遍历、行级可见性判断等,所以延迟和吞吐模型和 Redis 不一样。

另外 Redis 提供的是 MySQL 不具备的能力,比如分布式锁、计数器、限流、排行榜、位图、发布订阅、原子 Lua 操作等,这些不是“内存变大”可以替代的。

索引结构
#

20. MySQL 有哪些索引类型?
#

MySQL 支持 B+ 树索引、哈希索引、全文索引这三种索引类型。 我比较常用的是 B+ 树索引,因为它是 InnodB 引擎默认使用的索引类型,支持排序、分组、范围查询、模糊查询等功能。

21. InnodB 引擎的索引数据结构是什么?
#

我了解到 InnodB 引擎是采用了 B+ 树作为索引的数据结构。它的一些特性:​

  1. 数据组织形式:InnodB 存储引擎的主键索引 B+树的非叶子节点只存放索引键值和指向子节点的指针,不存储实际的数据,这里对应到MySQL中就是索引,叶子节点存储索引键值和行数据,所以 InnodB 存储引擎的主键索引属于聚簇索引​

  2. 叶子节点链表:所有叶子节点通过指针相连,形成一个双向链表,支持快速的顺序访问和范围查询。​

  3. 平衡树结构:所有叶子节点在同一层上,树的高度平衡,保证任何数据记录的查找、插入、删除和更新操作的路径长度相同,稳定性好。

22. B+树的特性是什么?
#

  • 叶子节点会存储索引+数据,中间节点不会存储数据​
  • 叶子节点之间用双向链表组织
  • B+查询性能稳定

23. B+ 和 B 树有什么区别?
#

  • B树所有节点都会存储索引+数据,而 B+ 树只有叶子节点才会存储数据,查询叶子节点的磁盘 I/O次数会更少
  • B+树叶子节点之间会通过双向指针串联在一起,构成一个双向链表,这种设计对范围查找非常有帮助

24. MySQL 为什么使用 B+ 树?
#

  • B+树是多叉树,而平衡二叉树、红黑树是二叉树,在同等数据量下,平衡二叉树、红黑树高度更高,磁盘IO次数更多,性能更差,而且它们会频繁执行再平衡过程,来保证树形结构平衡。​
  • 跳表和B+树相比,跳表在极端情况下会退化为链表,平衡性差,而数据库查询需要一个可预期的查询时间,并且跳表需要更多的内存。​
  • B 树和B+树相比,B 树的数据存储在全部节点中,对范围查询不友好。非叶子节点存储了数据,导致内存中难以放下全部非叶子节点。如果内存放不下非叶子节点,那么就意味着查询非叶子节点的时候都需要磁盘 IO。

25. 为什么索引用 B+ 树?而不用红黑树?
#

  • 红黑树本质上是二叉树,而 B+ 树是多叉树,红黑树的树高会比 B+ 树的树高,如果树的高度越高,意味着磁盘 I/O 就越多,这样就会影响查询性能。
  • B+树叶子节点是通过双向链表组织的,可以很好的实现范围查询

26. 为什么索引用 B+ 树?而不用 B 树?
#

  • B+ 树只有叶子节点才会存放索引和数据,B+ 树可以比 B 树更矮胖,查询叶子节点的磁盘 I/O次数会更少
  • B+ 树所有叶子节点间会用链表进行连接,这种设计对范围查找非常有帮助

27. 为什么索引用 B+ 树?而不用哈希表?
#

  • 哈希表索引不支持范围查询和排序操作,
  • 不支持联合索引最左匹配原则
  • 如果重复键值比较多,还容易造成哈希碰撞导致效率进一步降低。

28. B+ 树有什么优点和缺点?
#

  • B+树有一个最大的好处是方便范围查询
  • B+树最大的性能问题是会产生大量的随机IO

29. 聚簇索引和非聚簇索引有什么区别?
#

聚簇索引和非聚簇索(二级索引)引最主要的区别是 B+树叶子节点存放的内容不同:​

  • 聚簇索引的 B+树叶子节点存放的是主键值+完整的记录;​
  • 非聚簇索引的 B+树叶子节点存放的是索引值+主键值;

30. 什么是覆盖索引?
#

当查询的数据是能在二级索引的叶子节点里查询到的话,这时就不用再回主键索引查了,那就不需要回到主键索引去查行记录了,这种不需要回表的过程,就叫覆盖索引,这种查询方式效率会比较高,只需要查二级索引这一棵 B+ 树。

31. 什么情况下会回表?
#

在使用二级索引进行查询的时候,如果查询的列,不能在二级索引中全部查询到,那么就需要回到主键索引去查完成的行记录了,这种二级索引通过主键索引进行再一次查询的操作叫作「回表」

在使用二级索引进行查询的时候,如果查询的列,不能在二级索引中全部查询到,那么就会发生回表的过程,先通过二级索引的值查到聚簇索引值(即主键 id),再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低。

32. insert 操作对 B+ 树结构的改变是怎么样的?
#

页分裂问题 如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。

Pasted image 20260525140358.png

如果我们使用主键是顺序递增,那么每次插入的新数据就会顺序插入到叶子节点最右边的节点里,如果该页面满了,就会自动开辟一个新页面,将新数据插入到新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。​

如果我们使用主键不是顺序递增,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这时候为了保证B+ 树的有序性,要移动其它数据来满足新数据的插入。如果该页面满了,就发生页分裂,这时候要从一个页面复制数据到另外一个页面,目的是保证后一个数据页中的所有行主键值比前一个数据页中主键值大,页分裂可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。

33. 假如一张表有两千万的数据,B+树的高度是多少?怎么算的?
#

页大小(InnoDB 默认 16KB)

粗略算: 一个索引项 ≈ 16 字节 16384 / 16 ≈ 1024 一个非叶子节点大约可以指向: 1000 个子节点

我们按 100 行/页 来估算。 1000 × 100 = 10万 1000 × 1000 × 100 = 1亿

因此是三行

索引应用
#

34. MySQL 有哪些索引?
#

我了解到 MySQL 有主键索引、唯一索引、普通索引、前缀索引、联合索引这几种索引。

Innodb 引擎会要求每一张数据库表都必须要有一个主键索引,比如表里的 id 字段就是主键索引。​ 然后针对查询比较频繁的字段,我们可以对这个字段建立普通索引 如果是多个字段的话,可以考虑建立联合索引,利用索引覆盖的特性提高查询效率。​ 对于长文本、字符串等类型的字段,比如文章标题、商品名称等,我们可以只对这些字段的前缀部分建立索引,也就是建立前缀索引,这样可以减少索引的存储空间。

35. MySQL主键是聚簇索引吗?
#

聚簇索引就是按照每张表的主键构造一棵 B+ 树,同时叶子节点中存放的是整张表的行记录数据,就好像把数据和索引聚集在了一棵 B+ 树上,所以这种数据组织形式的索引叫聚簇索引

36. 主键为什么不推荐有业务含义?
#

  • 第一个业务会有变动的可能性,我们谁也无法预测 在项目的整个生命周期中,哪个业务字段会因为项目的业务需求而有重复,或者重用之类的情况出现,等需要变动的时候,去更改主键是成本很高的一件事情,不如设计阶段就规避不用有业务含义的主键。​
  • 第二个是业务含义的主键可能不是顺序自增的,有可能会发生页分裂问题,从而影响性能

37. 主键是用自增还是UUID?
#

  • 用自增 id 比较好,因为UUID是随机值,在数据插入的过程中,会导致索引树发生页分裂的问题,会影响性能,
  • 而且UUID是字符串类型,长度比较长,占用内存比较大,而页的大小是固定的,这样会导致索引树的高度越高,查询的时候会发生的磁盘 IO 次数也越多,性能也就更低。

38. 普通索引和唯一索引有什么区别?哪个更新性能更好?
#

查询:

  • 对于普通索引来说,查找到满足条件的第一个记录 (5,500) 后,需要查找下一个记录,直到碰到第一个不满足 k=5 条件的记录。
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。 因为引擎是按页读写的,所以说,当找到 k=5 的记录的时候,它所在的数据页就都在内存里了。那么,对于普通索引来说,要多做的那一次“查找和判断下一条记录”的操作,就只需要一次指针寻找和一次计算。

更新: 第一种情况是,这个记录要更新的目标页在内存中。这时,InnoDB 的处理流程如下:

  • 对于唯一索引来说,找到 3 和 5 之间的位置,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,找到 3 和 5 之间的位置,插入这个值,语句执行结束。

这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的 CPU 时间。

第二种情况是,这个记录要更新的目标页不在内存中。这时,InnoDB 的处理流程如下:

  • 对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,则是将更新记录在 change buffer,语句执行就结束了。

将数据从磁盘读入内存涉及随机 IO 的访问,是数据库里面成本最高的操作之一。change buffer 因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。

39. 主键怎么设置?追问:假如你不设置会怎么样?
#

  • 在创建表的时候,对id 列设置为 PRIMARY KEY,那么 id 列就是主键索引了。
  • 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键,如果这个条件也没有达成的话,InnoDB 将自动生成一个隐式 rowid 列作为聚簇索引的索引键。

40. 介绍一下什么是外键约束?
#

外键就是「从表」中用来引用「主表」中数据的那个公共字段,外键约束确保了数据的引用完整性,也就是「从表」中的外键必须存在于「主表」的主键中 如果发现要删除的主表记录,正在被「从表」中某条记录的外键字段所引用,MySQL 就会提示错误,从而保证了 2 个表中数据的一致性。

41.外键有什么优劣势?
#

外键能够保证数据的一致性和完整性,通过设置外键,数据库就会判断数据的完整性

  • 有了外键之后,每次增删改都需要额外检查外键约束,会占用数据库的计算资源,影响增删改的性能
  • 而且还需要额外获取锁,在高并发场景下很容易发生死锁的问题

42. 为什么要建索引?
#

如果没有建立索引,我们查询数据的话,搜索时间复杂度是 O(n),这样的查询效率还是比较低的,为了提高查询效率,我们可以建立索引。​

建立了索引后数据都会按照顺序存储,这时候我们可以利用类似二分查找的方式快速查找数据,B+ 树索引是多叉树,搜索时间复杂度是 O(logdN),这样就提高了查询速度

43. 索引的使用场景
#

  • 可以对频繁用于 WHERE 查询条件的字段建立索引,这样能够提高整张表的查询速度,如果查询条件不是一个字段,可以考虑建立联合索引。

  • 还有对于经常用于排序、分组的字段建立索引,这样在查询的时候就不需要再去做一次排序了

  • 对于一些区分度不高的字段,比如性别字段,只有男女,不建议建立索引

44. 索引越多越好吗?
#

不是的,索引虽然能提高查询效率,但是多建立一个索引,就意味着新生成一个 B+树索引,是需要占用存储空间的,特别是在表数据量非常大的时候,索引占用的空间越大。​

还有,索引越多数据库的写入性能会下降,因为每次对表进行增删改操作的时候,都需要去维护各个 B+ 树索引的有序性。

45. 什么时候不用索引更好?
#

第一个是空间代价,因为需要多构建一颗 b+树,会占用磁盘空间。 第二个更新时间代价,每次增删改索引,都需要动态维护 b+树,以满足 b+树的有序性。

我认识到如果一张表经常被增删改的话,也就是写多读少的场景下, 不建立索引会更好,因为这时候维护索引的开销可能会超过索引带来的性能提升。​

还有一点,如果表中某个列的值高度重复,那么建了索引也没有用,优化器会选择全表扫描,这样建立的索引会占用存储空间,也会影响增删改的效率,选择不用索引会更好。

46. 字段为什么要定义为NOT NULL?
#

  1. 如果查询中包含可为NULL的列,对MySQL的优化器来说更难优化,因为可为NULL的列使得索引、索引统计和值比较都更复杂。​

  2. 如果某列存在NULL的情况,可能导致 count() 等函数执行不准确,因为 count 不会统计值为 NULL 列。​

  3. NULL 值是一个没意义的值,但是它会占用物理空间,因为 InnoDB 存储记录的时候,如果表中存在允许为 NULL 的字段,那么行格式中至少会用 1 字节空间存储 NULL 值列表

47. 索引怎么优化?
#

  • 对于只需要查询几个字段数据的 SQL 来说,我们可以对这些字段建立联合索引,这样查询方式就变成了覆盖索引,避免了回表,减少了大量的 I/O 操作。​

  • 我们的主键索引最好是递增的值,因为我们索引是按顺序存储数据的,如果主键的值是随机的值,可能会引发页分裂的现象, 页分裂会导致大量的内存碎片,这样索引结构不紧凑了,就会影响查询效率。​

  • 我们要避免写出发生索引失效的 SQL 的语句,比如不要对索引进行计算、函数、类型转换操作,联合索引要能正确使用需要遵循最左匹配原则等等。​

  • 对于一些大字符串的索引,我们可以考虑用前缀索引只对索引列的前缀部分建立索引,节省索引的存储空间,提高查询性能。

48. 建立了索引,查询的时候一定会用到索引吗?
#

  • 查询语句对索引字段进行左模糊匹配、表达式计算、函数、隐式类型转换操作
  • 优化器会计算回表的成本和全表扫描的成本,如果回表的代价太高,优化器会选择不走索引,而是走全表扫描。

49. 索引失效
#

select * from t_user where time = 20230922; 因为 mysql 在遇到字符串和数字比较的时候,会发生隐式类型转换,会将字符串的对象转为数字,这个转换的过程实际上会涉及到函数。你说的这个查询,日期字段是字符串,那么发生隐式类型转换的时候,就会作用在日期这个索引字段上,对索引进行函数计算的话,是会发生索引失效的。

50. MySQL 最新版本解决了索引失效的哪些情况了吗?
#

我了解到 MySQL 8.0 可以给字段增加函数索引,这个新特性可以解决对索引使用函数的时候,索引失效的问题。​

还有一个新特性是索引跳跃式扫描,5.7 版本之前,使用联合索引的时候,如果不满足最左匹配原则,就会发生索引失效,而 8.0 出了索引跳跃式扫描特性之后,即使没有遵循最左匹配原则,部分场景下,依然可以使用联合索引。

51. 最左匹配原则
#

假设有一个(a, b, c) 联合索引,它的存储顺序是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。由于这个的特性,在使用联合索引时,存在最左匹配原则。

52. 建立联合索引有什么需要注意的?
#

最好把区分度比较大的字段放在联合索引最左侧,有助于提高索引的过滤效果,比如 UUID 这类字段就比较适合排在联合索引列的靠前的位置。​

如果区分度很低的字段放在了联合索引最左侧,有可能会导致查询优化器会选择全表扫描,而不走索引了。

53. 了解索引下推吗?什么情况下会下推到引擎去处理?
#

“把原本 Server 层做的部分 WHERE 条件,下推到存储引擎层,在扫描索引时提前过滤数据,减少回表次数。”

如果没有索引下推:

扫描到一条数据-> 回表-> 再判断 city

有索引下推后:

扫描索引时-> 先判断 city-> 满足条件再回表

54. 联合索引 (a,b,c),下面的查询语句会不会走索引?如果走具体是哪些字段能走?
#

  1. select * from T where a=1 and b=2 and c=3;​

  2. select * from T where a=1 and b>2 and c=3;​

  3. select * from T where c=1 and a=2 and b=3;​

  4. select * from T where a=2 and c=3;​

  5. select * from T where b=2 and c=3;​

  6. select (a,b) from T where a=1 and b>2

55. where a>1 and b = 2 and c <3怎么建立索引?
#

我会创建(bac)联合索引或者(bca)联合索引,因为这两种联合索引都可以有 2 个字段走索引。比如​

  • 创建 (bac) 联合索引,b 和 a 都能走索引,c 字段虽然无法走索引,但是可以进行索引下推,这样会减少回表的次数;

56. where a=? And b=? order by c 怎么建立索引?
#

可以建立(a,b,c)联合索引,这样 c 排序的时候,就能利用索引的有序性,避免 using filesort 了。

57. where a>100 and b=100 and c=123 order by d 怎么建立联合索引?​
#

如果是 bcad 联合索引的话,虽然 bca 能走索引,但是排序 d 无法利用索引,会发生 file sort(因为 a>100范围查询后获得的记录,d 并不一定是有序的了,所以需要额外排序,d有序的前提是a相等的情况下) 如果 bcda 联合索引,d 不仅能利用索引有序性,避免 file sort,a 虽然都不了索引,但是可以索引下推,所以建立(bcda)联合索引会比较好。

58. select b from table where a = 10 and c>20
#

可以考虑创建(a,c,b)顺序的联合索引,这时候查询的时候,a 和 c 既能都走索引,也能利用索引覆盖的特性,避免了回表。

59. select id, name from XX where age > 10 and name like ‘xx%’,有联合索引(name,age),说一下查询过程
#

联合索引的顺序是先 name,再age,结构上是先根据 name 排序,nam 相等的情况下再根据 age 排序。所以优化器需要先匹配 name,name 这时候是右模糊查询,并不会发生索引失效,所以这条 sql 是能走联合索引的,具体的话,只有 name 能走索引,这是因为由于 name 右模糊查询后,age 字段的值并不是有序的,因此 age 无法走索引,但是 age 可以进行索引下推。​

最后查询的字段是 id 和 name,这两个字段都能在联合索引上查找到,所以不需要回表,是索引覆盖查询。

60. where id NOT IN (?, ?, ?) 会走索引吗?
#

要看查询成本,如果走某个索引花费的随机 I/O 比从聚簇索引顺序查(顺序I/O)的成本都还要高,那还不如直接去全表扫描。​

举例: num 字段(非唯一二级索引)只包含 3 个值,1、2、3,3 只有几行,而 1、2 各有 100w 行,如果查询条件是 NOT IN (1, 2) 会走索引,如果查询条件是 NOT IN (3) 不会走索引。

61. 如果查询条件中包含索引列和非索引列,MySQL的具体查询流程是什么样的?
#

查询过程先按索引去二级索引B+树查,然后拿到主键 id,回表到主键索引B+树再过滤非索引列,查询过程会查 2 个 b+树,涉及回表的过程。​

事务
#

62. MySQL 事务有什么特性?
#

MySQL 事务有 ACID 四大特性,分别是原子性、一致性、隔离性、持久性。​

  • 原子性的意思是事务中的操作要么全部成功,要么全部失败。原子性是由 undo log 日志保证的;​

  • 一致性的意思是事务执行前后,数据库始终保持一致状态。一致性是由通过持久性+原子性+隔离性这三个共同保证的;​

  • 隔离性的意思是多个事务并发执行时,彼此之间互不干扰。隔离性是由 MVCC 和锁保证的;​

  • 持久性的意思是事务一旦提交,数据会永久保存,不会丢失。持久性是由 redo log 日志保证的

63. 事务的隔离性如何保证?
#

事务的隔离性是由 MVCC 和锁保证的。​

  • 可重复读隔离级别下的快照读(普通select),是通过 MVCC 来保证事务隔离性的
  • 当前读(update、select … for update)是通过行级锁来保证事务隔离性的

64. 事务的持久性如何保证?
#

事务的持久性是由 redo log 保证的

因为 MySQL 通过 WAL (先写日志再写数据)机制,在修改数据的时候,会将本次对数据页的修改以 redo log 的形式记录下来,这个时候更新就算完成了

Buffer Pool 的脏页会通过后台线程刷盘,即使在脏页还没刷盘的时候发生了数据库重启,由于修改操作都记录到了 redo log,之前已提交的记录都不会丢失,重启后就通过 redo log,恢复脏页数据,从而保证了事务的持久性。

65. 事务的原子性如何保证?
#

事务的原子性是通过 undo log 实现的

在事务还没提交前,历史数据会记录在 undo log 中,如果事务执行过程中,出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据,将数据恢复到事务开始之前的状态,从而保证了事务的原子性。

66. MySQL事务和Redis 事务有什么区别?
#

MySQL事务能够实现ACID四大特性,而Redis事务没保证原子性和持久性。

Redis 事务没有回滚功能 Redis 不管是 AOF 模式,还是 RDB 快照,都没办法保证数据不丢失,所以 Redis 事务不具有持久性。

67. MySQL 事务隔离级别有哪些?分别解决哪些问题?
#

  • 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;​

  • 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;​

  • 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;​

  • 串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

  • 脏读是指一个事务读取了另一个事务还未提交的数据,如果另一个事务回滚,则读取的数据是无效的。脏读可能导致数据的不一致性。​

  • 不可重复读是指一个事务多次读取同一条记录,但是在此期间另一个事务修改了该记录,导致前后读取的数据不一致。不可重复读可能导致数据的不一致性。​

  • 幻读是指一个事务多次执行同一个查询,但是在此期间另一个事务插入了符合该查询条件的新数据,导致前后查询的结果不一致。幻读可能导致数据的不完整性。

68. 串行化隔离级别是通过什么实现的?
#

串行化隔离级别所有SQL都会加行级锁,包括普通的 select 查询,都会加 S 型的 next-key 锁。其他事务就没办法对这些已经加锁的记录进行增删改操作了,从而避免了脏读、不可重复读和幻读现象,性能是隔离级别中最差的,没有MVCC机制,读写操作没办法并发。

69. 脏读和幻读有什么区别?
#

  • 脏读是一个事务读到了另一个未提交事务修改过的数据。

  • 幻读是前后两次的查询的结果集的数量是不同,比如,如果 select 执行了两次,但第二次返回了第一次没有返回的行数据,则该行是“幻像”行。

70. MySQL默认的隔离级别是什么?怎么实现的?
#

MySQL默认的隔离级别是可重复读。

select 查询是通过 MVCC 实现的,在 MVCC 实现中,每条记录都会保存多个版本,每个版本都有一个版本号,事务在读取数据时,会根据事务开始时的版本号来读取数据,从而保证了事务的隔离性。可重复读隔离级别是在开启事务后,执行一条 select 语句的时候, 会生成一个 Read View,后续事务查询数据的时候都在复用 Read View,所以保证了事务期间多次读到的数据都是一致的。

71. 介绍一下 MVCC
#

MVCC 是多版本并发控制,是通过记录历史版本数据,解决读写并发冲突问题,避免了读数据时加锁,提高了事务的并发性能。

MySQL将历史数据存储在 undo log 中,结构逻辑上类似一个链表,MySQL数据行上有两个隐藏列,一个是事务ID,一个就是指向 undo log 的指针。

事务开启后,执行第一条 select 语句的时候,会创建 ReadView ,ReadView 记录了当前未提交的事务,通过与历史数据的事务 ID 比较,就可以根据可见性规则进行判断,判断这条记录是否可见,如果可见就直接将这个数据返回给客客户端,如果不可见就继续往undo log 版本链查找第一个可见的数据。需要展开说说可见性规则吗?

72. MVCC的如何判断行记录对某一个事务是否可见
#

Read View 有四个字段,分别是创建 Read View 的事务 id、活跃事务 id 列表、活跃事务 id 列表中最小的 id、下一个事务的 id

  • 如果记录的事务 id 小于活跃事务 id 列表中最小的 id,就说明该记录是在创建 Read View 前就生成好了,所以该记录是当前事务是可见的。​

  • 如果记录的事务 id 大于下一个事务的 id,就说明该记录是在创建 Read View 后才生成的,所以该记录是当前事务是不可见的。​

  • 如果记录隐藏列的事务 id 在最小的 id 和下一个事务的 id 之间,这时候就需要看记录的事务 id 是否在活跃事务 id 列表中:​

    • 如果记录的事务 id 在活跃事务 id 列表中,说明修改该记录的事务还没提交,所以该记录是不可见的。​

    • 如果记录的事务 id 不在活跃事务 id 列表中,说明修改该记录的事务已经提交了,那么该记录就是可见。

73. 读已提交和可重复读隔离级别实现 MVCC 的区别?
#

读已提交和可重复读隔离级别都是由 MVCC 实现的,它们的区别在于创建 Read View 的时机不同。​

  • 读已提交隔离级别在事务开启后,每次执行 select 都会生成一个新的 Read View,所以每次 select 都能看到其他事务最近提交的数据。​

  • 可重复读隔离级别在事务开启后,执行第一条 select 时生成一个 Read View,然后整个事务期间都在复用用这个 Read View,所以一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。

74. 为什么互联网公司用读已提交隔离级别?
#

读已提交的并发性能更好,因为读已提交没有间隙锁,只有记录锁,发生死锁的概率比较低。然后互联网业务对于幻读和不可重复读的问题都是能接受的,所以为了降低死锁的概率,提高事务的并发性能,都会选择使用读已提交隔离级别。

75. 可重复读隔离级别是如何解决不可重复读的?
#

MySQL 提供了两种查询方式,一种是快照读,就是普通 select 语句,另外一种是当前读,比如 select for update 语句。不同的查询方式,解决不可重复读问题的方式是不一样的。​

针对快照读的话,是通过 MVCC 机制来解决的,在可重复读隔离级别下, 第一次select查询的时候,会生成 readview,在第二次执行select查询的时候,会复用这个readview,这样前后两次查询的记录都是一样的,不会读到其他事务更新的操作,这样就不会发生不可重复读的问题了。​

针对当前读的话,是靠行级锁中的记录锁来实现的,在可重复读隔离级别下,第一次 select for update 语句查询的时候,会对记录加next-key 锁,这个锁包含记录锁,这时候如果其他事务更新了加了锁的记录,都会被阻塞住,这样就不会发生不可重复读的问题了。

76. 可重复读隔离级别是怎么解决幻读的?
#

MySQL 提供了两种查询方式,一种是快照读,就是普通 select 语句,另外一种是当前读,比如 select for update 语句。不同的查询方式,解决幻读问题的方式是不一样的。​

针对快照读的话,是通过 MVCC 机制来解决的,在可重复读隔离级别下, 第一次select查询的时候,会生成 readview,在第二次执行select查询的时候,会复用这个readview,这样前后两次查询的结果集都是一样的,不会读到其他事务新插入的记录,这样就不会发生幻读的问题了。​

针对当前读的话,是靠行级锁中的间隙锁来实现的,在可重复读隔离级别下,第一次 select for update 语句查询的时候,会对记录加next-key 锁,这个锁包含间隙锁,这时候如果其他事务往这个间隙插入新记录的话,都会被阻塞住,这样就不会发生幻读的问题了。

77. 可重复读隔离级别解决了什么问题?有没有完全解决幻读?
#

可重复读隔离级别解决了脏读、不可重复读问题,幻读也很大程度上避免了,但是我觉得并没有完全解决幻读,在一些特殊的场景,还是会发生幻读的问题,需要我展开说下吗?

78. 可重复读隔离级别为什么不能完全避免幻读?什么情况下出现幻读?
#

比如说这个场景,事务 A 通过快照读的方式查询 id = 5 的记录,此时数据库没有这条记录,然后事务 B 向这张表中新插入了一条 id = 5 的记录并提交了事务。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录隐藏列中的事务id就变成了事务 A 的事务 id,这时候事务 A 再使用 select 语句去查询这条记录时就可以看到这条记录了,这里事务 A 前后两次查询的结果集合数不一样了,于是就发生了幻读。

事务 A 通过快照读的方式查询 id 大于 100 的记录,假设这时候有 1 条记录,然后事务 B 插入了 id = 200 的记录并提交了事务,接着事务 A 通过当前读的方式查询 id 大于 100 的记录,这时候就会得到 2 条记录,事务 A 前后两次查询的结果集合数不一样了,就发生了幻读。

79. 可重复读隔离级别,MVCC完全解决了不可重复读问题吗?
#

如果前后两次查询都是快照读,就是普通的 select 的话,那就不会产生不可重复读的问题的。但是如果第一次查询是快照读,第二次查询是当前读,那么就可能会发生不可重复读的问题。

80. 一个事务里有特别多SQL的弊端?
#

  • 锁是事务提交的时候才释放的,那么长事务会导致锁持久的时间过长,容易导致大量的死锁和锁超时的问题​

  • 执行事务中每条增删改SQL会产生 undo 日志,那么长事务就会导致 undo 日志堆积很多,占用存储空间,也会导致回滚的时间过长。​

  • 长事务执行时间长,容易造成主从延迟,如果一个主库上的语句执行10分钟,那这个事务很可能就会导致从库延迟10分钟​

  • 在长事务中,连接可能会被持续打开,这会占用数据库连接池的资源,可能导致连接池被占满

81. 详细说一下 MySQL数据库中锁的分类
#

MySQL 的锁可以分为全局锁、表级锁、行级锁

全局锁主要应用于做全库逻辑备份

表级锁

  • 表锁:通过lock tables 语句可以对表加表锁,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。​

  • 元数据锁:当我们对数据库表进行操作时,会自动给这个表加上 MDL,对一张表进行 CRUD 操作时,加的是 MDL 读锁;对一张表做结构变更操作的时候,加的是 MDL 写锁;MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。​

  • 意向锁:当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。意向锁的目的是为了快速判断表里是否有记录被加锁。

行级锁:InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。​

  • 记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的,满足读写互斥,写写互斥​

  • 间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。​

  • Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。​

  • 插入意向锁,当插入位置的下一条记录有间隙锁,那么就会生成插入意向锁,然后进入阻塞状态

82. MySQL 怎么实现乐观锁?
#

  • 可以在数据库表增加一个版本号字段,利用这个版本号字段在数据库中实现乐观锁。​

  • 具体的实现,每次更新数据的时候,都要带上版本号,同时将版本+1,比如现在要更新id=1,版本号为2的记录。这时候先要获取id=1的版本号,然后更新语句写成 update table set name = “小明”, version = version+1 where id = 1 and version = 2。​

  • 如果这个版本号与表记录中的版本号一致的话,就能更新成功,如果不相等则不进行更新,然后需要重新获取该记录的最新版本号,然后再尝试更新数据。

83. 在线上修改表结构,会发生什么?
#

线上环境可能存在很多事务都在读写这张表,如果对这张表进行了表结构修改,就会发生阻塞,原因是有事务对这张表进行读写操作的时候,会生成元数据读锁,而修改表结构的时候,会生成元数据写锁,这时候就产生了读写冲突,所以修改表结构的操作就会阻塞,并且后续事务的增删查改操作都会阻塞。

84. 创建索引的时候会锁表吗?
#

会的,创建索引的时候会加MDL写锁,如果这时候有其他事务对这张表进行增删查改的话,这些事务就都会被阻塞,原因是有事务对这张表进行读写操作的时候,会生成MDL读锁,这时候就产生了读写冲突。

85. Innodb 存储引擎中的行级锁有哪些?
#

记录锁、间隙锁、临键锁、插入意向锁

  • 记录锁,可以避免其他事务对该记录进行删除和更新操作​

  • 间隙锁,可以避免其他事务往间隙里插入新记录​

  • 临键锁,是记录锁和间隙锁的组合,所以它既可以免其他事务对该记录进行删除和更新操作,也可以避免其他事务往间隙里插入新记录​

  • 插入意向锁,插入意向锁和间隙锁是互斥的关系,其他事务插入的时候,发现插入位置的下一条记录有间隙锁的话,才会生成的插入意向锁,并且这时候锁的状态是阻塞状态,目的是告诉用户插入的位置存在间隙锁

86. 间隙锁的工作原理是什么?
#

间隙锁防止其他事务往间隙插入新记录,从而可以避免幻读的问题,具体的原理是当其他事务插入记录的时候,当发现插入位置的下一条记录有间隙锁,就会生成插入意向锁,然后锁设置为阻塞状态,目的是告诉用户插入的位置存在间隙锁

87. 一条Update语句没有带where条件,加的是什么锁?
#

  • 可重复读级别下,更新没有带 where 条件,会全表扫描,会对每一条记录都加next-key锁,相当于锁住了全表。​

  • 读已提交隔离级别下, 没有间隙锁,更新没有带 where 条件,是全表扫描,那么会对每一条记录都加记录锁。

88. 带了where条件没有命中索引,加的是什么锁?
#

  • 在可重复读级别下,全表扫描的话,会对每一条记录都加next-key锁。​

  • 在读已提交隔离级别下, 因为没有间隙锁,全表扫描的时候,会对每一条记录都加记录锁。

89. 两条更新语句更新同一条记录,加的是什么锁?
#

唯一索引 如果存在,那么这条记录加的记录锁,只锁住该条记录;​ 如果这条记录不存在,则加间隙锁。 非唯一索引

如果不存在,会对第一个不符合更新条件的二级索引记录加间隙锁。

90. 两条更新语句更新同一条记录的不同字段,加的是什么锁?
#

92. 了解过 MySQL 死锁问题吗?
#

了解过,在并发事务中,当两个事务出现循环资源依赖,这两个事务都在等待别的事务释放资源时,就会导致这两个事务都进入无限等待的状态,这时候就发生了死锁。

93. MySQL 怎么排查死锁问题?
#

在遇到线上死锁问题时,我们应该第一时间获取相关的死锁日志。我们可以通过 show engine innodb status 命令来获取死锁信息。​

然后就分析死锁日志。死锁日志通常分为两部分,上半部分说明了事务1在等待什么锁,下半部分说明了事务2当前持有的锁和等待的锁。​

通过阅读死锁日志,我们可以清楚地知道两个事务形成了怎样的循环等待,然后根据当前各个事务执行的SQL分析出加锁类型以及顺序,逆向推断出如何形成循环等待,这样就能找到死锁产生的原因了。

94. MySQL 怎么避免死锁?
#

缩短锁持久的时间,来降低死锁的概率 可以通过减少间隙锁,来降低死锁的概率: 可以通过减少加锁范围,来降低死锁的概率: 可以通过MySQL参数设置,来降低死锁的概率:

日志
#

95. MySQL三大日志是什么?
#

  • undo log是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC。在事务没提交之前,Innodb会先记录更新前的数据记录 undo log中,回滚时利用 undo log 来进行回滚。​

  • redo log 也是Innodb存储引擎层的日志,属于物理日志,记录了某个数据页做了什么修改,实现了事务的持久性,主要用于掉电等故障恢复。比如某个事务提交了,脏页数据还没有刷盘,如果 MySQL 机器断电了,脏页的数据就丢失了,MySQL 重启后可以通过redolog日志,可以将已提交事务的数据恢复回来。​

  • binlog 是 Server 层生成的日志,主要用于数据备份和主从复制。在完成一条更新操作后,Server 层会生成一条 binlog,等之后事务提交的时候,会将该事务执行过程中产生的所有 binlog 统一写入 binlog 文件。binlog 文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作。

96. redo log 和 binlog 的区别和应用场景?
#

适用对象不同、文件格式不同、写入方式不同、用途不同

  • redo log 是 InnoDB 引擎实现的日志,属于物理日志,记录了 Innodb 存储引擎对数据页所做的修改操作,主要用于崩溃恢复,比如某个事务提交了,脏页数据还没有刷盘,如果 MySQL 机器断电了,脏页的数据就丢失了,MySQL 重启后可以通过重做日志,可以将已提交事务的数据恢复回来。​

  • binlog 是 server 层实现的日志,保存了所有对数据库的增删改操作,binlog 有三种日志格式,日志的内容可能是 SQL 语句、数据本身或两者的混合,主要用于数据库备份和归档,也用于主从复制。

97. redo log 和 binlog 在恢复数据库有什么区别?
#

  • binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存了所有对数据库的更新操作,可以用来恢复数据库某个时刻的数据或者全量恢复数据库数据。​

  • redo log 是循环写,日志空间大小是固定,全部写满就从头开始,保存的是 Innodb 存储引擎对数据页所做的修改操作,用来恢复因中途 MySQL 断电丢失的脏页数据。

98. 为什么崩溃恢复不用binlog 而用redolog?
#

binlog 是 server 层的日志,不会记录 innodb 存储引擎层中有哪些数据页没有被刷盘, redolog 是 innodb 层的日志,可以记录哪些脏页没有被刷盘,崩溃恢复的时候,恢复的粒度更细粒,可以精确到需要恢复的数据页,而 binlog 保存的是全量日志,没办法做到这一点,所以崩溃恢复用的是redolog

99. binlog的三种格式是什么?
#

  • STATEMENT:每一条修改数据的 SQL 都会被记录到 binlog 中,主从复制中 slave 端再根据 SQL 语句重现。缺陷:STATEMENT 有动态函数的问题,比如用了 uuid 或者 now 这些函数,在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致​

  • ROW:记录行数据最终被修改成什么样了,不会出现 STATEMENT 下动态函数的问题。缺陷:但 ROW 的缺点是每行数据的变化结果都会被记录,比如执行批量 update 语句,更新多少行数据就会产生多少条记录,使 binlog 文件过大,而在 STATEMENT 格式下只会记录一个 update 语句。​

  • MIXED:包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式。

100. redo log 是怎么实现持久化的?
#

事务执行过程更新的数据,并不是在事务提交的时候,就把修改的数据刷入磁盘的,而是修改 buffer pool 中数据页,并标记为脏页,然后后台再找合适的时间刷盘。 如果事务提交了,脏页数据没有刷盘时,数据库发生宕机,这就会导致事务修改的数据丢失了。 所以 MySQL 就引入了 redo log, redo log 保存的内容是物理日志,主要是记录Innodb对某个数据页的修改操作,当事务提交的时候,redo log 会先刷入磁盘,因为 redo log 保存了数据页的修改操作,即使脏页数据没有刷盘时数据库发生宕机了,重启后 MySQL 通过重放 redo log ,就能恢复未刷盘的脏页,保证了数据的持久化。

101. redo log除了崩溃恢复还有什么其他作用?
#

写 Redolog 日志是追加的形式,所以 redolog 写磁盘是一个顺序写的过程,而数据页写磁盘是一个随机写的过程,顺序写的性能是比随机写性能高的,事务在提交的时候,是先写日志再写数据的机制,相当于把 MySQL 写入磁盘的操作从磁盘随机写成了顺序写,所以 redo log 还可以起到提升 MySQL 写入磁盘性能的作用。

102. 为什么需要两阶段提交?
#

两阶段提交是为了保证 redo log 和 binlog 逻辑一致,从而保证主从复制的时候不会出现数据不一致的问题。​

事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,比如在主从复制的场景下,如果在将 redo log 刷入到磁盘之后, MySQL 突然宕机了,而 binlog 还没有来得及写入磁盘,这时候主库是最新的数据,而从库是旧数据,这样就造成两份日志之间的逻辑不一致。

103. 两阶段提交的过程?
#

两阶段提交把事务的提交拆分成了 2 个阶段,分别是准备阶段和提交阶段。​

  1. 准备阶段会将 redo log 状态设置为 prepare 状态,然后将 redo log 刷入磁盘;​
  2. 提交阶段会将 binlog 刷入磁盘,然后设置 redo log 设置为 commit 状态,到这里两阶段就已经完成了。​

在两阶段提交中,是以 binlog 刷入磁盘时机作为事务提交成功的标志的:​

  • 如果 binlog 还没刷入磁盘的时候,MySQL 就发生了崩溃,MySQL 重启的时候就需要回滚事务;
  • 如果 binlog 刷入磁盘,即使 redo log 没有设置 commit 状态,MySQL 就发生了崩溃,MySQL 重启的时候就会提交事务

104. Redolog 刷盘策略有哪三种?
#

  1. 当刷盘策略配置为参数 0 的时候,表示每次事务提交时 ,还是将 redo log 留在 redo log buffer 中 ,该模式下在事务提交时不会主动触发写入磁盘的操作,后续由 innodb 后台线程把缓存在 redo log buffer 中的 redo log,写入到操作系统 pagecache 缓存并持久化到磁盘。​

  2. 当刷盘策略配置为参数 1 的时候,表示每次事务提交时,都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘​

  3. 当刷盘策略配置为参数 2 的时候,表示每次事务提交时,都只是缓存在 redo log buffer 里的 redo log 写到 操作系统的 pagecache 缓存,但是并不会执行刷盘操作,后续由 innodb 后台线程来执行刷盘操作。

Pasted image 20260526002648.png

调优
#

105. 怎么查看一条语句是否走了索引?
#

可以通过 explain 查看 SQL 的执行计划,关注 type 字段 system > const > eq_ref > ref > range > index > ALL

106. extra 字段中的 using index 和 using where 的区别?
#

using index 表示查询使用了索引覆盖,不会回表,这个可以提高查询效率。​

using where 表示MySQL的存储引擎返回给 server 层的数据并不一定满足 where 子句的条件,所以MySQL从存储引擎拿到的数据,还得在 server 层进行了 where 子句的条件判断,来过滤出最终 sql 所需要查询的数据。

107. 怎么找到慢 SQL?
#

可以开启慢查询日志,MySQL 就会自动将执行比较慢的 SQL 语句记录在慢查询日志中,具体多慢我们可以自己设置的,比如设置 3 秒,那么 MySQL 就会将执行超过 3 秒的 SQL 语句记录在慢查询日志中。

108. 如何优化慢 SQL?
#

优化数据访问:limit 子句缩减数据行数、避免 select *​

拆分查询:分而治之的思想,将一个大查询拆分多个小查询,每个小查询只返回一部分查询结果。

覆盖索引:当索引中的列包含所有查询中需要使用的列的时候,可以避免回表​

避免索引失效:检查 SQL 是否因为写的不合理,导致索引失效。​

分解联表查询:让业务层分多个查询来聚合,或者增加冗余字段减少联表查询​

排序优化:对于有排序场景,如果 extra 显示 filesort,这时候就需要考虑对排序的字段建立索引,避免文件排序

109. 深分页场景如何优化?
#

可以通过覆盖索引+子查询方式改进。子查询语句主要查询分页数据对应的数据库唯一id值,因为主键在辅助索引上就有,所以子查询可以不用回表。然后主查询再根据子查询返回的 id,进行索引查询完整的数据行。

110. 如果 SQL 和索引都没问题,查询还是很慢怎么办?
#

  • 缓存
  • 分库,分表,主从复制

选型
#

111. SQL 和 NoSQL 数据库有什么区别?
#

  • 数据存储的区别:SQL 数据库是关系型数据库,主要代表的数据库是MySQL,数据是严格按照二维表格的形式存储的,表与表之间可以建立连接来查询数据。NoSQL是非关系型数据库,主要代表的数据库是Redis、MongoDB等,数据是灵活存储的,对数据的存储格式没有约束,可以在NoSQL中存储各式各样的数据,比如Json文档、图、键值对等等。​

  • 事务的区别:SQL数据库具备ACID四大特性的事务,而 NoSQL数据库是不具备满足ACID特性的事务,因为 NoSQL 数据库都是通过牺牲了 ACID 特性来获取更高性能的​

  • 扩展性的区别:SQL 数据库的数据之间存在关联性,一般会选择垂直扩展,也就是增加服务器的性能,虽然也可以通过分库分表的方式实现水平扩展,但是水平扩展之后,会带来很多新的问题,比如需要解决跨库跨表的查询、分布式事务、全局唯一ID等问题。NoSQL 数据库的数据相当于独立的个人,数据之间的联系很少,因此在进行水平扩展的时候更方便,不用考虑复杂的数据关联问题。

112. MySQL 和 mongodb 之间怎么选型?
#

MySQL 是关系型数据库,支持 ACID 特性的事务,而 MongoDB 是NoSQL类型的数据库,不支持事务,如果业务需要通过事务保证数据一致性的话,是需要选择 MySQL的。​

如果业务上没有强一致性的要求,那么可以根据下面这两种场景来考虑:​

  • MongoDB 是灵活的文档模型。也就是说,如果我预计我的数据可以被一个稳定的模型来描述,那么我会倾向于使用 MySQL 等关系型数据库。而一旦我认为我的数据模型会经常变动,比如说我很难预料到用户会输入什么数据,这种情况下我就更加倾向于使用 MongoDB。​

  • MongoDB 属于 NoSQL,更容易进行横向扩展。虽然关系型数据库也可以通过分库分表来达成横向扩展的目标,但是比 MongoDB 要困难很多,后期运维也要复杂很多,而这一切在 MongoDB 里面都是自动的,运维成本低。

113. MySQL 主从复制的过程是怎么样?
#

Pasted image 20260526145025.png

  • 主库修改数据后,会写入 binlog 日志,从库连接到主库之后,主库会创建一个 log dump 线程,用于发送 bin log 的内容。​

  • 从库会创建一个专门的 I/O 线程 来连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应。​

  • 接着从库还会创建一个用于回放 binlog 的 SQL 线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。

114. MySQL 提供了几种复制模式?默认的复制模式是什么?
#

  • 同步复制:MySQL 主库提交事务的线程要等待所有从库的复制成功响应,才返回客户端结果。这种方式是性能最差的复制模式,但是能保证数据的安全性,如果对数据安全性比较高的业务,可以考虑采用同步复制的模式。​

  • 异步复制:MySQL 主库提交事务的线程并不会等待 binlog 同步到各从库,就返回客户端结果,这种模式性能是最高的,但是一旦主库宕机,数据就会发生丢失。​

  • 半同步复制:介于两者之间,事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行,比如一主二从的集群,只要数据成功复制到任意一个从库上,主库的事务线程就可以返回给客户端。这种半同步复制的方式,兼顾了异步复制和同步复制的优点,即使出现主库宕机,至少还有一个从库有最新的数据,不存在数据丢失的风险。

115. MySQL 主从复制的数据延迟怎么解决?
#

使用缓存解决:可以在写入数据主库的同时,把数据写到 Redis 缓存里,这样其他线程再获取数据时会优先查询缓存,也可以保证数据的一致性。不过这种方式会带来缓存和数据库的一致性问题。​

直接查询主库:对于数据延迟敏感的业务,可以强制读主库。但是我们要提前明确查询的数据量不大,不然会出现主库写请求锁行,影响读请求的执行,最终对主库造成比较大的压力。

116. MySQL 主从架构中,读写分离怎么实现?
#

独立部署的代理中间件,如 MyCat,这一类中间件部署在独立的服务器上,一般使用标准的 MySQL 通信协议,可以代理多个数据库。该方案的优点是隔离底层数据库与上层应用的访问复杂度,比较适合有独立运维团队的公司选型

117. MySQL 主库挂了怎么办?​
#

MySQL 主从复制没有实现发现主服务器宕机和处理故障迁移的功能,要实现自动主从故障迁移的话,我简单了解过,可以使用开源的 MySQL 高可用套件 MHA,MHA 可以在主数据库发生宕机时,可以剔除原有主机,选出新的主机,然后对外提供服务,保证业务的连续性。

118. 什么是分库分表?什么时候需要分表?什么时候需要分库?
#

  • 当单张数据表的数据量太大的时候,经验值是 500W以上的数据量,就会影响了事务的执行效率,这时候就要考虑分表了,通过减少每次查询数据总量来解决数据查询缓慢的问题。​

  • 当单台 MySQL 扛不住高并发流量的时候,就要考虑分库了,把并发请求分散到多台 MySQL 实例中。

Reply by Email