Q&A:MySQL


①基础

🌟MySQL 基础架构

MySQL 主要由下面几部分构成:

  • 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
  • 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
    • 缓存虽能够提升查询性能,但也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。
  • 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
  • 优化器: 按照 MySQL 认为最优的方案去执行。
  • 执行器: 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
  • 插件式存储引擎 : 主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。

MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。

引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。

🌟MySQL执行计划分析

执行计划 是指一条 SQL 语句在经过 MySQL 查询优化器 的优化会后,具体的执行方式。

执行计划通常用于 SQL 性能分析、优化等场景。通过 EXPLAIN 的结果,可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引可以被命中、哪些索引实际会命中、每个数据表有多少行记录被查询等信息。

EXPLAIN 适用于 SELECT, DELETE, INSERT, REPLACE, 和 UPDATE语句,我们一般分析 SELECT 查询较多。

我们这里简单来演示一下 EXPLAIN 的使用。

EXPLAIN 的输出格式如下:

mysql> EXPLAIN SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | cus_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 997572 |   100.00 | Using filesort |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

各个字段的含义如下:

列名 含义
id SELECT 查询的序列标识符
select_type SELECT 关键字对应的查询类型
table 用到的表名
partitions 匹配的分区,对于未分区的表,值为 NULL
type 表的访问方法
possible_keys 可能用到的索引
key 实际用到的索引
key_len 所选索引的长度
ref 当使用索引等值查询时,与索引作比较的列或常量
rows 预计要读取的行数
filtered 按表条件过滤后,留存的记录数的百分比
Extra 附加信息

🌟MySQL常见的存储引擎?区别是什么?

MySQL 存储引擎有 MyISAM 、InnoDB、Memory

MySQL 5.5 之前,MyISAM 是 MySQL 的默认存储引擎,MySQL 5.5 之后, InnoDB 成为 MySQL 的默认存储引擎。

  • 1.是否支持行级锁。InnoDB 支持行级别的锁粒度,MyISAM 不支持,只支持表级别的锁粒度。
  • 2.是否支持事务。MyISAM 不提供事务支持。InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别。
  • 3.是否支持外键。MyISAM 不支持外键,而 InnoDB 支持。
  • 4.是否支持数据库异常崩溃后的安全恢复。MyISAM 不支持数据库异常崩溃后的安全恢复,而 InnoDB 支持。
  • 5.是否支持 MVCC。MyISAM 不支持 MVVC,而 InnoDB 支持。
  • 6.索引实现不一样。虽然 MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是两者的实现方式不太一样。
  • 7.性能有差别。InnoDB 的性能比 MyISAM 更强大。

🌟SQL语句在MySQL中的执行过程

执行一条 SQL 查询语句,期间发生了什么?

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

查询语句执行流程

查询语句

查询语句的执行流程如下:权限校验(如果命中缓存)—>查询缓存—>分析器—>优化器—>权限校验—>执行器—>引擎

更新语句

更新语句执行流程如下:分析器—->权限校验—->执行器—>引擎—redo log(prepare 状态)—>binlog—>redo log(commit状态)

🌟InnoDB 四大特性如下

  1. 插入缓存,Insert buffer
  2. 二次写,double write
  3. 自适应哈希索引,adaptive hash index
  4. 预读,read ahead

②索引

🌟B+ 树简介

标准 B+ 树 Innodb 里的 B+ 树

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

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

🌟MySQL 为什么使用 B+ 树来作索引?

B 树和 B+ 都是通过多叉树的方式,会将树的高度变矮,所以这两个数据结构非常适合检索存于磁盘中的数据。

但是 MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:

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

🌟B+-tree优点

  1. I/O次数更少:B+-tree 的内部结点没有存储关键字,所以同样大小的磁盘页能容纳更多结点。一次性读入内存中的也就越多,相对来说IO读写次数也就降低了
  2. 查询速度更稳定:B+-tree 所有关键字数据地址都存在叶子结点上,所以每次查找的次数都相同所以查询速度要比B树更稳定;
  3. 范围查询更简便:B+-tree 所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。
  4. 全结点遍历更快:B+-tree 遍历整棵树只需要遍历所有的叶子节点即可, B-tree 需要对每一层进行遍历,这有利于数据库做全表扫描。

🌟B+-tree和B-tree的区别

  • B 树的所有节点既存放键(key) 也存放 数据(data),B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
  • B 树的叶子节点都是独立的;B+-tree 相邻的叶子节点之间是通过链表指针连起来的。
  • B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。

🌟InnoDB 和 MyISAM 实现的 B+ 树区别

InnoDB 和 MyISAM 都支持 B+ 树索引,但是它们数据的存储结构实现方式不同。不同之处在于:

  • InnoDB 存储引擎:B+ 树索引的叶子节点保存数据本身;
  • MyISAM 存储引擎:B+ 树索引的叶子节点保存数据的物理地址;
MyISAM实现的 B+ 树 InnoDB实现的 B+ 树

🌟聚集索引和二级索引的区别

对主键字段建立的索引叫做聚簇索引,对普通字段建立的索引叫做二级索引。

Innodb 根据索引类型不同,分为聚集和二级索引。他们区别在于,

  • 聚集索引的叶子节点存放的是实际数据,所有完整的用户记录都存放在聚集索引的叶子节点,

  • 二级索引的叶子节点存放的是主键值,而不是实际数据。

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

如下左图为聚集索引,右图为二级索引(name 字段设置为二级索引)

聚集索引 二级索引

在我们使用「主键索引」字段作为条件查询的时候,如果要查询的数据都在「聚簇索引」的叶子节点里,那么就会在「聚簇索引」中的 B+ 树检索到对应的叶子节点,然后直接读取要查询的数据。如下面这条语句:

// id 字段为主键索引
select * from t_user where id=1;

在我们使用「二级索引」字段作为条件查询的时候,如果要查询的数据都在「聚簇索引」的叶子节点里,那么需要检索两颗B+树:

  • 先在「二级索引」的 B+ 树找到对应的叶子节点,获取主键值;
  • 然后用上一步获取的主键值,在「聚簇索引」中的 B+ 树检索到对应的叶子节点,然后获取要查询的数据。

上面这个过程叫做回表,如下面这条语句:

// name 字段为二级索引
select * from t_user where name="林某";

在我们使用「二级索引」字段作为条件查询的时候,如果要查询的数据在「二级索引」的叶子节点,那么只需要在「二级索引」的 B+ 树找到对应的叶子节点,然后读取要查询的数据,这个过程叫做覆盖索引。如下面这条语句:

// name 字段为二级索引
select id from t_user where name="林某";

🌟聚簇索引和非聚簇索引有什么区别?

  • 聚簇索引即索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引

  • 非聚簇索引即索引结构和数据分开存放的索引,二级索引就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。

🌟非聚簇索引一定回表查询吗(覆盖索引)?

非聚簇索引不一定回表查询。当查询的数据刚好就是非聚簇索引叶子节点所保存的主键,则不需要回表。这种情况就称之为覆盖索引了。

🌟介绍一下覆盖索引

覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。

🌟唯一索引与普通索引的区别是什么?

  • 唯一索引(Unique Key) :唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。

  • 普通索引(Index)普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。

🌟简述 MySQL 常见索引类型

[深入浅出 InnoDB Index.pdf (aliyuncs.com)](https://smartkeyerror.oss-cn-shenzhen.aliyuncs.com/Phyduck/database/深入浅出 InnoDB Index.pdf)

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

前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。

全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。

主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。

普通索引:仅加速查询。

唯一索引:加速查询 + 列值唯一(可以有 NULL)。

覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。

联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。

全文索引:对文本的内容进行分词,进行搜索。目前只有 CHARVARCHARTEXT 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。

MySQL 8.x 中实现的索引新特性:

  • 隐藏索引:也称为不可见索引,不会被优化器使用,但是仍然需要维护,通常会软删除和灰度发布的场景中使用。主键不能设置为隐藏(包括显式设置或隐式设置)。
  • 降序索引:之前的版本就支持通过 desc 来指定索引为降序,但实际上创建的仍然是常规的升序索引。直到 MySQL 8.x 版本才开始真正支持降序索引。另外,在 MySQL 8.x 版本中,不再对 GROUP BY 语句进行隐式排序。
  • 函数索引:从 MySQL 8.0.13 版本开始支持在索引中使用函数或者表达式的值,也就是在索引中可以包含函数或者表达式。

🌟最左前缀匹配原则

面试官:谈谈你对mysql联合索引的认识? - 知乎 (zhihu.com)

最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 **><**)才会停止匹配。对于 >=<=BETWEENlike 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据,区分度低的字段放后面,像性别、状态这种字段区分度就很低,我们一般放后面。

比如,对(a,b,c,d)建立索引

where a = 1 and b = 2 and c > 3 and d = 4

那么,a,b,c三个字段能用到索引,而d就匹配不到。因为遇到了范围查询!

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

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;

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

  • where b=2;
  • where c=3;
  • where b=2 and c=3;

有一个比较特殊的查询条件:where a = 1 and c = 3 ,符合最左匹配吗?下面会讲解

为什么联合索引不遵循最左匹配原则就会失效?

在联合索引的情况下,数据是按照索引第一列排序,第一列数据满足时才会按照第二列排序。

也就是说,如果我们想使用联合索引中尽可能多的列,查询条件中的各个列必须是联合索引中从最左边开始连续的列。如果我们仅仅按照第二列搜索,肯定无法走索引。

🌟假设建立联合索引 (a, b, c) 如果对字段 b 和 c 查询,会用到这个联合索引吗?

多个普通字段组合在一起创建的索引就叫做联合索引

(a, b, c) 联合索引,是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。所以,b 和 c 是全局无序,局部相对有序的,这样在没有遵循最左前缀匹配原则的情况下,是无法利用到索引的。

🌟假设建立联合索引 (a, b, c) 如果对字段 a 和 c 查询,会用到这个联合索引吗?

MySQL 5.6 之前 a可以用,c不了,MySQL 5.6 开始可以用

MySQL 5.5 的话,前面 a 会走索引,在联合索引找到主键值后,开始回表,到主键索引读取数据行,Server 层从存储引擎层获取到数据行后,然后在 Server 层再比对 c 字段的值。

从 MySQL 5.6 之后,有一个索引下推功能,可以在存储引擎层进行索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,再返还给 Server 层,从而减少回表次数。

索引下推的大概原理是:截断的字段不会在 Server 层进行条件判断,而是会被下推到「存储引擎层」进行条件判断(因为 c 字段的值是在 (a, b, c) 联合索引里的),然后过滤出符合条件的数据后再返回给 Server 层。由于在引擎层就过滤掉大量的数据,无需再回表读取数据来进行判断,减少回表次数,从而提升了性能。

🌟索引下推

索引下推(Index Condition Pushdown)MySQL 5.6 版本中提供的一项索引优化功能,可以在非聚簇索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表次数。

索引下推的大概原理是:截断的字段不会在 Server 层进行条件判断,而是会被下推到「存储引擎层」进行条件判断(因为 c 字段的值是在 (a, b, c) 联合索引里的),然后过滤出符合条件的数据后再返回给 Server 层。由于在引擎层就过滤掉大量的数据,无需再回表读取数据来进行判断,减少回表次数,从而提升了性能。

给你这个SQL:

select * from employee where name like '小%' and age=28 and sex='0'; 

其中,nameage为联合索引(idx_name_age)。

如果是Mysql5.6之前,在idx_name_age索引树,找出所有名字第一个字是“小”的人,拿到它们的主键id,然后回表找出数据行,再去对比年龄和性别等其他字段。如图:

有些朋友可能觉得奇怪,idx_name_age(name,age)不是联合索引嘛?为什么选出包含“小”字后,不再顺便看下年龄age再回表呢,不是更高效嘛?所以呀,MySQL 5.6就引入了索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。因此,MySQL5.6版本之后,选出包含“小”字后,顺表过滤age=28

🌟MySQL 的索引什么情况下会失效?

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

🌟使用索引会有哪些优缺点?

优点

  • 使用索引可以大大加快数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

缺点

  • 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
  • 索引需要使用物理文件存储,也会耗费一定空间。

🌟MySQL 索引使用什么数据结构?

  • 哈希表结构

  • B+树结构

为什么 MySQL 没有使用其作为索引的数据结构呢? 主要是因为 Hash 索引不支持顺序和范围查询

🌟数据库如何设计索引,如何优化查询?

要设计一个适合 MySQL 索引的数据结构,至少满足以下要求:

  • 能在尽可能少的磁盘的 I/O 操作中完成查询工作;
  • 要能高效地查询某一个记录,也要能高效地执行范围查找;
    • 为频繁查询的字段建立索引
    • 避免为”大字段”建立索引
    • 选择区分度大的列作为索引
    • 尽量为ORDER BY 和 GROUP BY 后面的字段建立索引
    • 不要在条件中使用函数
    • 不要建立太多的索引
    • 频繁增删改的字段不要建立索引

MySql 使用索引进行查询优化

🌟优化索引的方法?

  • 前缀索引优化;
  • 覆盖索引优化;
  • 主键索引最好是自增的;
  • 防止索引失效;

③事务

🌟什么是数据库事务,MySQL 为什么会使用 InnoDB 作为默认选项

数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行

不过并不是所有的引擎都能支持事务,比如 MySQL 原生的 MyISAM 引擎就不支持事务,也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB。

🌟简述事务的四大特性

原子性Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

隔离性Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

持久性Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

🌟如何开启、提交、回滚一个事务?

# 开启一个事务
START TRANSACTION;
# 多条 SQL 语句
SQL1,SQL2...
## 提交事务
COMMIT;

### 回滚事务
rollback

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

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

🌟并行事务会引发什么问题?

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

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

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

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

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

不可重复读和幻读有什么区别?

  • 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
  • 幻读的重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。

总结:

  • 脏读:读到其他事务未提交的数据;
  • 不可重复读:前后读取的数据不一致;
  • 幻读:前后读取的记录数量不一致。

这三个现象的严重性排序如下:脏读 > 不可重复读 > 幻读

🌟数据库的事务隔离级别有哪些?各有哪些优缺点?

SQL 标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低,这四个隔离级别如下:

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

按隔离水平高低排序如下:串行化 > 可重复读 > 读提交 > 读未提交

针对不同的隔离级别,并发事务时可能发生的现象也会不同。

🌟InnoDB 是如何解决脏读、可重复读、幻读的?

解决脏读现象,就要升级到「读提交」以上的隔离级别;

解决不可重复读现象,就要升级到「可重复读」的隔离级别,

解决幻读现象不建议将隔离级别升级到「串行化」(影响性能)。

MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(update、insert、delete、select … for update等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

🌟简述 MySQL MVCC 的实现原理

MVCC 是多版本并发控制方法,即对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。

MVCC 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log

  • undo log : undo log 用于记录某行数据的多个版本的数据。
  • read view 和 隐藏字段 : 用来判断当前版本数据的可见性。

🌟MySQL 的隔离级别是基于什么实现的吗?

MySQL 的隔离级别基于MVCC 机制共同实现的。

锁可以看作是悲观控制的模式,多版本并发控制(MVCC)可以看作是乐观控制的模式。

SERIALIZABLE 隔离级别是通过锁来实现的,

READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。

不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。

④锁

🌟表级锁和行级锁了解吗?有什么区别?

  • MyISAM 仅仅支持表级锁,一锁就锁整张表,这在并发写的情况下性非常差。
  • InnoDB 不光支持表级锁,还支持行级锁,默认为行级锁。

表级锁和行级锁对比

  • 表级锁: MySQL 中锁定粒度最大的一种锁(全局锁除外),是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。不过,触发锁冲突的概率最高,高并发下效率极低。表级锁和存储引擎无关,MyISAM 和 InnoDB 引擎都支持表级锁。
  • 行级锁: MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁 ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存储引擎有关,是在存储引擎层面实现的。

🌟行级锁的使用有什么注意事项?

InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 UPDATEDELETE 语句时,如果 WHERE条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有行记录进行加上 next-key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。

那 update 语句的 where 带上索引就能避免全表记录加锁了吗?

并不是。关键还得看这条语句在执行过程种,优化器最终选择的是索引扫描,还是全表扫描,如果走了全表扫描,就会对全表的记录加锁了。

解决办法

当我们要执行 update 语句的时候,确保 where 条件中带上了索引列,并且在测试机确认该语句是否走的是索引扫描,防止因为扫描全表,而对表中的所有记录加上锁。

我们可以打开 MySQL sql_safe_updates 参数,这样可以预防 update 操作时 where 条件没有带上索引列。

如果发现即使在 where 条件中带上了列索引列,优化器走的还是全标扫描,这时我们就要使用 force index([index_name]) 可以告诉优化器使用哪个索引。

🌟select … for update加什么锁

如果 WHERE条件中字段命中唯一索引,即当查询用到索引时是行锁

如果 WHERE条件中字段没有命中唯一索引,即当查询用没用到索引时是表锁

select … for update学习

🌟InnoDB 有哪几类行锁?

InnoDB 行锁是通过对索引数据页上的记录加锁实现的,MySQL InnoDB 支持三种行锁定方式:

  • 记录锁(Record Lock) :也被称为记录锁,属于单个行记录上的锁。
  • 间隙锁(Gap Lock) :锁定一个范围,不包括记录本身。
  • 临键锁(Next-Key Lock) :Record Lock+Gap Lock,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题(MySQL 事务部分提到过)。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。

在 InnoDB 默认的隔离级别 REPEATABLE-READ 下,行锁默认使用的是 Next-Key Lock。但是,如果操作的索引是唯一索引或主键,InnoDB 会对 Next-Key Lock 进行优化,将其降级为 Record Lock,即仅锁住索引本身,而不是范围。

🌟记录锁

Record Lock 称为记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的:

  • 当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容);
  • 当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)。

举个例子,当一个事务执行了下面这条语句:

mysql > begin;
mysql > select * from test where id = 1 for update;

就是对 test 表中主键 id 为 1 的这条记录加上 X 型的记录锁,这样其他事务就无法对这条记录进行修改了。

当事务执行 commit 后,事务过程中生成的锁都会被释放。

🌟间隙锁

Gap Lock 称为间隙锁,锁定一个范围,不包括记录本身。只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。

假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。

间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻读记录而提出的

🌟临键锁

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

假设,表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录。

所以,next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。

next-key lock 是包含间隙锁+记录锁的,如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的

🌟当前读和快照读有什么区别?

快照读(一致性非锁定读)就是单纯的 SELECT 语句,但不包括下面这两类 SELECT 语句:

SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE

快照即记录的历史版本,每行记录可能存在多个历史版本(多版本技术)。

快照读的情况下,如果读取的记录正在执行 UPDATE/DELETE 操作,读取操作不会因此去等待记录上 X 锁的释放,而是会去读取行的一个快照。

只有在事务隔离级别 RC(读取已提交) 和 RR(可重读)下,InnoDB 才会使用一致性非锁定读:

  • 在 RC 级别下,对于快照数据,一致性非锁定读总是读取被锁定行的最新一份快照数据。
  • 在 RR 级别下,对于快照数据,一致性非锁定读总是读取本事务开始时的行数据版本。

快照读比较适合对于数据一致性要求不是特别高且追求极致性能的业务场景。

当前读 (一致性锁定读)就是给行记录加 X 锁或 S 锁。

当前读的一些常见 SQL 语句类型如下:

# 对读的记录加一个X锁
SELECT...FOR UPDATE
# 对读的记录加一个S锁
SELECT...LOCK IN SHARE MODE
# 对修改的记录加一个X锁
INSERT...
UPDATE...
DELETE...

🌟共享锁和排他锁呢?

不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:

  • 共享锁(S 锁) :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
  • 排他锁(X 锁) :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。

排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。

共享锁(S锁)满足读读共享,读写互斥。独占锁(X锁)满足写写互斥、读写互斥。

普通的 select 语句是不会对记录加锁的,因为它属于快照读(由于 MVCC 的存在)。如果要在查询时对记录加行锁,可以使用下面这两个方式,这种查询会加锁的语句称为锁定读

//对读取的记录加共享锁
select ... lock in share mode;

//对读取的记录加独占锁
select ... for update;

上面这两条语句必须在一个事务中,因为当事务提交了,锁就会被释放,所以在使用这两条语句的时候,要加上 begin、start transaction 或者 set autocommit = 0。

🌟意向锁有什么作用?

如果需要用到表锁的话,如何判断表中的记录没有行锁呢,一行一行遍历肯定是不行,性能太差。我们需要用到一个叫做意向锁来快速判断是否可以对某个表使用表锁

意向锁是表级锁,共有两种:

  • 在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁-IS 锁」;
  • 在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁-IX 锁」;

意向锁是有数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享/排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。

也就是,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。

而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:

//先在表上加上意向共享锁,然后对读取的记录加共享锁
select ... lock in share mode;

//先表上加上意向独占锁,然后对读取的记录加独占锁
select ... for update;

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁和独占表锁发生冲突。

🌟为什么会发生死锁,以及怎么避免死锁

两个事务即使生成的间隙锁的范围是一样的,也不会发生冲突,因为间隙锁目的是为了防止其他事务插入数据,因此间隙锁与间隙锁之间是相互兼容的。

在执行插入语句时,如果插入的记录在其他事务持有间隙锁范围内,插入语句就会被阻塞,因为插入语句在碰到间隙锁时,会生成一个插入意向锁,然后插入意向锁和间隙锁之间是互斥的关系。

如果两个事务分别向对方持有的间隙锁范围内插入一条记录,而插入操作为了获取到插入意向锁,都在等待对方事务的间隙锁释放,于是就造成了循环等待,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

如何避免死锁?

死锁的四个必要条件:互斥、占有且等待、不可强占用、循环等待。只要系统发生死锁,这些条件必然成立,但是只要破坏任意一个条件就死锁就不会成立。

在数据库层面,有两种策略通过「打破循环等待条件」来解除死锁状态:

  • 设置事务等待锁的超时时间。当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_lock_wait_timeout 是用来设置超时时间的,默认值时 50 秒。

    当发生超时后,就出现下面这个提示:

  • 开启主动死锁检测。主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑,默认就开启。

    当检测到死锁后,就会出现下面这个提示:

上面这个两种策略是「当有死锁发生时」的避免方式。

⑤日志

  • 二进制日志 binlog(归档日志)

  • 事务日志 redo log(重做日志)

  • undo log(回滚日志)

redo log

redo log 什么时候刷盘?

redo log 文件写满了怎么办?

简述 MySQL 三种日志的使用场景

简述 undo log 和 redo log 的作用

MySQL 日志:undo log、redo log、binlog 有什么用?

MySQL三大日志(binlog、redo log和undo log)详解

⑥其它

🌟MySQL 中 varchar 和 char 的区别是什么?

  • 区别一,定长和变长

char 表示定长,长度固定。varchar表示变长,即长度可变。

char如果插入的长度小于定义长度时,则用空格填充;varchar小于定义长度时,还是按实际长度存储,插入多长就存多长。

因为其长度固定,char的存取速度还是要比varchar要快得多,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以会占据多余的空间,可谓是以空间换取时间效率。varchar则刚好相反,以时间换空间。

  • 区别之二,存储的容量不同

对 char 来说,最多能存放的字符个数 255,和编码无关。
而 varchar 呢,最多能存放 65532 个字符。varchar的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是 65,532字节。

🌟MySQL 中 join 与 left join 的区别是什么?

  • join等价于inner join内连接抄,返回两个表中都有的符合条件的行。
  • left join左连接,返回左表中所有的行及右表中符合条件的行。
  • right join右连接,返回右表中所有的行及左表中符合条件的行。
  • full join全连接,返回左表中所有的行及右表中所有的行,并按条件连接。

🌟数据库反范式设计会出现什么问题?

范式是普适的规则,满足大多数的业务场景的需求。对于一些特殊的业务场景,范式设计的表,无法满足性能的需求。此时,就需要根据业务场景,在范式的基础之上进行灵活设计,也就是反范式设计。

反范式设计就是用空间来换取时间,提高业务场景的响应时间,减少多表关联。

🌟数据库设计的范式是什么?

概念解释: 三大范式其实就是数据库建表的规范。

  • 第一范式(1NF):无重复的列(原子性)。要求一张表中的数据每一列都是不可分割的原子项数据
  • 第二范式(2NF):属性完全依赖于主键。消除部分依赖,要求一张表中的每一列都完全依赖于主键(针对于组合主键),也就是不会出现某一列只和部分主键相关
  • 第三范式(3NF):属性不依赖于其它非主属性。消除传递依赖,要求一张表中的每一列都和主键是直接依赖的,不是间接依赖

🌟简述数据库读写分离?

读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。 这样的话,就能够小幅提升写性能,大幅提升读性能。

一般情况下,我们都会选择一主多从,也就是一台主数据库负责写,其他的从数据库负责读。主库和从库之间会进行数据同步,以保证从库中数据的准确性。这样的架构实现起来比较简单,并且也符合系统的写少读多的特点。

读写分离会带来什么问题?如何解决?

主从同步延迟

1.强制将读请求路由到主库处理。

2.延迟读取。

🌟如何实现读写分离?

  • 部署多台数据库,选择其中的一台作为主数据库,其他的一台或者多台作为从数据库。

  • 保证主数据库和从数据库之间的数据是实时同步的,这个过程也就是我们常说的主从复制

  • 系统将写请求交给主数据库处理,读请求交给从数据库处理。

落实到项目本身的话,常用的方式有两种:

1.代理方式

我们可以在应用和数据中间加了一个代理层。应用程序所有的数据请求都交给代理层处理,代理层负责分离读写请求,将它们路由到对应的数据库中。

2.组件方式

通过引入第三方组件来帮助我们读写请求。

如果你要采用这种方式的话,推荐使用 sharding-jdbc

🌟主从复制(主从同步)原理是什么?

MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据库中数据的所有变化(数据库执行的所有 DDL 和 DML 语句)。因此,我们根据主库的 MySQL binlog 日志就能够将主库的数据同步到从库中。

  • 主库将数据库中数据的变化写入到 binlog
  • 从库连接主库
  • 从库会创建一个 I/O 线程向主库请求更新的 binlog
  • 主库会创建一个 binlog dump 线程来发送 binlog ,从库中的 I/O 线程负责接收
  • 从库的 I/O 线程将接收的 binlog 写入到 relay log 中。
  • 从库的 SQL 线程读取 relay log 同步数据本地(也就是再执行一遍 SQL )。

🌟简述数据库中什么情况下进行分库,什么情况下进行分表?

  • 单表的数据达到千万级别以上,数据库读写速度比较缓慢。
  • 数据库中的数据占用的空间越来越大,备份时间越来越长。
  • 应用的并发量太大。

🌟MySQL 有什么调优的方式?

  • 读写分离

  • 分库分表

🌟MySQL 如何存储 IP 地址?

可以将 IP 地址转换成整形数据存储,性能更好,占用空间也更小。

MySQL 提供了两个方法来处理 ip 地址

  • INET_ATON() : 把 ip 转为无符号整型 (4-8 位)
  • INET_NTOA() :把整型的 ip 转为地址

插入数据前,先用 INET_ATON() 把 ip 地址转为整型,显示数据时,使用 INET_NTOA() 把整型的 ip 地址转为地址显示即可。

🌟什么是 SQL 注入攻击?如何防止这类攻击?

SQL注入是通过将恶意的SQL语句插入到Web应用的输入参数中,欺骗服务器执行恶意的SQL命令的攻击。

主要防御:

  • 选项 1:使用预准备语句(使用参数化查询)
  • 选项 2:使用正确构造的存储过程
  • 选项 3:允许列表输入验证
  • 选项 4:转义所有用户提供的输入

附加防御:

  • 另外:强制实施最低特权
  • 另:执行允许列表输入验证作为辅助防御

❤️Sponsor

您的支持是我不断前进的动力,如果您感觉本文对您有所帮助的话,可以考虑打赏一下本文,用以维持本博客的运营费用,拒绝白嫖,从你我做起!🥰🥰🥰

支付宝 微信

文章作者: 简简
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 简简 !
评论
填上邮箱会收到评论回复提醒哦!!!
 上一篇
Web服务器配置与管理 Web服务器配置与管理
一、实验环境 操作系统:Windows系统、阿里云Windows Server 2016 实验设备:PC机 二、 实验内容任务 1:Web服务器的安装任务 2:创建Web站点并实现外网访问任务 3:配置客户端访问Web站点任务
2020-03-12
下一篇 
计网 & OS 计网 & OS
计网①网络分层 ②TCP🌟简述 TCP 的报文头部结构 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。 确认应答号:指
2019-12-17
  目录