小林Coding—Java「七、MySQL面试题」

发布于:2024-11-27 ⋅ 阅读:(96) ⋅ 点赞:(0)

SQL基础

数据库三大范式是什么?

具体看这里
第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。
第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
第三范式(3NF):在2NF基础上,任何非主属性 (opens new window)不依赖于其它非主属性(在2NF基础上消除传递依赖)第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

MySQL 怎么连表查询?

数据库有一下几种链表查询类型:

  1. 内连接(INNER JOIN)
  2. 左外连接(LEFT JOIN)
  3. 右外连接(RIGHT JOIN)
  4. 全外连接(FULL JOIN)
    在这里插入图片描述
    1. 内连接(INNER JOIN)
    内连接返回两个表中有匹配关系的行。
    语法:
select a.*, b.* from tablea a
inner join tableb b
on a.id = b.id

或

select a.*, b.* from tablea a
join tableb b
on a.id = b.id

在这里插入图片描述
2. 左外连接(LEFT JOIN)

select a.*, b.* from tablea a
left join tableb b
on a.id = b.id
或
select a.*, b.* from tablea a
left outer join tableb b
on a.id = b.id

在这里插入图片描述
3. 右外连接(RIGHT JOIN)

select a.id aid,a.age,b.id bid,b.name from tablea a
right join tableb b
on a.id = b.id

在这里插入图片描述
4. 全连接(FULL JOIN)
全外连接返回两个表中所有行,包括非匹配行,在MySQL中,FULL JOIN 需要使用 UNION 来实现,因为 MySQL 不直接支持 FULL JOIN。

select a.id aid,a.age,b.id bid,b.name from tablea a
left join tableb b
on a.id = b.id
union
select a.id aid,a.age,b.id bid,b.name from tablea a
right join tableb b
on a.id = b.id

在这里插入图片描述

MySQL如何避免重复插入数据?

方式一:使用UNIQUE约束
在表的相关列上添加UNIQUE约束,确保每个值在该列中唯一。例如:

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(255)
);

方法二:使用INSERT … IN DUPLICATE KEY UPDATE
这种语句允许在插入记录时处理重复键的情况。如果插入的记录与现有记录冲突,可以选择更新现有记录:

INSERT INTO users (email, name) 
VALUES ('example@example.com', 'John Doe')
ON DUPLICATE KEY UPDATE name = VALUES(name);

方式三:使用INSERT IGNORE:
该语句会在插入记录时忽略那些因重复键而导致的插入错误:

INSERT IGNORE INTO users (email, name) 
VALUES ('example@example.com', 'John Doe');

如果email已经存在,这条插入语句将被忽略而不会返回错误。

选择哪种方法取决于具体的需求:

  • 如果需要保证全局唯一性,使用UNIQUE约束是最佳做法
  • 如果需要插入和更新结合,可以使用ON DUPLICATE KEY UPDATE。
  • 对于快速忽略重复插入,INSERT IGNORE是合适选择。

CHAR 和 VARCHAR有什么区别?

  • CHAR是固定长度的字符串类型,定义时需要指定固定长度,存储时会在末尾补足空格。CHAR适合存储长度固定的数据,如固定长度的代码、状态等。存储空间固定对于短字符串效率较高。
  • VARCHAR是可变长度的字符串类型,定义时需要指定最大长度,实际存储时根据实际长度占用存储空间。VARCHAR适合存储可变长度的数据,如用户输入的文本、备注等,节约存储空间。

Text数据类型可以无限大吗?

MySQL 3种text类型的最大长度如下:

  • TEXT:65535bytes ~ 64kb
  • MEDIUMTEXT:16777215 bytes ~ 16Mb
  • LONGTEXT:4294967295 bytes ~ 4Gb

说一下外键约束

外键约束的作用是维护表与表之间的关系,确保数据的完整性和一致性。让我们举一个简单的例子:

假设你有两个表,一个是学生表,另一个是课程表,这两个表之间有一个关系,即一个学生可以选修多门课程,而一门课程也可以被多个学生选修。在这种情况下,我们可以在学生表中定义一个指向课程表的外键,如下所示:

CREATE TABLE students (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  course_id INT,
  FOREIGN KEY (course_id) REFERENCES courses(id)
);

这里student表中的course_id字段是一个外键,它指向courses表中的id字段。这个外键约束确保了每个学生所选的课程在courses表中都存在,从而维护了数据的完整性和一致性。

如果没有定义外键约束,那么就有可能出现学生选了不存在的课程或者删除了一个课程而忘记从学生表中删除选修该课程的学生的情况,这会破坏数据的完整性和一致性。因此,使用外键约束可以帮助我们避免这些问题。

MySQL的关键字in和exist

存储引擎

执行一条SQL请求的过程是什么?

下图是执行一条SQL查询语句 的流程,也可以从途中看出MySQL内部架构里的各个功能模块。
在这里插入图片描述

  • 连接器:建立连接、管理连接,校验用户身份;
  • 查询缓存:查询语句如果命中查询缓存,则直接返回,否则继续执行往下执行。MySQL 8.0已经删除该模块
  • 解析器:通过解析器对SQL查询语句进行语法分析、词法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;
  • 执行SQL:执行SQL共有 3 个阶段:
    • 预处理阶段:检查表或字段是否存在;将select * 中的*符号扩展为表上的所有列。
    • 优化阶段:基于查询成本的考虑,选择查询成本最小的执行计划;
    • 执行阶段:根据执行计划执行SQL查询语句,从存储引擎读取记录,返回给客户端

讲一讲mysql的引擎吧,你有什么了解?

  • InnoDB:InnoDB是MySQL的默认存储引擎,具有ACID事务支持、行级锁、外键约束等特性。它适用于高并发的读写操作,支持较好的数据完整性和并发控制。
  • MyISAM:MyISAM是MySQL的另一种存储引擎,具有较低的存储空间和内存消耗,适用于大量读操作的场景。然而,MyISAM不支持事务、行级锁、外键约束,因此在并发写入和数据完整性方面有一定的限制。
  • Memory:Memory引擎将数据存储在内存中,适用于对性能要求较高的读操作。但在服务器重启或崩溃时数据会都是。它不支持事务、行级锁、外键约束

MySQL为什么InnoDB是默认引擎?

  • 事务: InnoDB支持事务,MyISAM不支持事务,这是MySQL将默认存储引擎从MyISAM改为InnoDB的重要原因之一。
  • 索引结构: InnoDB是聚簇索引,MyISAM是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此InnoDB必须要有主键。通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键过大,其他索引也都会很大。而MyISAM是非聚簇索引,数据文件是分离的。索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  • 锁粒度: InnoDB最小的锁粒度是行锁,MyISAM最小锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。
  • count的效率: InnoDB不保存表的具体行数,执行selec t count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句只需要读出变量即可,速度很快。

数据管理里,数据文件大体分成哪几种数据文件?

索引

索引是什么?有什么好处?

索引类似于书籍的目录,可以减少扫描的数据量,提高查询效率。

  • 如果查询的时候,没有用到索引就会全表扫描,这时候查询的时间复杂度是O(n)
  • 如果用到了索引,那么查询的时候,可以基于二分查找算法,通过索引快速定位到目标数据,mysql的索引的数据结构一般是b+树,其搜索时间复杂度为O(logdN),其中d表示节点允许的最大子节点个数为d个。

讲讲索引的分类是什么?

  • 按「数据结构」分类:B+ tree索引、Hash索引、Full-text索引
  • 按「物理存储」分类:聚簇索引(主键索引)、二级索引(辅助索引)
  • 按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。
  • 按「字段个数」索引:单列索引、联合索引。

按数据结构分类:
在这里插入图片描述
InnoDB 是在 MySQL 5.5 之后成为默认的 MySQL 存储引擎,B+Tree 索引类型也是 MySQL 存储引擎采用最多的索引类型。

在创建表时,InnoDB 存储引擎会根据不同的场景选择不同的列作为索引:

  • 如果有主键,默认会使用主键作为聚簇索引的索引键(key);
  • 如果没有主键,就选择第一个不包含NULL值的唯一列作为聚簇索引的索引键(key)
  • 在上面两个都没有的情况下,InnoDB将自动生成一个隐式自增id列作为聚簇索引的索引键(key);

其他索引都属于辅助索引,也被称为二级索引或非聚簇索引。创建的主键索引和二级索引默认使用的是B + Tree索引

按物理存储分类:
从物理存储的角度来看,索引分为聚簇索引(主键索引)、二级索引(辅助索引)。
这两个的区别:

  • 主键索引的B+ 树的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的B+树的叶子节点里
  • 二级索引的B+树的叶子节点存放的是主键值,而不是实际数据。

所以,在查询时使用了二级索引,如果查询的数据能在二级索引里查询得到,那么就不需要回表,这个过程就是覆盖索引。如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,再检索主键索引,就能查询到数据了。这个过程就是回表。

按字段特性分类:

从字段特性的角度来看,索引分为:主键索引、唯一索引、普通索引、前缀索引。

  • 主键索引
    主键索引就是建立在主键字段上的索引,通常在创建表的时候一起创建,一张表最多只有一个主键索引,索引列的值不允许有空值。(使用primary key)

创建主键索引的方式如下:

CREATE TABLE table_name  (
  ....
  PRIMARY KEY (index_column_1) USING BTREE
);
  • 唯一索引
    唯一索引建立在UNIQUE字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值。

在创建表时,创建唯一索引的方式如下:

CREATE TABLE table_name  (
  ....
  UNIQUE KEY(index_column_1,index_column_2,...) 
);
  • 普通索引
    普通索引就是建立在普通字段上的索引,既不要求字段为主键,也不要求字段为 UNIQUE。

在创建表时,创建普通索引的方式如下:

CREATE TABLE table_name  (
  ....
  INDEX(index_column_1,index_column_2,...) 
);

建表后,如果要创建普通索引,可以使用这面这条命令:

CREATE INDEX index_name
ON table_name(index_column_1,index_column_2,...);
  • 前缀索引
    前缀索引是指对字符类型字段的前几个字符建立的索引,而不是在整个字段上建立的索引,前缀索引可以建立在字段类型为 char、 varchar、binary、varbinary 的列上。

使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率。

在创建表时,创建前缀索引的方式如下:

CREATE TABLE table_name(
    column_list,
    INDEX(column_name(length))
);

建表后,如果要创建前缀索引,可以使用这面这条命令:

CREATE INDEX index_name
ON table_name(column_name(length));

按字段个数分类:
从字段个数的角度来看,索引分为单列索引、联合索引(复合索引)

  • 建立在单列上的索引被称为单列索引,比如主键索引。
  • 建立在多列上的索引被称为联合索引;

通过将多个字段组合成一个索引,该索引就被称为联合索引。

比如,将商品表中的pruduct_no和name字段组合成联合索引(product_no, name),创建联合索引的方式如下:

CREATE INDEX index_product_no_name ON product(product_no, name);

联合索引(product_no, name) 的 B+Tree 示意图如下
![[Pasted image 20241118165229.png]]

也就是说,联合索引查询的 B+Tree 是先按 product_no 进行排序,然后再 product_no 相同的情况再按 name 字段排序。

因此,使用联合索引时,存在最左匹配原则,也就是默认按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效。这样就无法利用索引快速查询的特性了。

比如,创建了一个(a, b, c)联合索引,如果查询条件是一下几种,就可以匹配上联合索引:

  • where a = 1;
  • where a = 1 and b = 2 and c =3;
  • where a = 1 and b = 2;
    需要注意的是,由于有查询优化器,所以a字段在where子句的顺序并不重要。

但是,如果查询条件是以下这几种,因为不符合「最左匹配原则」,所以就无法匹配上联合索引,联合索引就会失效:

  • where b = 2;
  • where c = 3;
  • where b = 2 and c = 3;
    上面这些查询条件之所以会失效,是因为(a, b, c)联合索引,是先按a排序,在a相同的情况下再按b排序,在b相同的情况下再按c排序。所以,b和c是全局无需,局部嫌贵的有序的,这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。

联合索引有一些特殊情况,并不是查询过程使用了联合索引查询,就代表联合索引中的所有字段都用到了联合索引进行索引查询。 也就是可能存在部分字段用到了联合索引的B+树,部分字段没有用到联合索引的B+树的情况。

这种特殊情况就发生在范围查询。联合索引的最左匹配原则会一直向右匹配直到遇到「范围查询」就会停止匹配。也就是范围查询的字段可以用到联合索引,但是在范围查询字段后面的字段无法用到联合索引。

MySQL聚簇索引和非聚簇索引的区别是什么?
![[../../../assets/Pasted image 20241118191649.png]]

  • 数据存储: 在聚簇索引中,数据行按照索引键值的顺序存储,也就是说,聚簇索引的叶子节点包含了实际的数据行。这意味着索引结构本身就是数据的物理存储结构。菲聚簇索引的叶子节点不包含完整的数据行,而是包含执行数据行的指针或主键值。数据行本身存储在聚簇索引中。
  • 索引与数据关系: 当通过聚簇索引查找数据时,可以直接从所以那种获取数据行而不需要额外的步骤去查找数据所在的位置。单通过非聚簇索引查找数据时,首先在非聚簇索引中找到对应的主键值,然后通过这个主键值回溯到聚簇索引中查找实际的数据行,这一过程叫做回表。
  • 唯一性: 聚簇索引通常是基于主键构建的,因此每个表只能有一个聚簇索引,因为数据只能有一种物理排序方式。一个表可以有多个非聚簇索引,因为他们不直接影响数据的物理存储位置。
  • 效率: 对于范围查询和排序查询,聚簇索引通常更有效率。因为他避免了额外的寻址开销。非聚簇索引在使用覆盖索引查找时效率更高,因为他不需要读取完整的数据行,但是需要进行回表的操作,使用非聚簇索引效率比较低,因为需要进行额外的回表操作。
    在这里插入图片描述

如果聚簇索引的数据更新,它的存储要不要变化?

  • 如果更新的数据是非索引数据,即普通的用户记录,那么存储结构是不会发生变化的。
  • 如果更新的数据是索引数据,那存储结构是有变化的,因为要维护b+树的有序性。

MySQL主键是聚簇索引吗?

在MySQL的InnoDB存储引擎中,主键是以聚簇索引的形式存储的。

InnoDB将数据存储在B+树的结构中,其中主键索引的B+树就是所谓的聚簇索引。这意味着表中的数据行在物理上是按照主键的顺序排序的,聚簇索引的叶节点包含了实际的数据行。
在这里插入图片描述
InnoDB在创建聚簇索引时,会根据不同的场景选择不同的列作为索引:

  • 如果有主键,默认会使用主键作为聚簇索引的索引键;
  • 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键;
  • 在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键;

一张表只能有一个聚簇索引,为了实现非主键字段的快速索引,就引出了二级索引(非聚簇索引/辅助索引),它也是利用了B+树的数据结构,但是二级索引的叶子节点存放的是主键值,不是实际数据。

什么字段适合做主键?

  • 字段具有唯一性,且不能为空。
  • 字段最好是有递增的趋势,如果字段的值是随机无序的,可能会引发页分裂的问题,造成性能影响。
  • 不建议用业务数据做主键,例如会员卡号、订单号、学生号之类的。因为我们无法预测未来会不会因为业务需要,而出现业务字段重复或复用的情况。
  • 通常情况下会用自增字段来做主键,对于单机系统来说是没问题的,但是如果有多台服务器,各自都可以录入数据,那就不一定使用了。因为如果每台机器各自产生的数据需要合并,就可能会出现主键重复的问题,这时候就要考虑分布式id的方案了。

性别字段能加索引吗?为什么?

不建议针对性别字段加索引。

这实际上与索引创建规则之一区分度有关,性别字段,假设有100w数据,50w男以及50w女,区别度几乎等于0.

区分度的计算方式为:select count(DISTINCT sex)/count(*) from sys_user

实际上对于性别字段不适合创建索引,是因为select *操作,还得进行50w次回表操作,根据主键从聚簇索引中找到其他字段,这一部分开销从上面的测试来说还是比较大的,所以从性能角度来看不建议性别字段加索引,加上索引并不是索引失败,而是回表操作使其很慢。

既然走索引的查询的成本比全表扫描高,优化器就会选择全表扫描的方向进行查询,这时候建立的性能字段索引就没有起到加快查询的作用,反而还因为创建了索引占用空间。

表中十个字段,你主键是用自增ID还是UUID,为什么?

用的是自增Id。

因为uuid相对于 顺序的自增id来说是毫无规律可言的, 新行的值不一定要比之前的主键的值要大,所以innoDB无法做到总是把新行插入到索引的最后,而是需要为新行重新寻找合适的位置从而来分配新的空间。

这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:

  • 写入的目标也很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,InnoDB在插入之前不得不先找到病从磁盘读取目标页到内存中,这将导致大量的随机IO。
  • 因为写入的顺序是乱序的,InnoDB不得不频繁的做页分裂操作,以便为新的行分配存储空间。页分裂导致移动大量的数据,影响性能。
  • 由于频繁的页分裂,页会变得稀疏并被不规则的填满,最终会导致数据会有碎片。

结论:使用InnoDB应该尽可能按主键的自增顺序插入,并且尽可能使用单调增加的聚簇键的值来插入新行。

什么自增ID更快一些,UUID不快吗,它在B+树里存储的是有序的吗?

自增的主键的值是顺序的,所以InnoDB把每一条记录都存储在一条记录的后面,所以自增id更快的原因:

  • 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式被加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费。
  • 新插入的行一定会在原有的最大数据行下一行,mysql定位和寻址的很快,不会因为计算新航的位置而做出额外的消耗。
  • 减少了页分裂和碎片的产生。

但是UUID不是递增的,MySQL中索引的数据结构是B+树,这种数据结构的特点是索引树上的节点的数据是有序的,而如果使用UUID作为主键,那么每次插入数据时,因为无法保证每次产生的UUID有序,所以就会出现新的UUID需要插入到索引树的中间去,这样可能会频繁地导致页分裂,使性能下降。

而且,UUID太占用内存。每个UUID由36个字符组成,在字符串进行比较时,需要从前往后比较,字符串越长,性能越差。另外字符串越长,占用的内存越大,由于页的大小是固定的,这样一个页上能存放的关键字数量就会减少,这样最终就会导致索引树的高度越大,在索引搜索的时候,发生的磁盘IO次数越多,性能越差。

MySQL中的索引是怎么实现的?

MySQL InnoDB引擎是用了B+树作为索引的数据结构。

B+树是一种多叉树,叶子节点才存放数据,非叶子节点只存放索引,而且每个节点里的数据是按照主键顺序存放的。每一层父节点的索引值都会出现在下层子节点的索引值中,因此在叶子节点中,包含了所有的索引值信息,并且每一个叶子节点都有两个指针,分别指向下一个叶子节点和上一个叶子节点,形成一个双向链表。

主键索引的B+树如图所示:
在这里插入图片描述
比如,我们执行了下面这条查询语句:

select * from product where id= 5;

这条语句使用了主键索引查询 id 号为 5 的商品。查询过程是这样的,B+Tree 会自顶向下逐层进行查找:

  • 将 5 与根节点的索引数据 (1,10,20) 比较,5 在 1 和 10 之间,所以根据 B+Tree的搜索逻辑,找到第二层的索引数据 (1,4,7);
  • 在第二层的索引数据 (1,4,7)中进行查找,因为 5 在 4 和 7 之间,所以找到第三层的索引数据(4,5,6);
  • 在叶子节点的索引数据(4,5,6)中进行查找,然后我们找到了索引值为 5 的行数据。
    数据库的索引和数据都是存储在硬盘的,我们可以把读取一个节点当作一次磁盘 I/O 操作。那么上面的整个查询过程一共经历了 3 个节点,也就是进行了 3 次 I/O 操作。

B+Tree 存储千万级的数据只需要 3-4 层高度就可以满足,这意味着从千万级的表查询目标数据最多需要 3-4 次磁盘 I/O,所以B+Tree 相比于 B 树和二叉树来说,最大的优势在于查询效率很高,因为即使在数据量很大的情况,查询一个数据的磁盘 I/O 依然维持在 3-4次。

查询数据时,到了B+树的叶子节点,之后的查找数据是如何做?(不懂⭐️⭐️)

数据页中的记录按照「主键」顺序组成的单向链表。 单项链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索。

因此,数据页中有一个页目录,起到记录的索引作用,就像我们的书那样,针对书中内容的每个章节设立了一个目录,想看某个章节时,可以查看目录,快速找到对应的章节的页,而数据页中的页目录就是为了能够快速找到记录。

那InnoDB是如何给记录创建页目录的呢?(不懂⭐️⭐️)在这里插入图片描述
页目录创建的过程如下:

  1. 将所有的记录划分成几个组,这些记录包含最小记录和最大记录,但不包括标记为“已删除”的记录;
  2. 每个记录组的最后一条记录就是组内最大的那条记录,并且最后一条记录的头信息中会存储该组一共有多少条记录,作为n_owned字段(上图中粉红色字段)
  3. 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录。

从图中可以看到,页目录就是由多个槽组成的,槽相当于分组记录的索引。

B+树的特性是什么?

  • 所有叶子节点都在同一层: 这确保了所有数据项的检索都具有相同的I/O延迟,提高了搜索效率。每个叶子节点都包含指向相邻叶子节点的指针,形成一个链表。由于叶子节点之间的链接,B+树非常适合进行范围查询和排序扫描。可以沿着叶子节点的链表顺序访问数据,而无需进行多次随机访问。
  • 非叶子节点存储键值: 非叶子节点仅存储键值和指向子节点的指针,不包含数据记录。这些键值 用于指导搜索路径,帮助快速定位到正确的叶子节点。并且, 由于非叶子节点只存放键值,当数据量比较大时,相对于B树,B+树的层高更少,查找效率也就更高。
  • 叶子节点存储数据记录: 与B树不同,B+树的叶子节点存放实际的数据记录或指向数据记录的指针。这意味着每次搜索都会到达叶子节点,才能找到所需数据。
  • 自平衡: B+树在插入、删除和更新操作后会自动重新平衡,确保树的高度相对稳定,从而保持良好的搜索性能。每个节点最多可以有M个子节点,最少可以有ceil(M/2)个子节点(除根节点外)。M为树的阶数。

说说B+树和B树的区别

  • 在B+树中,数据都存储到叶子节点上,而分支节点只存储索引信息;B树的非叶子节点即存储索引也存储部分数据
  • B+树的叶子节点使用链表相连,便于范围查找和顺序访问,B树的叶子节点没有链表连接。
  • B+树的查找性能更稳定,每次查找时都需要查找到叶子节点;而B树的查找可能会在分支节点找到数据,性能相对不稳定。

B+树的好处是什么?

B树和B+树都是通过多叉树的方式,会将树的高度变矮,所以这两个数据结构非常适合检索存于磁盘中的数据。
在这里插入图片描述
但是MySQL默认存储引擎InnoDB采用的是B+作为索引的数据结构,原因如下:

  • B+树的分支节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的B树,B+树的分支节点可以存放更多的索引,因此B+树可以比B树更「矮胖」,查询底层节点的磁盘I/O次数会更少。
  • B+树有大量的冗余节点(所有的分支节点都是冗余索引),这些冗余索引会让B+树在插入、删除的效率都更高,比如删除根节点的时候,不会像B树那样发生复杂的树的变化。
  • B+树叶子节点之间用链表连接起来,有利于范围查询,而B树要实现范围查询只能通过树的遍历来完成范围查询,这会设计多个节点的磁盘I/O操作,范围查询效率不如B+树。

B+树的叶子节点链表是单向的还是双向的?

双向的,为了实现倒序遍历或者排序。
在这里插入图片描述

  • B+ 树的叶子节点之间是用「双向链表」进行连接,这样的好处是既能向右遍历,也能向左遍历。
  • B+ 树的节点内容是数据页,数据页里存放了用户的记录以及各种信息,每个数据页默认大小是 16 KB。

InnoDB根据索引类型不同,分为聚集和二级索引。他们的区别在于,聚集索引的叶子节点存放的是实际数据,所有完整的用户记录都存放在聚集索引的叶子节点上,而二级索引的叶子节点存放的是主键值,而不是实际数据。

因为表的数据都是存放着聚集索引的叶子节点里,所以InnoDB存储引擎一定会为表创建一个聚集索引,且由于数据在物理上只会保存一份,所以聚簇索引只能有一个,而二级索引可以创建多个。

MySQL为什么用B+树结构,和其他结构比的优点?

  • B+Tree vs B Tree: B+Tree 只在叶子节点存储数据,而 B 树 的非叶子节点也要存储数据,所以 B+Tree 的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。另外,B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。
  • B+Tree vs 二叉树: 对于有 N 个叶子节点的 B+Tree,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。在实际的应用当中, d 值是大于100的,这样就保证了,即使数据达到千万级别时,B+Tree 的高度依然维持在 3~4 层左右,也就是说一次数据查询操作只需要做 3~4 次的磁盘 I/O 操作就能查询到目标数据。而二叉树的每个父节点的儿子节点个数只能是 2 个,意味着其搜索复杂度为 O(logN),这已经比 B+Tree 高出不少,因此二叉树检索到目标数据所经历的磁盘 I/O 次数要更多。
  • B+Tree vs Hash: Hash 在做等值查询的时候效率贼快,搜索复杂度为 O(1)。但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因

为什么 MysSQL 不用 跳表?

B+树的高度在3层时存储的数据可能已经到千万级别,但是对于跳表而言同样去维护千万的数据量造成的跳表层数过高而导致的磁盘io次数增多,也就是使用B+树在存储同样的数据下磁盘io次数更少。

联合索引的实现原理?

将多个字段组合成一个索引,该索引就被称为联合索引。

比如,将商品表中的product_no和name字段组合成联合索引(product_no, name),创建联合索引的方式如下:

CREATE INDEX index_product_no_name ON product(product_no, name);

联合索引()的B+树示意图如下:
在这里插入图片描述
可以看到,联合索引的分支节点用两个字段的值作为B+树的key值。当在联合索引查询数据时,先按product_no字段比较,在product_no相同的情况下再按name字段比较。

也就是说,联合索引查询的B+树是先按照product_no进行排序,在product_no相同的情况下再按照name字段排序。

因此使用联合索引时,存在「最左匹配原则」,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询时,如果不遵循「最左匹配原则」,联合索引就会失效,这样就无法利用索引快速查询的特性了。

比如,如果创建了一个(a,b,c)联合索引,条件是以下几种就可以匹配联合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;
    需要注意的是,因为有查询优化器,所以a字段在where语句的顺序并不重要。

但是如果查询条件是以下几种,因为不符合最左匹配原则就无法匹配上联合索引,联合索引就会失效:

  • where b=2;
  • where c=3;
  • where b=2 and c=3;
    上面查询条件失效的原因是因为(a,b,c)是联合索引,是先按a排序,在a相同的情况下再按b排序,在b相同的情况下再按c排序。所以b和c是全局无序,局部相对有序的。这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。
    我这里举联合索引(a,b)的例子,该联合索引的 B+ Tree 如下:
    在这里插入图片描述
    可以看到,a 是全局有序的(1, 2, 2, 3, 4, 5, 6, 7 ,8),而 b 是全局是无序的(12,7,8,2,3,8,10,5,2)。因此,直接执行where b = 2这种查询条件没有办法利用联合索引的,利用索引的前提是索引里的 key 是有序的。

只有在 a 相同的情况才,b 才是有序的,比如 a 等于 2 的时候,b 的值为(7,8),这时就是有序的,这个有序状态是局部的,因此,执行where a = 2 and b = 7是 a 和 b 字段能用到联合索引的,也就是联合索引生效了。

创建联合索引时需要注意什么?

建立联合索引时的字段顺序,对索引效率也有很大影响。越靠前的字段被用于索引过滤的概率越高,实际开发中建立联合索引时,要把区分度大的字段排在前面,这样区分度大的字段越有可能被更多的SQL使用到。

区分度就是某个字段column不同值的个数 除以 表的总行数,计算公式如下:
在这里插入图片描述
比如,性别的区分度就很小,不适合做索引后排在联合索引列的靠前的位置,而UUID这类字段就比较适合做索引后排在联合索引列的靠前位置。

因为如果索引的区分度很小,假设字段的值分布均匀,name无论搜索哪个值都可能得到一半的数据。这种情况还不如不要索引。因为 MySQL 还有一个查询优化器,查询优化器发现某个值出现在表的数据行中的百分比(惯用的百分比界线是"30%")很高的时候,它一般会忽略索引,进行全表扫描。

联合索引ABC,现在有个执行语句是A = XXX and C < XXX,索引怎么走

根据最左匹配原则,A可以走联合索引,C不会走联合索引,但是C可以走索引下推

联合索引(a,b,c) ,查询条件 where b > xxx and a = x 会生效吗

索引会生效,a和b字段都能利用联合索引,符合联合索引最左匹配原则。

联合索引 (a, b,c),where条件是 a=2 and c = 1,能用到联合索引吗?

会用到联合索引,但是只有a才能走索引,c无法走,因为不符合「最左匹配原则」。虽然c无法走索引,但是c字段在5.6版本后,会有索引下推的优化,能减少回表查询的次数。

索引失效有哪些情况?

6钟情况会发生索引失效:

  • 当我们使用左或左右模糊匹配时,即like %xx 或 like %xx%这两种方式都会造成索引失效;
  • 当我们在查询条件中对索引列使用函数,就会导致索引失效。
  • 当我们在查询条件中对索引列进行表达式计算,也会导致索引失效。
  • MySQL在遇到字符串和数组比较时,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列而条件语句中输入参数是数字的话,那索引列就会发生隐式类型转换,由于隐式类型转换是通过CAST函数实现的,等同于对索引列使用了函数,所以也会导致索引失效。
  • 联合索引要能正确使用需要遵从最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
  • 在WHERE子句中,如果在OR前的条件是索引列,而在OR后的条件列不是索引列,那么索引会失效。

什么情况下会回表查询

从物理存储的角度来看,索引分为聚簇索引(主键索引)、二级索引(辅助索引)。

它们的主要区别如下:

  • 主键索引的B+树的叶子节点存放的是实际数据,所有完整的用户记录都存放在主键索引的B+树的叶子节点里。
  • 二级索引的B+树的叶子节点存放的是主键值,而不是实际数据。

所以,在查询时使用了二级索引,如果查询的数据能在二级索引里查询得到,则不需要回表,这个过程就是覆盖索引

如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了。这个过程就叫回表
在这里插入图片描述
在这里插入图片描述

什么是覆盖索引:

覆盖索引指的是一个索引包含了查询所需的所有列,因此不需要访问表中的数据行就能完成查询。

换句话说,查询所需的所有数据都能从索引中直接获取,而不需要进行回表查询。覆盖索引能够显著提高查询性能,因为减少了访问数据页的次数,从而减少了I/O操作。

假设有一张表 employees,表结构如下:

CREATE TABLE employees (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  age INT,
  department VARCHAR(100),
  salary DECIMAL(10, 2)
);

CREATE INDEX idx_name_age_department ON employees(name, age, department);

如果我们有以下查询:

SELECT name, age, department FROM employees WHERE name = 'John';

在这种情况下,idx_name_age_department 是一个覆盖索引,因为它包含了查询所需的所有列:name、age 和 department。(即这个索引包含了数据库中的字段,根据索引就可以直接获取结果,而不需要再去查看表中的实际数据行)。查询可以完全在索引层完成,而不需要访问表中的数据行。

如果一个列即是单列索引,又是联合索引,单独查他的话先走哪个索引?

mysql优化器会分析每个索引的查询成本,然后选择成本最低的方案来执行sql。

如果单列索引是a,联合索引是(a, b),那么针对下面的查询:

select a, b from table where a = ? and b =?

优化器会选择联合索引,因为查询成本更低, 查询也不需要回表了,直接索引覆盖了。

索引字段是不是越多越好?

不是,建的越多会占用越多的空间,而且在写入频繁的场景下,对于B+树的维护所付出的性能消耗也会越大。

如果一个字段的status是0或1,适合建立索引吗?

不适合,区分度低的字段不适合建立索引。

索引的优缺点?

索引最大的好处是提高查询速度,但是索引也是有缺点的,如:

  • 需要占用物理空间,数量越大,占用空间越大。
  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增大而增大;
  • 会降低表的增删改的效率,因此每次增删改索引,B+树为了维护索引有序性,都需要进行动态维护。

所以,索引不是万能钥匙,它也是根据场景来使用的。

怎么决定建立哪些索引?

什么时候适用索引?

  • 字段有唯一性限制的,比如商品编码;
  • 经常用于WHERE查询条件的字段,这样能够提高整个表的查询速度,如果查询条件不是一个字段,可以建立联合索引。
  • 经常用于GROUP BYORDER BY的字段,这样在查询的时候就不需要再去做一次排序了。因为我们都已经知道了建立索引之后在B+树中的记录都是排好序的,不需要再去做一次排序了。
    在这里插入图片描述

什么时候不需要创建索引?

  • WHEREGROUP BYORDER BY里用不到的字段。索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占据物理空间的。
  • 字段中存在大量重复数据,不需要创建索引。比如性别字段,只有男女。如果数据库表中,男女的记录分布均匀,那么无论搜索哪个值都可能得到一半的数据。在这些情况下,还不如不要索引。因为MySQL还有一个查询优化器,查询优化器发现某个值出现在表的数据行中的百分比很高的时候,他一般会忽略索引,进行全表扫描。
  • 表数据太少,不需要创建索引。
  • 经常更新的字段不用创建索引,比如不要对电商项目的用户余额建立索引,因为索引字段频繁修改。

索引优化详细讲讲

常见优化索引的方法:

  • 前缀索引优化:使用前缀索引是为了减少索引字段的大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减少索引项的大小。
  • 覆盖索引优化:覆盖索引是指SQL中query的所有字段,在索引B+树的叶子节点上都能找得到那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作。
  • 主键索引最好是自增的:
    • 如果我们使用自增主键,那么每次插入的新数据就会按照顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满时,会自动开辟一个新页面。因为每次插入一条新纪录,都是追加操作,不需要移动已有的数据。当页面写满时,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据。 因此这种插入数据的方法效率非常高。
    • 如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其他数据来满足新数据的插入,甚至需要从一个页面复制数据到另一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。
  • 防止索引失效:
    • 当我们使用左或左右模糊匹配的时候,即like %xx或者like %xx%这两种方式都会造成索引失效。
    • 当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
    • 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
    • 在WHERE语句汇总,如果在OR前面的条件是索引列而OR后面的条件列不是索引列,那么索引会失效。

了解过前缀索引吗

使用前缀索引是为了见效索引字段的大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。
在这里插入图片描述

事务

事务的特性是什么,如何实现的?

  • 原子性(Atomicity): 一个事务中的所有操作,要么全部完成,要么全部不完成,不会阶数在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始时的状态,就像这个事务没有被执行过一样。
  • 一致性(Consistency): 是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。比如,用户 A 和用户 B 在银行分别有 800 元和 600 元,总共 1400 元,用户 A 给用户 B 转账 200 元,分为两个步骤,从 A 的账户扣除 200 元和对 B 的账户增加 200 元。一致性就是要求上述步骤操作后,最后的结果是用户 A 还有 600 元,用户 B 有 800 元,总共 1400 元,而不会出现用户 A 扣除了 200 元,但用户 B 未增加的情况(该情况,用户 A 和 B 均为 600 元,总共 1200 元)。
  • 隔离性(Isolation): 数据库运行多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同事使用相同的数据时,不会相互干扰,每个事务都要一个完整的数据空间,对其他并发事务是隔离的。也就是手,消费者购买商品这个事务,是不会影响其他消费者购买的。
  • 持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即使系统故障也不会丢失。

MySQL InnoDB引擎通过什么技术来保证事务的四个特性呢?

  • 持久性是通过redo log(重做日志)来保证的 。
  • 原子性是通过undo log(回滚日志)来保证的
  • 隔离性是通过MVCC(多版本并发控制)或锁机制来保证的
  • 一致性是通过持久性+原子性+隔离性来保证的。

mysql可能出现什么和并发相关问题?

MySQL 服务端是允许多个客户端连接的,这意味着 MySQL 会出现同时处理多个事务的情况。

那么在同时处理多个事务的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。

  • 脏读:
    如果在一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。

  • 不可重复读:
    在一个事务内 多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生「不可重复读」现象

  • 幻读:
    在一个事务内多次查询某个符合查询条件「记录数量」,如果出现前后查询到的记录数量不一致,则出现了幻读。

哪些场景不适合脏读,举例说明?

脏读是指一个事务在读取到另一个事务未提交的数据时发生。脏读可能会导致不一样的数据被读取

  • 银行系统: 如果一个账户的余额正在被调整但未提交,另一个事务读取到了这个临时的余额,就会导致客户看不到正确的余额。
  • 库存管理系统: 如果一个商品的数量正在被更新但尚未提交,另一个事务读取了这个临时的数量,就会导致库存管理错误。
  • 在线订单系统: 如果一个订单正在被修改但未提交,另一个事务读取了这个临时的订单状态,可能导致订单状态显示错误,客户收到不准确的信息。

mysql是怎么解决并发问题的?

  • 锁机制:MySQL提供了多种锁机制来保证数据的一致性,包括行级锁、表级锁,页级锁等。通过锁机制,可以在读写操作时对数据进行加锁,确保同事只有一个操作能够访问或修改数据。
  • 事务隔离级别:MySQL提供了多种事务隔离级别,包括读未提交、读已提交、可重复度和串行花。通过设置合适的事务隔离级别,可以在多个事务并发执行时,控制事务之间的隔离程度,以避免数据不一致的问题。
  • MVCC(多版本并发控制):MySQL使用MVCC来管理并发访问。它通过在数据库中保存不同版本的数据来实现不同事务之间的隔离。在读取数据时,MySQL会根据事务的隔离级别来选择合适的数据版本,从而保证数据的一致性。

事务的隔离级别有哪些?

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

  • 读已提交(read commited): 指一个事务提交后,它做的变更才能被其他事务看到。

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

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

  • 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;

  • 在「读已提交」隔离级别下,可能发生不可重复读和幻读,不会发生脏读

  • 在「可重复读」隔离级别下,可能发生幻读,但不会发生脏读和不可重复读

  • 在「串行化」隔离级别下,脏读、不可重复读、幻读都不会发生。

举例:
有一张账户余额表,里面有一条账户余额为 100 万的记录。然后有两个并发的事务,事务 A 只负责查询余额,事务 B 则会将我的余额改成 200 万,下面是按照时间顺序执行两个事务的行为:
在这里插入图片描述

  • 在「读未提交」隔离级别下,事务 B 修改余额后,虽然没有提交事务,但是此时的余额已经可以被事务 A 看见了,于是事务 A 中余额 V1 查询的值是 200 万,余额 V2、V3 自然也是 200 万了;
  • 在「读提交」隔离级别下,事务 B 修改余额后,因为没有提交事务,所以事务 A 中余额 V1 的值还是 100 万,等事务 B 提交完后,最新的余额数据才能被事务 A 看见,因此额 V2、V3 都是 200 万;
  • 在「可重复读」隔离级别下,事务 A 只能看见启动事务时的数据,所以余额 V1、余额 V2 的值都是 100 万,当事务 A 提交事务后,就能看见最新的余额数据了,所以余额 V3 的值是 200 万;
  • 在「串行化」隔离级别下,事务 B 在执行将余额 100 万修改为 200 万时,由于此前事务 A 执行了读操作,这样就发生了读写冲突,于是就会被锁住,直到事务 A 提交后,事务 B 才可以继续执行,所以从 A 的角度看,余额 V1、V2 的值是 100 万,余额 V3 的值是 200万。

这四种隔离级别具体是如何实现的?

  • 对于「读未提交」,因为可以直接读取到未提交事务修改的数据,所以直接读取最新的数据。
  • 对于「读已提交」和「可重复读」,他们是通过Read View实现的,他们的区别在于创建Read View的实际不同:「读已提交」是在「每个语句执行前」都会重新生成一个Read View,而「可重复读」是「启动事务时」生成一个Read View,然后整个事务期间都在用这个Read View。
  • 对于「串行化」隔离级别事务来说,通过加读写锁的方式来避免并行访问;

MySQL默认隔离级别是什么?

可重复读隔离级别

可重复度隔离级别下,A事务提交的数据,在B事务能看见吗?

可重复读隔离级别是由MVCC(多版本并发控制)实现的,实现的方式是开始事务后(执行begin语句后),在执行第一个查询语句后,会创建一个Read View,后续的查询语句利用这个Read View,通过这个Read View就可以在undo log版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这个数据的。

举例说明可重复读下的幻读问题

可重复读隔离级别下虽然很大程度上解决了幻读,但是还是没有能完全解决幻读。
在这里插入图片描述
事务A执行查询id=5的记录,此时表中是没有该记录的,所以查询不出来。

然后事务 B 插入一条 id = 5 的记录,并且提交了事务。

此时事务A更新id=5这条记录,是的,事务A虽然看不到id=5这条记录,但是他去更新了这条记录。这场景确实太他妈违和了,然后在此查询id=5的记录,事务A就能看到事务B插入的记录了。幻读就是发生在这种违和的场景中。

整个幻读的时序图如下:

在可重复读隔离级别下,事务A第一次执行普通的select语句时生成一个ReadView,之后事务B向表中新插入了一条id=5的记录并提交。接着,事务A对id=5这条记录进行了更新,在这个时刻,这条新纪录trx_id隐藏列的值就变成了事务A的事务id(trx_id表示最近一次修改此记录的事务 ID,会被更新为事务 A 的事务 ID),之后事务A再使用普通select语句去查询就可以看到这条记录了,就发生了幻读。

因为这种特殊现象的存在,所以我们认为MySQL InnoDB中的MVCC并不能完全避免幻读现象。

Mysql 设置了可重复 读隔离级后,怎么保证不发生幻读?

尽量在开启事务后,马上执行select … for update这类锁定读(重点是锁定读)的语句,因为它会对记录加next-key lock,从而避免其他事务插入一条新纪录,就避免了幻读的问题。
select... for update 是锁定读
读的总结:
在这里插入图片描述

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

是通过行级锁实现的,序列化(串行化)隔离级别下,普通的select查询是会对记录加S型的next-key锁,其他事务就没办法对这些已经加锁的记录来增删改了,从而避免了脏读、不可重复读和幻读现象。

介绍MVCC实现原理(好多 记不住⭐️⭐️)

MVCC允许多个事务同事读取同一行数据,而不会彼此阻塞。每个事务看到的数据版本是该事务开始时的数据版本。这意味着,如果其他事务在此期间修改了数据,正在运行的事务仍然看到的是它开始时的数据状态,从而实现了非阻塞读操作。

对于「读已提交」和「可重复度」隔离级别的事务来说,他们是通过Read View来实现的,他们的区别在于创建Read View的时机不同。大家可以把Read View理解成一个数据快照,就像相机拍照那样,定格某一刻的风景。

  • 「读已提交」是在「每个select语句执行前」都会重新生成一个Read View;
  • 「可重复读」是在执行第一条select时,生成一个Read View,然后整个事务期间都在用这个Read View。

Read View 有四个重要的字段:
在这里插入图片描述

  • m_ids:指的是在创建Read View时,当前数据库中「活跃事务」的事务id列表。 注意是一个列表。“活跃事务”指的是,启动了但还没提交的事务。
  • min_trx_id:指的是在创建Read View时,当前数据库中「活跃事务」中事务id最小的事务,也就是m_ids的最小值。
  • max_trx_id:这个并不是m_ids的最大值,而是创建Read View时当前数据库中应该给下一个事务的id值,也就是全局事务中最大的事务id值+1;
  • creator_trx_id:指的是创建该Read View的事务的事务id

对于使用InnoDB存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
在这里插入图片描述

  • trx_id:当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务id记录着trx_id隐藏列里;
  • roll_pointer:每次对某条局促索引进行改动是,都会把旧版本的记录写入到undo日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过他找到修改前的记录。

在创建Read View后, 我们可以将记录中的trx_id划分为三种情况:
在这里插入图片描述
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的trx_id值小于Read View中的min_trx_id值,表示这个版本的记录是在创建Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见。
  • 如果记录的trx_id值大于等于Read View中的max_trx_id值,表示这个版本的记录是在创建Read View后才启动的事务生成的,所以该版本的记录对当前事务不可见。
  • 如果记录的trx_id值在Read View的min_trx_id和max_trx_id之间,需要判断trx_id是否在m_ids列表中:
    • 如果改记录的trx_id值 m_ids列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
    • 如果记录的trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

这种通过「版本链」来控制并发食物访问同一个记录时的行为就叫MVCC(多版本并发控制)。

一条update是不是原子性的?为什么?

是原子性的。主要通过锁+undolog日志保证原子性的。

  • 执行 update时,会加行级别锁,保证了一个事务更新一条记录的时候,不会被其他事务干扰。
  • 事务执行过程中,会生成undolog,如果事务执行失败,就可以通过undolog日志进行回滚。

滥用事务,或者一个事务里有特别多sql的弊端?(不懂⭐️)

事务的资源在事务提交之后才会释放,比如存储资源、锁。

如果一个事务特别多sql,那么会带来这些问题:

  • 如果一个事务特别多sql,锁定的数据太多,容易造成大量的死锁和锁超时。
  • 回滚记录会占用大量存储空间,事务回滚时间长。在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚个操作,都可以得到钱一个状态的值。sql越多,所需要保存的回滚数据就越多。
  • 如果执行时间长,容易造成主从延迟,主库上必须等事务执行完成才会写入binlog,再传给备库。所以,如果一个主库上的语句执行10分钟,那这个事务很可能就会导致从库延迟10分钟。

讲一下mysql中有哪些锁?

在MySQL中,根据加锁的范围,可以分为全局锁、表级锁和行锁 这三类
在这里插入图片描述

  • 全局锁: 通过flush tables with read lock语句会将整个数据库设为只读状态,这时其他线程执行增删改或表结构修改都会阻塞。全局锁主要应用于做全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件和原来不一样的情况。

  • 表级锁: MySQL中表级锁有这几种:

    • 表锁:通过lock tables语句可以对表加表锁,表锁除了会限制别的线程的读写外,也会限制本线程后面的读写操作。
    • 元数据锁:当我们对数据库表进行操作时,会自动给这个表加上MDL,对一张表进行CRUD操作时,加的是MDL读锁;对一张表做结构变更操作的时候,加的是MDL写锁;MDL是为了保证当用户对表执行CRUD操作时,防止其他线程对这个表结构做变更。
    • 意向锁:当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。意向锁的目的是为了快速判断表里是否有记录被加锁。
  • 行级锁: InnoDB引擎是支持行级锁的,而MyISAM引擎不支持行级锁。

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

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

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

数据库的表锁和行锁有什么作用?

表锁的作用:

  • 整体控制:表锁可以用来控制整个表的并发访问,当一个事务获取了表锁时,其他事务无法对该表进行任何读写操作,从而确保数据的完整性和一致性。
  • 粒度大:表锁的粒度比较大,在锁定表的情况下,可能会影响到整个表的其他操作,可能会引起锁竞争和性能问题。
  • 适用于大批量操作:表锁适合于需要大批量操作表中数据的场景,例如表的重建、大量数据的加载等。

行锁的作用:

  • 细粒度控制:行锁可以精确控制对表中某行数据的访问,使得其他事务可以同时访问表中的其他行数据,在并发量大的系统中能够提高并发性能。
  • 减少锁冲突:行锁不会像表锁那样造成整个表的锁冲突,减少了锁竞争的可能性,提高了并发访问的效率。
  • 适用于频繁单行操作:行锁适合于需要频繁对表中单独行进行操作的场景,例如订单系统中的订单修改、删除等操作。

MySQL两个线程的update语句同时处理一条数据,会不会有阻塞?

如果是两个事务同时更新了id=1,比如update … where id=1,那么是会阻塞的。因为InnoDB存储引擎实现了行级锁。

当A事务对id=1这行记录进行更新时,会对主键id=1的记录加X类型的记录锁,这样第二事务对id=1进行更新时,发现已经有记录锁了,就会陷入阻塞状态。

两条update语句处理一张表的不同的主键范围的记录,一个<10,一个>15,会不会遇到阻塞?底层是为什么的?(不懂⭐️)

不会,因为锁住的范围不一样,不会形成冲突。

第一条 update sql 的话( id<10),锁住的范围是(-♾️,10)
第二条 update sql 的话(id >15),锁住的范围是(15,+♾️)

如果2个范围不是主键或索引?还会阻塞吗?(不懂⭐️)

如果两个范围查询的字段不是索引的话,那么代表update没有用到索引,这时候触发了全表扫描,全部索引都会加行级锁,这时候第二条update执行的时候,就会阻塞了。

因为如果update没有用到索引,在扫描过程中会对索引加锁,所以全表扫描的场景下,所有记录都会被加锁,也就是这条update语句产生了4个记录锁和5个间隙锁,相当于锁住了全表。
在这里插入图片描述

日志

日志文件是分成了哪几种?

  • redo log 重做日志,是InnoDB存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复。
  • undo log 回滚日志,是InnoDB存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC。
  • bin log 二进制日志,是Server层生成的日志,主要用于数据备份和主从复制;
  • relay log 中继日志,用于主从复制场景下,slave(从服务器)通过io线程拷贝master(主服务器)的bin log后本地生成的日志
  • 慢查询日志,用于记录执行时间过长的sql,需要设置阈值后手动开启

讲一下bin log(记不住⭐️)

binlog 文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如 SELECT 和 SHOW 操作。

MySQL 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件,binlog 是 MySQL 的 Server 层实现的日志,所有存储引擎都可以使用。

binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志,用于备份恢复、主从复制;

binlog 有 3 种格式类型,分别是 STATEMENT(默认格式)、ROW、 MIXED,区别如下:

  • STATEMENT:每一条修改数据的 SQL 都会被记录到 binlog 中(相当于记录了逻辑操作,所以针对这种格式, binlog 可以称为逻辑日志),主从复制中 slave 端再根据 SQL 语句重现。但 STATEMENT 有动态函数的问题,比如你用了 uuid 或者 now 这些函数,你在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致;
  • ROW:记录行数据最终被修改成什么样了(这种格式的日志,就不能称为逻辑日志了),不会出现 STATEMENT 下动态函数的问题。但 ROW 的缺点是每行数据的变化结果都会被记录,比如执行批量 update 语句,更新多少行数据就会产生多少条记录,使 binlog 文件过大,而在 STATEMENT 格式下只会记录一个 update 语句而已;
  • MIXED:包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式;

UndoLog日志的作用是什么?

undo log是一种用于撤销回退的日志,它保证了事务的ACID特性中的原子性(Atomicity)
在事务没提交之前,MySQL会先记录更新前的数据到undo log日志文件里。当事务回滚时,可以利用undo log来进行回滚
在这里插入图片描述
每当InnoDB引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到undo log里,比如:

  • 插入 一条记录时,要把这条记录的主键值记下来,这样之后回滚时只需要把这个主键值对应的记录删除 即可。
  • 删除 一条记录时,要把这条记录中的内容都记下来,这样之后回滚时再由这些内容组成的记录插入 到表中即可。
  • 在更新 一条记录时,要把被更新的列的旧值记下来,这样之后回滚时再把这些列更新为旧值 即可。

在发生回滚时,就读取 undo log 里的数据,然后做原先相反操作。如delete一条记录时,undo log就会把记录中的内容都记下来,然后执行回滚操作的时候,就读取undo log里的数据,然后进行insert 操作。

有了undo log为什么还需要redo log呢?

Buffer Pool 是提高了读写效率没错,但是问题来了,Buffer Pool 是基于内存的,而内存总是不可靠,万一断电重启,还没来得及落盘的脏页数据就会丢失。

为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。

后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里,这就是 WAL (Write-Ahead Logging)技术。

WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。

过程如下图:
在这里插入图片描述
redo log 是物理日志,记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。

在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。

当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。

redo log 和 undo log 这两种日志是属于 InnoDB 存储引擎的日志,它们的区别在于:

  • redo log 记录了此次事务「完成后」的数据状态,记录的是更新之后的值;
  • undo log 记录了此次事务「开始前」的数据状态,记录的是更新之前的值;

事务提交之前发生了崩溃,重启后会通过 undo log 回滚事务,事务提交之后发生了崩溃,重启后会通过 redo log 恢复事务,如下图:
在这里插入图片描述
所以有了 redo log,再通过 WAL 技术,InnoDB 就可以保证即使数据库发生异常重启,之前已提交的记录都不会丢失,这个能力称为 crash-safe(崩溃恢复)。可以看出来, redo log 保证了事务四大特性中的持久性。

写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写

磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。

针对「顺序写」为什么比「随机写」更快这个问题,可以比喻为你有一个本子,按照顺序一页一页写肯定比写一个字都要找到对应页写快得多。

可以说这是 WAL 技术的另外一个优点:MySQL 的写操作从磁盘的「随机写」变成了「顺序写」,提升语句的执行性能。这是因为 MySQL 的写操作并不是立刻更新到磁盘上,而是先记录在日志上,然后在合适的时间再更新到磁盘上 。

至此, 针对为什么需要 redo log 这个问题我们有两个答案:

  • 实现事务的持久性,让MySQL有crash-safe的能力,能够保证MySQL在任何时间段突然崩溃,重启后之前已提交的记录都不会丢失;
  • 将写操作从「随机写」变成了「顺序写」,提升MySQL写入磁盘的能力。

redo log怎么保证持久性?(对上面问题的总结更像是)

Redo log是MySQL中用于保证持久性的重要机制之一。它通过以下方式来保证持久性:

  1. Write-ahead logging(WAL):在事务提交之前,将事务所做的修改操作记录到redo log中,然后再将数据写入磁盘。这样即使在数据写入磁盘之前发生了宕机,系统可以通过redo log中的记录来恢复数据。
  2. Redo log的顺序写入:redo log采用追加写入的方式,将redo日志记录追加到文件末尾,而不是随机写入。这样可以减少磁盘的随机I/O操作,提高写入性能。
  3. Checkpoint机制:MySQL会定期将内存中的数据刷新到磁盘,同时将最新的LSN(Log Sequence Number)记录到磁盘中,这个LSN可以确保redo log中的操作是按顺序执行的。在恢复数据时,系统会根据LSN来确定从哪个位置开始应用redo log。

能不能只用bin log 不用redo log?

不行。bin log是server层的日志,没办法记录哪些赃页还没有刷盘。redo log是存储引擎层的日志,可以记录哪些赃页还没有刷盘,这样崩溃恢复的时候,就能恢复那些还没有被刷盘的赃页数据。

bin log 两阶段提交 过程是怎么样的?(没看懂⭐️⭐️)

事务提交之后,redo log和bin log都要持久化到磁盘。但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。

在MySQL的InnoDB存储引擎中,开启bin log的情况下,MySQL会同时维护bin log与InnoDB的redo log,为了保证这两个日志的一致性,MySQL使用了内部XA事务(也有外部XA事务,但和这里不相关),内部XA事务由bin log作为协调者,存储引擎是参与者。

当客户端执行commit语句或在自动提交的情况下,MySQL内部开启一个XA事务,分两阶段来完成XA事务的提交,如下图:
在这里插入图片描述
从图中可看出,事务的提交过程有两个阶段,就是将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入binlog,具体如下:

  • prepare阶段: 将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘(innodb_flush_log_at_trx_commit = 1 的作用);
  • commit 阶段: 把 XID 写入到 binlog,然后将 binlog 持久化到磁盘(sync_binlog = 1 的作用),接着调用引擎的提交事务接口,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;

我们来看看在两阶段提交的不同时刻,MySQL 异常重启会出现什么现象?下图中有时刻 A 和时刻 B 都有可能发生崩溃:
在这里插入图片描述
不管是时刻 A(redo log 已经写入磁盘, binlog 还没写入磁盘),还是时刻 B (redo log 和 binlog 都已经写入磁盘,还没写入 commit 标识)崩溃,此时的 redo log 都处于 prepare 状态。

在MySQL重启后,会按顺序扫描redo log文件,碰到处于prepare状态的redo log,就拿着redo log中的XID去binlog查看是否存在此XID:

  • 如果 binlog 中没有当前内部 XA 事务的 XID,说明 redolog 完成刷盘,但是 binlog 还没有刷盘,则回滚事务。 对应时刻 A 崩溃恢复的情况。
  • 如果 binlog 中有当前内部 XA 事务的 XID,说明 redolog 和 binlog 都已经完成了刷盘,则提交事务。 对应时刻 B 崩溃恢复的情况。

可以看到,对于处于prepare阶段的redo log,既可以提交事务,也可以回滚事务,这取决于是否能在 binlog 中查找到与 redo log 相同的 XID, 如果有就提交事务,如果没有就回滚事务。这样就可以保证 redo log 和 binlog 这两份日志的一致性了。

所以说,两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。

update语句的具体执行过程是怎么样的?(记不住)

具体更新一条记录UPDATE t_user SET name = 'xiaolin' WHERE id = 1; 流程如下:

  1. 执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
    • 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
    • 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
  2. 执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
    • 如果一样的话就不进行后续更新流程;
    • 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
  3. 开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。
  4. InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
  5. 至此,一条记录更新完了。
  6. 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
  7. 事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):
    • prepare 阶段:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
    • commit 阶段:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件);
  8. 至此,一条更新语句执行完成。

性能调优

mysql的explain有什么作用?

explain是查看sql的执行计划,主要用于分析sql语句的执行过程,比如有没有走索引,有没有外部排序,有没有索引覆盖等等。

如下图,就是一个没有使用索引,并且是一个全表扫描的查询语句。
在这里插入图片描述
对于执行计划,参数有:

  • possible_keys 字段表示可能用到的索引;
  • key 字段表示实际用的索引,如果这一项为 NULL,说明没有使用索引;
  • key_len 表示索引的长度;
  • rows 表示扫描的数据行数。
  • type 表示数据扫描类型,我们需要重点看这个。

type字段就是描述了找到所需数据时使用的扫描方式是什么,常见扫描类型的执行效率从低到高的顺序为:

  • All(全表扫描)all是最坏的情况。因为是全表扫描
  • index(全索引扫描):index 和 all 差不多,只不过 index 对索引表进行全扫描,这样做的好处是不再需要对数据进行排序,但是开销依然很大。所以,要尽量避免全表扫描和全索引扫描。
  • range(索引范围扫描):range 表示采用了索引范围扫描,一般在 where 子句中使用 < 、>、in、between 等关键词,只检索给定范围的行,属于范围查找。从这一级别开始,索引的作用会越来越明显,因此我们需要尽量让 SQL 查询可以使用到 range 这一级别及以上的 type 访问方式。
  • ref(非唯一索引扫描):ref 类型表示采用了非唯一索引,或者是唯一索引的非唯一性前缀,返回数据返回可能是多条。因为虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。(不懂⭐️)
  • eq_ref(唯一索引扫描):eq_ref 类型是使用主键或唯一索引时产生的访问方式,通常使用在多表联查中。比如,对两张表进行联查,关联条件是两张表的 user_id 相等,且 user_id 是唯一索引,那么使用 EXPLAIN 进行执行计划查看的时候,type 就会显示 eq_ref。
  • const(结果只有一条的主键或唯一索引扫描):const 类型表示使用了主键或者唯一索引与常量值进行比较,比如 select name from product where id=1。需要说明的是 const 类型和 eq_ref 都使用了主键或唯一索引,不过这两个类型有所区别,const 是与常量进行比较,查询效率会更快,而 eq_ref 通常用于多表联查中。

extra 显示的结果,这里说几个重要的参考指标:(不懂⭐️⭐️)

  • Using filesort :当你在查询中使用了 GROUP BY 或 ORDER BY,但数据库无法利用已有的索引来完成排序时,就会采用一种称为“文件排序”的方法。这种方法通常涉及读取数据到临时区域进行排序,效率较低。
  • Using temporary:使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表,常见于排序 order by 和分组查询 group by。效率低,要避免这种问题的出现。
  • Using index:所需数据只需在索引即可全部获得,不须要再到表中取数据,也就是使用了覆盖索引,避免了回表操作,效率不错。

给你张表,发现查询速度很慢,你有那些解决方案

  • 分析查询语句: 使用EXPLAIN命令分析SQL执行计划,找出慢查询的原因,比如是否使用了全表扫描,是否存在索引未被利用的情况等,并根据相应情况对索引进行适当修改。
  • 创建或优化索引: 根据查询条件创建合适的索引,特别是经常用于WHERE子句的字段、Orderby 排序的字段、Join 连表查询的字典、 group by的字段,并且如果查询中经常涉及多个字段,考虑创建联合索引,使用联合索引要符合最左匹配原则,不然会索引失效
  • 避免索引失效: 比如不要用左模糊匹配、函数计算、表达式计算等等。
    查询优化:避免使用SELECT *,只查询真正需要的列;使用覆盖索引,即索引包含所有查询的字段;联表查询最好要以小表驱动大表,并且被驱动表的字段要有索引,当然最好通过冗余字段的设计,避免联表查询。
  • 分页优化: 针对 limit n,y 深分页的查询优化,可以把Limit查询转换成某个位置的查询:select * from tb_sku where id>20000 limit 10,该方案适用于主键自增的表,
  • 优化数据库表: 如果单表的数据超过了千万级别,考虑是否需要将大表拆分为小表,减轻单个表的查询压力。也可以将字段多的表分解成多个表,有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。
  • **使用缓存技术:**引入缓存层,如Redis,存储热点数据和频繁查询的结果,但是要考虑缓存一致性的问题,对于读请求会选择旁路缓存策略,对于写请求会选择先更新 db,再删除缓存的策略。

架构

MySQL主从复制了解吗

MySQL的主从复制依赖于bin log,也就是记录MySQL上的所有变化并以二进制形式保存在磁盘上。复制过程就是将bin log中的数据从主库传输到从库上。

这个过程一般是异步 的,也就是主库上执行事务操作的线程不会等待复制bin log的线程同步完成。
在这里插入图片描述
MySQL 集群的主从复制过程梳理成 3 个阶段:

  • 写入 Binlog: 主库写 binlog 日志,提交事务,并更新本地存储数据。
  • 同步 Binlog: 把 binlog 复制到所有从库上,每个从库把 binlog 写到暂存日志中。
  • 回放 Binlog: 回放 binlog,并更新存储引擎中的数据。

具体详细过程如下:

  • MySQL 主库在收到客户端提交事务的请求之后,会先写入 binlog,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端“操作成功”的响应。
  • 从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应。
  • 从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。

在完成主从复制之后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。

在这里插入图片描述

主从延迟都有什么处理方法?

强制走主库方案: 对于大事务或资源密集型操作,直接在主库上执行,避免从库的额外延迟。

分表和分库是什么?有什么区别?

分库是一种水平扩展数据库的技术,将数据根据一定规则划分到多个独立的数据库中。每个数据库只负责存储部分数据,实现了数据的拆分和分布式存储。分库主要是为了解决并发连接过多,单机 mysql扛不住的问题。
分表指的是将单个数据库中的表拆分成多个表,每个表只负责存储一部分数据。这种数据的垂直划分能够提高查询效率,减轻单个表的压力。分表主要是为了解决单表数据量太大,导致查询性能下降的问题。

分库与分表可以从:垂直(纵向)和 水平(横向)两种纬度进行拆分。下边我们以经典的订单业务举例,看看如何拆分。
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到