八股文MYSQL

SQL基础

NOSQL和SQL的区别?

SQL数据库 (Structured Query Language)指关系型数据库 - 主要代表:SQL Server,Oracle,MySQL(开源)

关系型数据库存储结构化数据 。这些数据逻辑上以行列二维表的形式存在每一列代表数据的一种属性,每一行代表一个数据实体。

NoSQL非关系型数据库 (Not Only SQL) ,主要代表:MongoDB,Redis。NoSQL 数据库逻辑上提供了不同于二维表的存储方式,存储方式可以是JSON文档、哈希表或者其他方式。

选择 SQL vs NoSQL,考虑以下因素。

ACID vs BASE

关系型数据库支持 ACID 即原子性,一致性,隔离性和持续性。 相对而言,NoSQL 采用更宽松的模型 BASE , 即基本可用,软状态和最终一致性。

  • Basically Available(基本可用)系统在任何时候都保持基本可用,即使在部分组件失败的情况下也不例外。这意味着系统可能会拒绝一些请求,但仍然能够处理大部分请求。

  • Soft state(软状态) : 系统中的数据不需要始终保持一致,可以容忍一段时间内的数据不一致。 这意味着系统的状态可以在多个请求之间发生变化,不需要严格的实时一致性。

  • Eventual consistency(最终一致性) : 系统保证在没有后续更新的情况下,数据最终会达到一致的状态。这意味着在分布式系统中,数据可能暂时不一致,但最终会趋于一致。

从实用的角度出发,我们需要考虑对于面对的应用场景,ACID 是否是必须的。比如银行应用就必须保证 ACID,否则一笔钱可能被使用两次;又比如社交软件不必保证 ACID,因为一条状态的更新对于所有用户读取先后时间有数秒不同并不影响使用。

对于需要保证 ACID 的应用,我们可以优先考虑 SQL。反之则可以优先考虑 NoSQL。

扩展性对比

NoSQL数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力。比如 redis 自带主从复制模式、哨兵模式、切片集群模式。

相反**关系型数据库的数据之间存在关联性,水平扩展较难 ,**需要解决跨服务器 JOIN,分布式事务等问题。

数据库三大范式是什么?

三大范式是MySQL数据库 设计表结构时候所遵循的规范和指导方法 ,为了减少冗余建立结构合理的数据库。 三大范式之间是具有依赖关系

第一范式:要求数据库表中的每个列必须是原子性 的,不能再分解为更小的数据项。

第二范式:要求表中的非主键列必须完全依赖于主键,而不是依赖于主键的一部分。

第三范式:要么非主键列之间不能存在以来传递的关系,即一个非主键值不能依赖于另一个非主键值

SQL查询语句的执行顺序是怎么样的?

MySQL 怎么连表查询?

1. 内连接 (INNER JOIN)

内连接返回两个表中有匹配关系的行 ,多用于等值查询

显式内连接 ON 隐式内连接WHERE

示例:

复制代码
SELECT employees.name, departments.name
FROM employees
INNER JOIN departments
ON employees.department_id = departments.id;

这个查询返回每个员工及其所在的部门名称。

2. 左外连接 (LEFT JOIN)

左外连接返回左表中的所有行, 即使在右表中没有匹配的行。未匹配的右表列会包含NULL。示例:

复制代码
SELECT employees.name, departments.name
FROM employees
LEFT JOIN departments
ON employees.department_id = departments.id;

这个查询返回所有员工及其部门名称,包括那些没有分配部门的员工。

3. 右外连接 (RIGHT JOIN)

右外连接返回右表中的所有行, 即使左表中没有匹配的行。未匹配的左表列会包含NULL。示例:

复制代码
SELECT employees.name, departments.name
FROM employees
RIGHT JOIN departments
ON employees.department_id = departments.id;

这个查询返回所有部门及其员工,包括那些没有分配员工的部门。

4. 全外连接 (FULL JOIN)

全外连接返回两个表中所有行,包括非匹配行,

在MySQL中,FULL JOIN 需要使用 UNION 来实现, 因为 MySQL 不直接支持 FULL JOIN。示例:

复制代码
SELECT employees.name, departments.name
FROM employees
LEFT JOIN departments
ON employees.department_id = departments.id

UNION

SELECT employees.name, departments.name
FROM employees
RIGHT JOIN departments
ON employees.department_id = departments.id;

这个查询返回所有员工和所有部门,包括没有匹配行的记录。

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

方式一:使用UNIQUE约束

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

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

如果尝试插入重复的email,MySQL会返回错误。

方式二:使用INSERT ... ON DUPLICATE KEY UPDATE

这种语句允许在插入记录时处理重复键的情况。如果插入的记录与现有记录冲突,可以选择更新现有记录

复制代码
INSERT INTO users (email, name) 
VALUES ('[email protected]', 'John Doe')
ON DUPLICATE KEY UPDATE name = VALUES(name);

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

复制代码
INSERT IGNORE INTO users (email, name) 
VALUES ('[email protected]', 'John Doe');

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

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

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

CHAR 和 VARCHAR有什么区别?

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

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

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

  • TEXT :65,535 bytes ~64kb
  • MEDIUMTEXT :16,777,215 bytes ~16Mb
  • LONGTEXT :4,294,967,295 bytes ~4Gb

说一下外键约束?

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

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

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

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

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

MySQL的关键字in和exist?

EXSIST查到匹配行后就停下,IN会扫描全表 ,但IN能正确处理NULL值

mysql中的一些基本函数,你知道哪些?

存储引擎

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

  • 连接器:建立连接,管理连接、校验用户身份;

  • **查询缓存:查询语句如果命中查询缓存则直接返回,**否则继续往下执行。MySQL 8.0 已删除该模块;

  • 解析 SQL,通过解析器对 SQL 查询语句进行 词法分析、语法分析 ,然后构建语法树,方便后续模块读取表名、字段、语句类型;

  • 执行 SQL:执行 SQL 共有三个阶段:

    • 预处理 阶段:检查表或字段是否存在 ;将 select * 中的 * 符号扩展为表上的所有列。

    • 优化 阶段:基于查询成本的考虑, 选择查询成本最小的执行计划

    • 执行 阶段**:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;**

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

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

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

  • 事务支持:InnoDB引擎提供了对事务的支持, 可以进行ACID(原子性、一致性、隔离性、持久性)属性的操作。Myisam存储引擎是不支持事务的。
  • 并发性能:InnoDB引擎采用了行级锁定的机制, 可以提供更好的并发性能,Myisam存储引擎只支持表锁,锁的粒度比较大。
  • 崩溃恢复:InnoDB引引擎通过 redolog 日志实现了崩溃恢复, 可以在数据库发生异常情况(如断电)时,通过日志文件进行恢复,**保证数据的持久性和一致性。**Myisam是不支持崩溃恢复的。

说一下mysql的innodb与MyISAM的区别?

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

索引

MySQL可以按照四个角度来分类索引。

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

接下来,按照这些角度来说说各类索引的特点。

数据结构分类

从数据结构的角度来看,MySQL 常见索引有 B+Tree 索引、HASH 索引、Full-Text 索引

每一种存储引擎支持的索引类型不一定相同,我在表中总结了 MySQL 常见的存储引擎 InnoDB、MyISAM 和 Memory 分别支持的索引类型。

InnoDB 是在 MySQL 5.5 之后成为默认的 MySQL 存储引擎,B+Tree 索引类型也是 MySQL 存储引擎采用最多的索引类型。

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

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

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

物理存储分类

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

这两个区别在前面也提到了:

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

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

字段特性分类

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

  • 主键索引

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

在创建表时,创建主键索引的方式如下:

复制代码
CREATE TABLE table_name  (
  ....
  PRIMARY KEY (index_column_1) USING BTREE
);
  • 唯一索引

唯一索引建立在UNIQUE 字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值。

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

复制代码
CREATE TABLE table_name  (
  ....
  UNIQUE KEY(index_column_1,index_column_2,...) 
);

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

复制代码
CREATE UNIQUE INDEX index_name
ON table_name(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));

字段个数分类

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

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

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

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

复制代码
CREATE INDEX index_product_no_name ON product(product_no, name);

可以看到,联合索引的非叶子节点用两个字段的值作为 B+Tree 的 key 值。当在联合索引查询数据时,先按 product_no 字段比较,在 product_no 相同的情况下再按 name 字段比较。

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

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

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

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

MySQL聚簇索引和非聚簇索引的区别是什么?

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

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

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

MySQL主键是聚簇索引吗?

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

什么字段适合当做主键?

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

性别字段能加索引么?为啥?

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

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

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

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

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

用的是自增 id。

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

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

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

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

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

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

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

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

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

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

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

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

主键索引的 B+Tree 如图所示:

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

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

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

因此,数据页中有一个页目录,起到记录的索引作用。

页目录与记录的关系如下图:

页目录创建的过程如下:

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

从图可以看到,页目录就是由多个槽组成的,槽相当于分组记录的索引。然后,因为记录是按照「主键值」从小到大排序的,所以我们通过槽查找记录时,可以使用二分法快速定位要查询的记录在哪个槽(哪个记录分组),定位到槽后,再遍历槽内的所有记录,找到对应的记录,无需从最小记录开始遍历整个页中的记录链表。

说说B+树 B树 和Hash索引?

B+树的特性:

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

Innodb 使用的 B+ 树有一些特别的点,比如:

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

Hash索引是采用一定的Hash算法,把键值换成新的hash映射到槽位上,

冲突则用链表处理,只能用于等值查询比较 = in 不能用范围查询 不能索引 查询效率高

为什么 MysSQL 不用 跳表?

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

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

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

**区分度就是某个字段 column 不同值的个数「除以」表的总行数,**计算公式如下:

比如,性别的区分度就很小,不适合建立索引或不适合排在联合索引列的靠前的位置,而 UUID 这类字段就比较适合做索引或排在联合索引列的靠前的位置。

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

索引失效有哪些?

  1. 仅有尾部模糊匹配时候索引才生效
  2. 对索引列使用函数或者表达式计算时候,索引会失效
  3. 在遇到字符串和数字比较时会自动把字符串转化为数字,再进行比较,如果字符串是索引列的话,那么索引列会发生隐式转化 CAST函数实现,相当于对索引列使用了函数 索引失效
  4. 联合索引要满足最左前缀法则 索引才生效
  5. WHERE子句中 or前是索引 or后不是索引 则失效
  6. 范围查询出现><则失效

索引的优缺点?

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

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

怎么进行索引优化?

1.前缀索引 2.覆盖索引 3.自增 4.防止失效

常见优化索引的方法:

  • **前缀索引优化:使用前缀索引是为了减小索引字段大小,**可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。
  • **覆盖索引优化:覆盖索引是指 SQL 中 query 的所有字段,**在索引 B+Tree 的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作。
  • 主键索引最好是自增的
    • 如果我们使用自增主键,那么每次插入的新数据就会按顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。
    • 如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面, 我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。
  • 防止索引失效:
    • 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
    • 当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
    • 在遇到字符串和数字比较时会自动把字符串转化为数字,再进行比较,如果字符串是索引列的话,那么索引列会发生隐式转化 CAST函数实现,相当于对索引列使用了函数 索引失效
    • 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
    • 在 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)事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 持久性 是通过**redo log (重做日志)**来保证的;
  • 原子性 是通过 **undo log(回滚日志)**来保证的;
  • 隔离性 是通过 MVCC(多版本并发控制) 或锁机制来保证的;
  • 一致性 则是通过持久性+原子性+隔离性来保证;

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

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

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

脏读

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

不可重复读

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

幻读

在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。

查询时候没有 要插入的时候有了 然后在查询的时候又没有。

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

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

事务的隔离级别有哪些?

  • 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
  • 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
  • 可重复读(repeatable read) ,指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别
  • 串行化(serializable);会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

也就是说:

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

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

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

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

  • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;

  • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View(快照) 来实现的,它们的区别在于创建 Read View 的时机不同,「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View(就是看看别的事务提交给数据库带来的变化)

  • 「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View(事务中不在关别的事务提交带来的变化)MVCC实现

  • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;

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

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

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

可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读

我举例一个可重复读隔离级别发生幻读现象的场景。以这张表作为例子:

事务 A 执行查询 id = 5 的记录,此时表中是没有该记录的,所以查询不出来。

复制代码
# 事务 A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t_stu where id = 5;
Empty set (0.01 sec)

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

复制代码
# 事务 B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_stu values(5, '小美', 18);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

此时,事务 A 更新 id = 5 这条记录,对没错,事务 A 看不到 id = 5 这条记录,但是他去更新了这条记录,这场景确实很违和,然后再次查询 id = 5 的记录,事务 A 就能看到事务 B 插入的纪录了,幻读就是发生在这种违和的场景

复制代码
# 事务 A
mysql> update t_stu set name = '小林coding' where id = 5;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t_stu where id = 5;
+----+--------------+------+
| id | name         | age  |
+----+--------------+------+
|  5 | 小林coding   |   18 |
+----+--------------+------+
1 row in set (0.00 sec)

整个发生幻读的时序图如下:

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

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

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

尽量在开启事务之后,马上执行 select ... for update 这类锁定读的语句,因为它会对记录加 临建所,从而避免其他事务插入一条新记录,就避免了幻读的问题。

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

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

介绍MVCC实现原理

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

  • 「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View;
  • 「可重复读」隔离级别是在事务开启t时,生成一个 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 (opens new window)中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值,sql 越多,所需要保存的回滚数据就越多。
  • **执行时间长,容易造成主从延迟,主库上必须等事务执行完成才会写入binlog,再传给备库。**所以,如果一个主库上的语句执行10分钟,那这个事务很可能就会导致从库延迟10分钟

讲一下mysql里有哪些锁?

在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行锁三类。

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

  • 表级锁:MySQL 里面表级别的锁有这几种:

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

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

    • 共享锁 shared_read shared_write 独占锁exclude

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

    • IS意向共享 select lock in share mode

    • IX意向排他 insert/update/delete/select for update

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

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

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

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

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

表锁的作用:

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

行锁的作用:

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

如果2个范围不是主键或索引?还会阻塞吗?

2个范围查询的字段不是索引的话,那就代表 update 没有用到索引,这时候触发了全表扫描,全部索引都会加行级锁,会被升级为表锁

日志

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

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

讲一下binlog

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

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

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 操作。

有了undolog为啥还需要redolog呢?

因为执行一条更新语句时候是会先在Buffer Pool 中将数据页更新为脏页的,Buffer Pool 是基于内存的,而内存总是不可靠,万一断电重启,还没来得及落盘的脏页数据就会丢失。

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

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

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

过程如下图:

redo log 是物理日志,记录了某个数据页做了什么修改当执行一个事务就会产生这样的一条或者多条物理日志。

在事务提交时,只要先将 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怎么保证持久性的?

  • Write-ahead logging(WAL ):在事务提交之前,将事务所做的修改操作记录到redo log中,然后再将数据写入磁盘。这样即使在数据写入磁盘之前发生了宕机,系统可以通过redo log中的记录来恢复数据。
  • Redo log的顺序写入:redo log采用追加写入的方式,将redo日志记录追加到文件末尾, 而不是随机写入。这样可以减少磁盘的随机I/O操作,提高写入性能。

binlog 两阶段提交过程是怎么样的?

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

在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的情况下,MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log,维持这两个日志的一致性

从图中可看出,事务的提交过程有两个阶段,就是将 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。

能不能只用binlog不用relo log?

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

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. 至此,一条更新语句执行完成。

RedoLog是在内存里吗?

事务执行过程中,生成的 redolog 会在 redolog buffer 中,也就是在内存中,等事务提交的时候,会把 redolog 写入磁盘。

# 为什么要写RedoLog,而不是直接写到B+树里面?

因为 redolog 写入磁盘是顺序写 ,而 b+树里数据页写入磁盘是随机写,顺序写的性能会比随机写好,这样可以提升事务提交的效率。

最重要的是redolog具备故障恢复的能力,Redo Log 记录的是物理级别的修改,包括页的修改,如插入、更新、删除操作在磁盘上的物理位置和修改内容。例如,当执行一个更新操作时,Redo Log 会记录修改的数据页的地址和更新后的数据,而不是 SQL 语句本身。

在数据页实际更新之前,先将修改操作写入 Redo Log。当数据库重启时,会进行恢复操作。首先,根据 Redo Log 检查哪些事务已经提交但数据页尚未完全写入磁盘。然后,使用 Redo Log 中的记录对这些事务进行重做(Redo)操作,将未完成的数据页修改完成,确保事务的修改生效。

MySQL主从复制了解吗

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

这个过程一般是异步 的,也就是主库上执行事务操作的线程不会等待复制 binlog 的线程同步完成。

MySQL 集群的主从复制过程梳理成 3 个阶段:

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

具体详细过程如下:

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

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

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

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

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 :主表扫描甚至可能会通过文件排序,效率是很低的,所以要避免这种问题的出现。
  • Using temporary:使了用临时表保存中间结果,多个字段部分有序,整体无序, 效率低,要避免这种问题的出现。创建了一个临时表(temporary table)来存储中间结果
  • 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,再删除缓存的策略。
相关推荐
leing1232 分钟前
c++项目-KV存储-模仿redis实现kv键值对存储的基本功能。
数据库·redis·缓存
兩尛29 分钟前
Spring Boot02(数据库、Redis)02---java八股
java·数据库·spring boot
冬瓜~33 分钟前
Ubuntu实时读取音乐软件的音频流
数据库·ubuntu·音视频·portaudio
风继续吹..1 小时前
uniapp整合SQLite(Android)
数据库·sqlite·uni-app
阿 才1 小时前
Sqlite3数据库
jvm·数据库·sqlite
Hadoop_Liang1 小时前
openEuler24.03 LTS下安装Hive3
linux·hive·hadoop·mysql·安装·openeuler
W_X_995156812 小时前
MATH4068 R code - raw code
数据库
ToreanonyTang2 小时前
StarRocks中优雅处理JSON与列表字段的初步示例
数据库·mysql·json
夜晚中的人海2 小时前
【C语言】字符串函数详解
android·c语言·数据库
RamendeusStudio2 小时前
4.1-1 IS-NET-Pro视频转图片的插件
数据库·人工智能·gpt·音视频·stablediffusion·controlnet