0


JAVA工程师面试专题-《Mysql》篇

一、基础

1、mysql可以使用多少列创建索引?

16

2、mysql常用的存储引擎有哪些

存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。常用的存储引擎有以下:

  • Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
  • MyISAM****引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。
  • MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高

3、MySQL 存储引擎,两者区别

MySQL 5.5.5 之前,MyISAM 是 MySQL 的默认存储引擎。5.5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。

MyISAM和InnoDB 区别:

  • 事务支持 MyISAM 不提供事务支持。InnoDB 提供事务支持
  • 表锁差异 MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
  • 索引结构 二者数据结构存储内容不同:主索引的区别,InnoDB的数据文件本身就是索引文件。而MyISAM的索引和数据是分开的。
  • 外键****支持 MyISAM 不支持外键,而 InnoDB 支持外键。
  • 容灾恢复 是否支持数据库异常崩溃后的安全恢复:MyISAM 不支持,而 InnoDB 支持。

4、mysql默认的隔离级别

MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重复读)

5、数据库三范式

1NF:每个列不可再拆分

2NF:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分

3NF:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键

另一个版本:

第一****范式:列的原子性,即每一列(每一个属性,字段)都不可分割。   举例:销售成本=成本的单价*销售的数量,所以这里就不可以以销售成本作为字段。 第二范式:非主属性必须完全依赖于主属性,不能存在只依赖于主属性的一部分属性。   主键是唯一的,用来确定每一行数据的。学生信息表由,学号,姓名,性别,年龄组成,这里不能以姓名作为主键,因为可能存在同名,不是唯一的,学号是唯一的,所以选择作为主键。   “张三”的学号对应了自己的姓名,年龄,性别,这里年龄性别,不能存储别人的信息,不能只有姓名,年龄依赖于学号,必须所有的信息都依赖于学号。 第三范式:消除依赖的传递。   这里可以理解为消除冗余,这里以学生信息表(同上),院系表(主键系编号,属性:系名字,系主任),如果把院系表的系编号,系名字,系主任都存储到“张三”的信息中,这里就会发现数据冗余,可能许多学生的院系信息都是相同的, 我们只需要将系编号这个字段存储到学生信息表即可。通过系编号即可查询院系表对应信息,第三范式消除了依赖的传递。

6、drop、delete 与 truncate 区别?

  • drop(丢弃数据): drop table 表名 ,直接将表都删除掉,在删除表的时候使用。
  • truncate (清空数据) : truncate table 表名 ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
  • delete(删除数据) : delete from 表名 where 列名=值,删除某一行的数据,如果不加 where 子句和truncate table 表名作用类似。

7、IN与EXISTS的区别

exists和in都用于两个表的连接查询中,最好遵循小表驱动大表的原则。

exists适合B表比A表数据大的情况,in适合A表比B表数据大的情况

(SELECT * FROM A WHERE id IN (SELECT id FROM B))

二、索引

1、索引及索引底层数据结构

索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。

  1. Hash 哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。mysql不采用这种数据结构,主要是因为 Hash 索引不支持顺序和范围查询。
  2. B 树& B+树

B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。

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

2、二叉树、B树和B+树有什么区别?

认识各种树

二叉树,每个节点支持两个分支的树结构,相比于单向链表,多了一个分支。 二叉查找树,在二叉树的基础上增加了一个规则,左子树的所有节点的值都小于它的根节点,右子树的所有子节点都大于它的根节点。

二叉查找树会出现斜树问题,导致时间复杂度增加,因此又引入了一种平衡二叉树,它具有二叉查找树的所有特点,同时增加了一个规则:”它的左右两个子树的高度差的绝对值不超过 1“。平衡二叉树会采用左旋、右旋的方式来实现平衡

而 B 树是一种多路平衡查找树,它满足平衡二叉树的规则,但是它可以有多个子树,子树的数量取决于关键字的数量,比如这个图中根节点有两个关键字 3 和 5,那么它能够拥有的子路数量=关键字数+1。 因此从这个特征来看,在存储同样数据量的情况下,平衡二叉树的高度要大于 B 树

B+树,其实是在 B 树的基础上做的增强,最大的区别有两个:

  • B 树的数据存储在每个节点上,而 B+树中的数据是存储在叶子节点,并且通过链表的方式把叶子节点中的数据进行连接。
  • B+树的子树数量等于关键字数

这个是 B 树的存储结构,从 B 树上可以看到每个节点会存储数据。

这个是 B+树,B+树的所有数据是存储在叶子节点,并且叶子节点的数据是用双向链表关联的。

B树和B+树对比

B 树和 B+树,一般都是应用在文件系统和数据库系统中,用来减少磁盘 IO 带来的性能损耗。以 Mysql 中的 InnoDB 为例,当我们通过 select 语句去查询一条数据时,InnoDB 需要从磁盘上去读取数据,这个过程会涉及到磁盘 IO 以及磁盘的随机 IO(如图所示)我们知道磁盘 IO 的性能是特别低的,特别是随机磁盘 IO。因为,磁盘 IO 的工作原理是,首先系统会把数据逻辑地址传给磁盘,磁盘控制电路按照寻址逻辑把逻辑地址翻译成物理地址,也就是确定要读取的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要把磁头放在这个扇区的上面,为了实现这一个点,磁盘会不断旋转,把目标扇区旋转到磁头下面,使得磁头找到对应的磁道,这里涉及到寻道事件以及旋转时间。

很明显,磁盘 IO 这个过程的性能开销是非常大的,特别是查询的数据量比较多的情况下。所以在 InnoDB 中,干脆对存储在磁盘块上的数据建立一个索引,然后把索引数据以及索引列对应的磁盘地址,以 B+树的方式来存储

为什么用 B 树或者 B+树来做索引结构?

为什么用 B 树或者 B+树来做索引结构?原因是 AVL 树的高度要比 B 树的高度要高,而高度就意味着磁盘 IO 的数量。所以为了减少磁盘 IO 的次数,文件系统或者数据库才会采用 B 树或者 B+树。

B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。B+ 树的优点在于:

  • IO****次数更少:由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
  • 遍历更加方便:B+树的叶子结点都是相链的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

但是B树****也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

3、关于索引的底层实现,为什么选择 B+Tree 而不是红黑树?

我主要从两个点来回答

  1. 第一点,对于一个数据库来说,存储的数据量会比较多,导致索引也很大 因此需要将索引存储 在磁盘,但是磁盘的 IO 操作又非常耗时,所以提高索引效率的关键在于减少磁盘 IO 的次 数。 举个例子 对于 31 个节点的树来说 ,一个 5 阶 B+Tree 的高度是 3 一个红黑树的 最小高度是 5,树的高度基本决定了磁盘的 IO 次数 ,所以使用 B+Tree 性能要高 很多
  2. 第二点,B+Tree 有个特点是相邻的数据在物理上也是相邻的,因为 B+Tree 的 node 的大小设为一个页,而一个节点上存有多个相邻的关键字和分支信息,每个节点只需要一次 IO就能完全载入,相当于一次 IO 载入了多个相邻的关键字和分支,而红黑树不具有这个特性,红黑树中大小相邻的数据,在物理结构上可能距离相差很大。由于程序的局部性原理,如果我们在索引中采用了预加载的技术,每次磁盘访问的时候除了将访问到的页加载到磁盘,我们还可以基于局部性原理加载,几页相邻的数据到内存中,而这个加载是不需要消耗多余磁盘 IO 时间的。因此 基于局部性原理,以及 B+Tree 存储结构物理上的特性,所以 B+Tree 的索引性能比红黑树要好很多

简洁答案:

  • 相比与二叉树,层级更少,搜索效率高
  • 对于B树,叶子节点和非叶子节点都会存储数据,这样就导致一页中存储的键值、指针跟着减少,同样保存大量数据,就需要增加树的高度,导致性能降低
  • 相比hash索引,B+树支持范围匹配及排序操作

4、索引算法有哪些

  • BTree****算法

BTree是最常用的mysql数据库索引算法,也是mysql默认的算法。因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符,只要它的查询条件是一个不以通配符开头的常量, 例如:

1 ‐‐ 只要它的查询条件是一个不以通配符开头的常量 select * from user where name like'jack%'; 2 ‐‐ 如果一通配符开头,或者没有使用常量,则不会使用索引,例如: select * from user where name like '%jack'; 
  • Hash算法

Hash索引只能用于对等比较,例如=,<=>(相当于=)操作符。由于是一次定位数据,不像BTree索引需要从根节点到枝节点,最后才能访问到页节点这样多次IO访问,所以检索效率远高于BTree索引

5、InnoDB一棵B+树的查找流程

  1. 在B+树中叶子节点存放数据,非叶子节点存放键值+指针。InnoDB存储引擎的最小存储单元是页,页可以用于存放数据也可以用于存放键值+指针
  2. 页内的记录是有序的,所以可以使用二分查找在页内到下一层的目标页面的指针。从根页开始,首先通过非叶子节点的二分查找法,确定数据在下一层哪个页之后,一层一层往下找,一直到叶子节点,进而在叶子节点(数据页)中查找到需要的数据;

6、高为2的B+树可以存放多少行数据

首先,需要计算出非叶子节点能存放多少指针。B+ 树能够存放多少行数据,其实问的就是这棵 B+ 树的非叶子节点中存放的数据量。

简单按照一行记录的数据大小为 1k 来算的话(实际上现在很多互联网业务数据记录大小通常就是 1K左右),一页(16K)或者说一个叶子节点可以存放 16 行这样的数据。那么 ,这颗B+ 树 的非叶子节点( 唯一的)能够存储多少数据呢?

非叶子节点里面存的是主键值 + 指针,为了方便分析,这里我们把一个主键值 + 一个指针称为一个单元,

  • 我们假设主键的类型是 bigint,长度为 8 字节,
  • 而指针大小在 InnoDB 中设置为 6 字节

这样一个单元,一共 14 字节。

这样的话,一页或者说一个非叶子节点能够存放 16384 / 14=1170 个这样的单元。

也就是说一个非叶子节点中能够存放 1170 个指针,即对应 1170 个叶子节点,所以对于这样一棵高度为 2 的 B+ 树,能存放 1170(一个非叶子节点中的指针数) * 16(一个叶子节点中的行数)= 18720 行数据。

7、高为3的B+树可以存放多少行数据

分析完高度为 2 的 B+ 树,同样的道理,我们来看高度为 3 的:

根页(page10)可以存放 1170 个指针,然后第二层的每个页(page:11,12,13)也都分别可以存放1170个指针。

这样一共可以存放 1170 * 1170 个指针,即对应的有 1170 * 1170 个非叶子节点,

所以,高为3的B+树一共可以存放 1170 * 1170 * 16 = 21902400 行记录。

8、创建索引的原则

  1. 较频繁作为查询条件的字段才去创建索引 ,更新频繁字段不适合创建索引 。
  2. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。
  3. 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
  4. 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 andc > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
  5. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
  6. 对于定义为text、image和bit的数据类型的列不要建立索引

9、索引使用场景

  1. where
  2. order by
  3. join

10、索引类型

  1. *主键***索引(Primary Key)**:主键不能为 null,不能重复。
  2. **唯一索引(Unique Key) **:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
  3. 普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
  4. **前缀索引(Prefix) **:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。
  5. 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。

11、聚簇索引与非聚簇索引

  • 聚簇索引(聚集索引):即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
  • 非聚簇索引:即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引

12、最左前缀匹配的内部原理

索引的底层是一颗 B+ 树,那么联合索引当然还是一颗 B+ 树,只不过联合索引的键值数量不是一个,而是多个。构建一颗 B+ 树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建 B+ 树。因为联合索引的B+Tree是按照第一个关键字进行索引排列的。在 InnoDB 中联合索引只有先确定了前一个(左侧的值)后,才能确定下一个值。如果有范围查询的话,那么联合索引中使用范围查询的字段后的索引在该条 SQL 中都不会起作用。

  • 顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
  • 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4,如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
  • =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

13、索引失效的情况

  • like 以%开头索引无效,当 like 以%结尾,索引有效。
  • or 语句前后没有同时使用索引,当且仅当 or 语句查询条件的前后列均为索引时,索引 生效。
  • 组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。
  • 数据类型出现隐式转换,如 varchar 不加单引号的时候可能会自动转换为 int 类型,这 个时候索引失效。
  • 在索引列上使用 IS NULL 或者 IS NOT NULL 时候,索引可能失效,因为索引是不索引空值。(索引不一定失效。索引是否失效取决于表中数据分布情况,mysql会评估索引快,还是全表快,去选择是否使用索引)
  • 在索引字段上使用,NOT、 <>、!= 、时候是不会使用索引的,对于这样的处理只会进 行全表扫描。
  • 对索引字段进行计算操作,函数操作时不会使用索引。
  • 当全表扫描速度比索引速度快的时候不会使用索引。

14、索引条件下推

MySQL5.6新添加的特性,用于优化数据查询的。

5.6 之前通 过非主键索引查询时,存储引擎通过索引查询数据,然后将结果返回给MySQL server层,在server层判断是否符合条件,在以后的版本可以使用索引下推,当存在索引列作为判断条件时,Mysql server 将这一部分判断条件传递给存储引擎,然后存储引擎会筛选出符合传递传递条件的索引项,即在存储引擎层根据索引条件过滤掉不符合条件的索引项,然后回表查询得到结果,将结果再返回给Mysql server,有了索引下推的优化,在满足一定条件下,存储 引擎层会在回表查询之前对数据进行过滤,可以减少存储引擎回表查询的次数。

回表查询:先根据二级索引(非聚集索引)在树中查询主键,然后在去聚集索引树中根据主键查询对应的行记录。

15、什么是自适应哈希索引?

自适应哈希索引是Innodb引擎的一个特殊功能,当它注意到某些索引值被使用的非常频繁时,会在内存中基于B-Tree所有之上再创建一个哈希索引,这就让B-Tree索引也具有哈希索引的一些优点,比如快速哈希查找。这是一个完全自动的内部行为,用户⽆法控制或配置

使用命令

SHOW ENGINE INNODB STATUS ;

查看INSERT BUFFER AND ADAPTIVE HASH INDEX

16、为什么官方建议使用自增长主键作为索引?(说一下自增主键和字符串类型主键的区别和影响)

  1. 自增主键能够维持底层数据顺序写⼊
  2. 读取可以由b+树的二分查找定位
  3. ⽀持范围查找,范围数据自带顺序

17、MySQL explain执行计划详解

  1. 作用:explain显示了mysql如何使用索引来处理select语句以及连接表
  2. explain执行计划包含的信息:
  • id(table是id对应的表):表的读取顺序,id相同顺序执行,id不同,数字越大优先级越高
  • type:查询类型。性能由最好到最差依次是:system > const > eq_ref > ref > range > index > ALL- system、const:MySQL对查询的某部分进行优化并把其转化成一个常量(可以通过show warnings命令查看结果)。system是const的一个特例,表示表里只有一条元组匹配时为system- eq_ref:主键或唯一键索引被连接使用,最多只会返回一条符合条件的记录。简单的select查询不会出现这种type。- ref:相比eq_ref,不使用唯一索引,而是使用普通索引或者唯一索引的部分前缀,索引和某个值比较,会找到多个符合条件的行。- range:通常出现在范围查询中,比如in、between、大于、小于等。使用索引来检索给定范围的行。- index:扫描全索引拿到结果,一般是扫描某个二级索引,二级索引一般比较少,所以通常比ALL快一- ALL:全表扫描,扫描聚簇索引的所有叶子节点。
  • key:实际用到的索引键
  • rows:索引查询时,大致估算出查询到所需记录读取的行数,rows越小越好
  • Extra:额外信息 using index : 覆盖索引,避免回表 using where: 全表扫描 + where 条件过滤数据 using filesort: 无法使用索引排序,只能使用排序算法进行排序,会产生额外的消耗 using temporary: 使用了临时表 no matching row in const table: 在唯一性索引上无法匹配到数据 using join buffer: 连接字段上没有索引,使用了连接缓存

三、****事务

1、事务的四大特性(ACID)介绍一下

  1. 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  2. 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
  3. 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  4. 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

2、MySQL的事务ACID是如何实现的?

以MySQL 的InnoDB (InnoDB 是 MySQL 的一个存储引擎)为例。InnoDB 是通过 日志和锁 来保证的事务的 ACID特性,具体如下:

  1. 通过数据库锁机制,保障事务的隔离性;
  2. 通过 Redo Log(重做日志)来,保障事务的持久性;
  3. 通过 Undo Log (撤销日志)来,保障事务的原子性;
  4. 通过 Undo Log (撤销日志)来,保障事务的一致性;

3、什么是脏读?幻读?不可重复读?

  • 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的
  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
  • 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

4、什么是事务的隔离级别?MySQL的默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。

  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读

Mysql** 默认采用的 REPEATABLE_READ隔离级别 Oracle默认采用的 READ_COMMITTED隔离级别 **

5、事务的实现原理

Innodb通过Buffer Pool,LogBuffer,Redo Log,Undo Log来实现事务,以⼀个update语句为例:

  1. Innodb在收到⼀个update语句后,会先根据条件找到数据所在的⻚,并将该⻚缓存在Buffer Pool中
  2. 执⾏update语句,修改Buffer Pool中的数据,也就是内存中的数据
  3. 针对update语句⽣成⼀个RedoLog对象,并存⼊LogBuffer中
  4. 针对update语句⽣成undolog⽇志,⽤于事务回滚
  5. 如果事务提交,那么则把RedoLog对象进⾏持久化,后续还有其他机制将Buffer Pool中所修改的数据⻚持久化到磁盘中
  6. 如果事务回滚,则利⽤undolog⽇志进⾏回滚

6、MySQL事务日志介绍下?

innodb 事务日志包括 redo log 和 undo log。

undo log 指事务开始之前,在操作任何数据之前,首先将需操作的数据备份到一个地方。

redo log 指事务中操作的任何数据,将最新的数据备份到一个地方。

(1)redo log

redo log 不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入 redo 中。具体的落盘策略可以进行配置 。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 MySQL 服务的时候,根据 redo log 进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。RedoLog 是为了实现事务的持久性而出现的产物。

(2)undo log

undo log 用来回滚行记录到某个版本。事务未提交之前,Undo 保存了未提交之前的版本数据,Undo 中的数据可作为数据旧版本快照供其他并发事务进行快照读。是为了实现事务的原子性而出现的产物,在 MySQL innodb 存储引擎中用来实现多版本并发控制。

7、什么是MySQL的 binlog?

MySQL的 binlog 是记录所有数据库表结构变更(例如 CREATE、ALTER TABLE)以及表数据修改(INSERT、UPDATE、DELETE)的二进制日志。binlog 不会记录 SELECT 和 SHOW 这类操作,因为这类操作对数据本身并没有修改,但你可以通过查询通用日志来查看 MySQL 执行过的所有语句。

MySQL binlog 以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型的。binlog 的主要目的是复制和恢复。

binlog 有三种格式,各有优缺点:

  • statement: 基于 SQL 语句的模式,某些语句和函数如 UUID, LOAD DATA INFILE 等在复制过程可能导致数据不一致甚至出错。
  • row: 基于行的模式,记录的是行的变化,很安全。但是 binlog 会比其他两种模式大很多,在一些大表中清除大量数据时在 binlog 中会生成很多条语句,可能导致从库延迟变大。
  • mixed: 混合模式,根据语句来选用是 statement 还是 row 模式。

四、锁

1、Mysql 中锁机制

事务的隔离性(隔离级别)是由锁来保证的。

并发访问数据的情况分为:

读-读

即并发事务相继读取相同的记录,因为没涉及到数据的更改,所以不会有并发安全问题,允许这种情况发生。

写-写

即并发事务对相同记录进行修改,会出现脏写问题,因为任何一种隔离级别都不允许发生脏写,所以多个未提交的事务对同一个记录修改时需要加锁,保证它们是顺序执行的。

锁内存中的结构,当事务想对某条数据进行更改时,首先会查看该记录有没有与之关联的锁结构,有的话则等待它的事务被提交,锁被释放;反之没有锁则生成锁结构与该记录继续关联。

写-读或读-写

即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生脏读 、不可重复读 、幻读的问题。可以使用两种方式解决(都离不开锁):

  • 读写都采用加锁的方式,读写也需要排队执行,性能较差
  • 写操作加锁,读操作利用MVVC多版本并发控制,读取历史记录,性能更高

涉及到写操作时,必须有锁

https://blog.csdn.net/weixin_45902285/article/details/126186759

(1)从数据操作的类型分类

  • 共享锁S锁****)

也称读锁,允许事务对某些数据进行读取。多个事务的读操作不会相互影响,也不会相互阻塞。

  • 排他锁(X锁)

也称写锁,允许事务对某些数据进行删除或更新。如果当前操作还没完成,其他事务的S和X锁是会被阻塞的,确保在多个事务中,对同一资源,只有一个事务能写入,并防止其他用户读取正在写入的资源。

(2)从锁的粒度分类

从锁的粒度划分可分为:

  • 全局锁

全局锁就是对整个数据库实例加锁,主要是做全库逻辑备份

  • 表锁
  • 表级别的S锁和X锁
  • 意向锁:意向锁的作用就是加快表锁的检查过程。

意向共享锁(IS):事务有意向对表中的某些行加共享锁(S锁),会自动加上意向共享锁 意向排他锁(IX):事务有意向对表中的某些行加排他锁(X锁),会自动加上意向排它锁

  • 自增锁: 表中有自增列时,插入记录会使用到自增锁
  • 元数据锁: 在对某个表执行一些诸如ALTER TABLE 、DROP TABLE这类的DDL语句时,其他事务对这个表并发执行诸如SELECT、INSERT、DELETE、UPDATE的语句会发生阻塞
  • 行锁

记录锁(Record Lockss)

记录锁就是行级别的X锁和S锁,仅仅锁住一行记录。当我们针对主键或者唯一索引加锁的时候,

Mysql 默认会对查询的这一行数据加行锁,避免其他事务对这一行数据进行修改。例如:select * from user

where id='1' for UPDATE;

间隙锁(Gap Locks)

就是某个事务对索引列加锁的时候,默认锁定对应索引的左右开区间范围。间隙锁是针对事务隔离级别为可重复读或以上级别而言的。例如:

select * from user where id BETWEEN '5' and '8' for UPDATE;

图中id值为8的记录加了gap锁,意味着不允许别的事务在(5,8)之间插入新记录。比如,有另外一个事务再想插入一条id值为6的新记录,它定位到该条新记录的下一条记录的id值为8,而这条记录上又有一个gap锁,所以就会阻塞插入操作,直到拥有这个gap锁的事务提交了之后,id列的值在区间(5, 8)中的新记录才可以被插入。

临键锁(Next-Key Locks)

记录锁 + 间隙锁,它指的是加在某条记录以及这条记录前面间隙上的锁。例如:我们使用非唯一索引列进行查询的时候,默认会加一个临键锁,锁定一个左开右闭区间的范围,select * from user where age= '11' for UPDATE,锁定区间(10,11];

插入意向锁(Insert Intention Locks)

表明想在某个间隙中插入一条数据

(3)从锁的态度分类

乐观锁

  • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现
  • 简单实现:
  • CAS的实现原理是比较并交换,简单点来说就是,更新数据之前,会先检查数据是否有被修改过:如果没有修改,则直接更新;如果有被修改过,则重试;
  • 版本号机制:在表中加个version或updatetime字段,在每次更新操作时对此一下该字段,如果一致则更新数据,数据不等则放弃本次修改,根据实际业务需求做相应的处理。
  • 乐观锁的适用场景:读多写少

悲观锁

  • 悲观锁:共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。实现:Java中synchronized和ReentrantLock
  • 悲观锁的适用场景:写多读少

2、隔离级别与锁的关系

数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别。

  1. Read Uncommitted:读或写完成时释放锁,然后提交事务,会造成脏读。
  2. Read Committed:大多数数据库默认的隔离级别,将释放锁的位置调整到事务提交后。会造成不可重复读。(在同一个事务中如果两次读取相同的数据时,最后的结果却不一致)
  3. Repeatable-Read:mysql数据库所默认的级别,使用MVCC并发版本控制来实现读写一致性。
  4. serializable:表锁

*注意:幻读和不可重复读的区别:

幻读是针对的一批数据记录整体

不可重复读针对的是同一数据项的记录

3、Mysql如何解决幻读问题

MySQL默认采用的隔离级别是可重复读,在这种隔离级别下不同的读模式,针对幻读问题采用了不同解决方案:

针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。

针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。

但是,强调一点的是,MySQL在可重复读级别下,并没有完完全全的解决幻读问题,特别是在一个事务的快照读和当前读穿插使用的场景下,还是会出现幻读的情况,比如下图所示。

4、MVCC 多版本并发控制

MVCC, 即多版本并发控制。MVCC 的实现,是通过保存数据在某个时间点的快照来实现的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

  • undo Log

当我们读取一条被其他事务变更的数据时,会在undo Log中产生一条变更前的日志.这个日志可以专门用于回滚。

我们大概来看一下这个日志的大概结构:

前面三个字段属于变更前的,另外:

**trx_id **: 代表是哪个事务编号修改的。

**roll_pointer **: 相当于一个链表,往下查找就是上一次更改前的。当一条数据被更改了多次之后,由该字段构建成一个链表俗称版本链。

  • Read View 读视图

基于当前活跃事务列表构成的ReadView,当某个事务创建ReadView时,会将当前活跃的事务也加入其中。

readview中四个比较重要的概念:

m_ids:表示在生成readview时,当前系统中活跃的读写事务id列表;

min_trx_id:表示在生成readview时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中最小的值;

max_trx_id:表示生成readview时,系统中应该分配给下一个事务的id值;

creator_trx_id:表示生成该readview的事务的事务id;

有了readview,在访问某条记录时,按照以下步骤判断记录的某个版本是否可见:

基于****可重复读

ReadView(简称RV)一旦创建是不可变的,即便其中某个线程事务提交了,也不会影响当前线程创建的ReadView,你可以理解为一个副本快照。

总的来说判断就三个条件:

  1. undo log的数据中包含的trx_id是介于min_trx_id和max_trx_id之间1. 如果小于min_trx_id说明创建RV 之前 的时候这个trx_id就已经事务提交了,不活跃了,说明可以读。2. 如果大于max_trx_id说明这个版本是在创建RV 之后 产生的,不可读。因为创建RV时你这个版本还不存在3. 如果是在这之间的再看步骤2
  2. 查看trx_id是否包含m_id之中:1. 包含说明创建RV的时候,还是活跃(没提交)事务。那么是不可见的,脏读;继续看步骤32. 不包含说明创建RV之前这个事务已经被提交了,那么是可见的。
  3. 到了这里说明这条数据的变更版本在RV之内,则要查看creator_trx_id与trx_id是否一致:1. 一致说明就是当前事务创建的;允许使用;2. 否则说明是当前RV的其他事务操作的不能使用;

Read Committed:某个事务对某个数据每进行一次快照读,都会新生成一个新的ReadView。会造成不可重复读。

Repeatable-Read:当某个事务对某条记录进行快照读(即不加锁的select)时,会创建一个ReadView,之后再进行快照读时,还是继续使用同一个ReadView

五、架构

1、为什么要分库分表

在业务场景中,mysql的单表数据量出现在千万级左右查询数据就会出现瓶颈。在各种优化后,可以考虑对数据库进行拆分。

(1)水平拆分:

就是把一张表的数据拆成两张表

(2)垂直拆分:

将表中的某些字段,单独拆分出来,并通过某个字段和原表建立联系分库分表常用思路

2、分库分表常用思路

(1)hash取模方案

hash的方案就是对指定的路由key(通常为主键id)对分表总数进行取模。

优点:某个表不会出现热点问题(某个表被频繁访问,其他表访问较少的情况)

缺点:对于以后要扩容会比较麻烦,增加表的时候要对所有数据重新hash

(2)range范围方案

简单来说就是1-1000万的数据放在1数据库并以此类推。

优点:有利于将来扩容,有新数据就直接加入就好了

缺点:缺点也很明显,有可能出现热点问题。比如刚加进来的数据经常使用之类的问题

(3)两者结合的方案

一个Group01可以存放4000w个数据,Group01有三个DB,共10table。那么可以通过id范围确定在哪

个group,然后id%10哪个db。再根据范围确定哪个table。简单来说就是整体有序,局部用hash打

乱,当扩容的时候只要加group就好了。

3、分库分表带来的问题有哪些?

(1)分布式事务问题

使用分布式事务中间件解决,具体是通过最终一致性还是强一致性分布式事务,看业务需求。

(2)跨节点关联查询 Join 问题

切分之前,我们可以通过Join来完成。而切分之后,数据可能分布在不同的节点上,此时Join带来的问题就比较麻烦了,考虑到性能,尽量避免使用Join查询。解决这个问题的一些方法:

  • 全局表

全局表,也可看做是 "数据字典表",就是系统中所有模块都可能依赖的一些表,为了避免跨库Join查询,可以将这类表在每个数据库中都保存一份。这些数据通常 很少会进行修改,所以也不担心一致性的问题。

  • 字段冗余

利用空间换时间,为了性能而避免join查询。例:订单表保存userId时候,也将userName冗余保存一份,这样查询订单详情时就不需要再去查询"买家user表"了。

  • 数据组装

在系统层面,分两次查询。第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据。最后将获得到的数据进行字段拼装。

(3)跨节点分页、排序、函数问题

跨节点多库进行查询时,会出现Limit分页、Order by排序等问题。分页需要按照指定字段进行排序,当排序字段就是分片字段时,通过分片规则就比较容易定位到指定的分片;当排序字段非分片字段时,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。

(4)全局主键避重问题

如果都用主键自增肯定不合理,如果用UUID那么无法做到根据主键排序,所以我们可以考虑通过雪花ID来作为数据库的主键

4、超大分页怎么处理?

(1)用id优化

先找到上次分页的最大ID,然后利用id上的索引来查询,类似于

select * from user where id>1000000 limit 100

这样的效率非常快,因为主键上是有索引的,但是这样有个缺点,就是ID必须是连续的,并且查询不能有where语句,因为where语句会造成过滤数据。

(2)用覆盖索引优化

Mysql的查询完全命中索引的时候,称为覆盖索引,是非常快的,因为查询只需要在索引上进行查找,之后可以直接返回,而不用再回表拿数据.因此我们可以先查出索引的ID,然后根据Id拿数据

select * from table where id in (select id from table where age > 20 limit 1000000,10)

(3)在业务允许的情况下限制页数

建议跟业务讨论,有没有必要查这么靠后的分页啦。因为绝大多数用户都不会往后翻太多页

5、order by 调优10倍,思路是啥?

(1)减少select 后⾯的查询的字段。 禁⽌使用select *

(2)联合索引优化

如果数据本身是有序的,那就不需要排序,而索引数据本身是有序的,所以,我们可以通过建立联

合索引,跳过排序步骤。

(3)参数优化

通过调整参数,也可以优化 order by 的执行。

  1. 调整 sort_buffer_size 参数的值。如果 sort_buffer 值太小而数据量大的话,MySQL 会采用磁盘临时文件辅助排序。MySQL 服务器配置高的情况下,可以将参数调大些。
  2. 调整 max_length_for_sort_data 的值,值太小的话 MySQL 会采用 rowid 排序,会多一次回表操作导致查询性能降低。同样可以适当调大些。

6、聊聊:什么是双路排序和单路排序

  1. 单路排序:一次取出所有字段进行排序,内存不够用的时候会使用磁盘
  2. 双路排序:取出排序字段进行排序,排序完成后再次回表查询所需要的其他字段

7、什么是MySQL 的 MRR 优化?

MRR 是 Multi-Range Read Optimization,通过把随机磁盘读转化为顺序磁盘读,提高索引查询的性能。

在不使用 MRR 时,优化器需要根据二级索引返回的记录来进行“回表”,这个过程一般会有较多的随机IO,使用 MRR 时,SQL 语句的执行过程是这样的:

(1)先把通过二级索引取出的值缓存在缓冲区中

这个缓冲区叫做 read_rnd_buffer ,简称 rowid buffer。

(2)再把这部分缓冲区中的数据按照ID进行排序。

如果二级索引扫描到索引文件的末尾或者缓冲区已满,则使用快速排序对缓冲区中的内容按照主键进行排序;

(3)然后再依次根据ID去聚集索引中获取整个数据行。

线程调用 MRR 接口取 rowId,然后根据rowId 取行数据;当根据缓冲区中的 rowId 取完数据,则继续调用过程 2) 3),直至扫描结束;

MRR 的本质:

是在回表的过程中, 把分散的无序回表, 变成排序后有序的回表, 从而实现 随机磁盘读 尽可能变成顺序读。

MRR 使用限制

MRR 适用于range、ref、eq_ref的查询

8、一条MySQL更新语句的执行过程是什么样的?

(1)连接验证及解析

客户端与MySQL Server建立连接,发送语句给MySQL Server,接收到后如果是查询语句会先去查询缓存中看,有的话就直接返回了,(新版本的MySQL已经废除了查询缓存,命中率太低了),如果是缓存没有或者是非查询语句,会创建一个解析树,然后进行优化,(解析器知道语句是要执行什么,会评估使用各种索引的代价,然后去使用索引,以及调节表的连接顺序)然后调用innodb引擎的接口来执行语句。

(2)写undo log

innodb 引擎首先开启事务,获得一个事务ID(是一直递增的),根据执行的语句生成一个反向的语句,(如果是INSERT会生成一条DELETE语句,如果UPDATE语句就会生成一个UPDATE成旧数据的语句),用于提交失败后回滚,将这条反向语句写入undo log,得到回滚指针,并且更新这个数据行的回滚指针和事务id。(事务提交后,Undo log并不能立马被删除,而是放入待清理的链表,由purge 线程判断是否有其他事务在使用undo 段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间,简单的说就是看之前的事务是否提交成功,这个事务及之前的事务都提交成功了,这部分undo log才能删除。)

(3)从索引中查找数据

根据索引去B+树中找到这一行数据(如果是普通索引,查到不符合条件的索引,会把所有数据查找出来,唯一性索引查到第一个数据就可以了)

(4)更新数据

看图。。。

(5)写redo log(prepare状态)

将对数据页的更改写入到redo log,此时redo log中这条事务的状态为prepare状态。

(6)写bin log(同时将redo log设置为commit状态)

通知MySQL server已经更新操作写入到redo log 了,随时可以提交,将执行的SQL写入到bin log日志,将redo log 中这条事务的状态改成commit状态,事务提交成功。

9、大表查询优化

  1. 选择合适的数据类型
  2. 建立合适的索引
  3. sql语句优化
  4. 分表分库
  5. 读写分离
  6. 冷热数据分离
  7. Elastic search
  8. 大数据

10、百万级别或以上的数据,你是如何删除的?

  1. 我们想要删除百万数据的时候可以先删除索引
  2. 然后批量删除其中无用数据
  3. 删除完成后重新创建索引。

11、Mysql 主从复制的原理及流程

就是将主节点的数据复制到从节点。

  • 用途:
  1. 可以读写分离,主库用来写数据,从库用来读数据。在更新数据时,会对整个表加锁,如果是读写分离的,可以去从表读取数据,这样就不会有问题。
  2. 做备份,主库出故障后,进行故障转移,让从库代替主库,提供服务。
  • 实现原理

主节点日志发送线程

当主节点和从节点建立连接后,主服务器上会起一个bin log dump线程,用于给从节点发送bin log日志(日志所包含的信息之外,还包括本次返回的信息的bin-log file 的以及bin-log position),在读取bin log日志时,会对日志文件加锁,读取完成后会解锁。

从节点I/O线程

从节点与主节点建立连接后会起一个I/O线程来接受主节点发送过来的bin log日志内容,并且保存在从节点的relay log文件中,保存成功后就会给主节点回复ACK消息,表明接收成功。

从节点SQL线程

从节点同时会起一个SQL线程,来读取 relay log 中的内容,解析成SQL,并且在从节点上执行,保证和主节点的数据一致性。

  • 复制模式

异步模式(默认的模式)

主节点不会主动push bin log给从节点,也不会管从节点的同步情况,默认就是这种模式。

半同步模式(**MySQL 5.5之后提供)**

主节点给从节点发送bin log 之后,会一直等待回应,只要一个从节点接受bin log,并且写入relay log 成功,给主节点返回接受成功的ACK信息,主节点就认为成功,提交事务。

全同步模式

就是需要所有的从节点接受日志,并且写入relay log 成功,给主节点返回接受成功的ACK信息,主节点才认为成功,提交事务。

总结:

二个日志:二进制日志和中继日志 三个线程:master的dump和slave的I/O、SQL 主要原理:master将数据保存在二进制日志中,I/O向dump发出同步请求,dump把数据发送给I/O线程,I/O写入本地的中继日志,SOL线程读取本地中继日志数据,同步到自己数据库中,完成同步

12、Mysql 主从集群同步延迟问题怎么解决

复制过程分为几个步骤:

  1. 主库的更新事件(update、insert、delete)被写到 binlog
  2. 从库发起连接,连接到主库
  3. 此时主库创建一个 binlog dump thread,把 binlog 的内容发送到从库
  4. 从库启动之后,创建一个 I/O 线程,读取主库传过来的 binlog 内容并写入到relay log
  5. 从库还会创建一个 SQL 线程,从 relay log 里面读取内容,从Exec_Master_Log_Pos 位置开始执行读取到的更新事件,将更新内容写入到 slave的 db

主从数据同步涉及到网络数据传输,由于网络通信的延迟以及从库数据处理的效率问题,就会导致主从数据同步延迟的情况

一般可以通过以下几个方法来解决

  • 设计一主多从来分担从库压力,减少主从同步延迟问题
  • 如果对数据一致性要求高,在从库存在延迟的情况下,可以强制走主库查询数据
  • 可以在从库上执行 show slave status 命令,获取 seconds_behind_master 字段的延迟时间,然后通过 sleep 阻塞等待固定时间后再次查询
  • 通过并行复制解决从库复制延迟的问题

13、执行 SQL 响应比较慢,你有哪些排查思路及解决方案?

MySQL 的性能优化我认为可以分为 4 大部分

(1)硬件及操作系统层面优化

从硬件层面来说,影响 Mysql 性能的因素有,CPU、可用内存大小、磁盘读写速度、网络带宽。从操作系层面来说,应用文件句柄数、操作系统网络的配置都会影响到 Mysql 性能

(2)架构设计层面的优化

MySQL 是一个磁盘 IO 访问量非常频繁的关系型数据库,在高并发和高性能的场景中.MySQL 数据库必然会承受巨大的并发压力,而此时,我们的优化方式可以分为几个部分

  • 搭建 Mysql 主从集群,单个 Mysql 服务容易单点故障,一旦服务器宕机,将会导致依赖 Mysql 数据库的应用全部无法响应。 主从集群或者主主集群可以保证服务的高可用性。
  • 读写分离设计,在读多写少的场景中,通过读写分离的方案,可以避免读写冲突导致的性能影响。
  • 引入分库分表机制,通过分库可以降低单个服务器节点的 IO 压力,通过分表的方式可以降低单表数据量,从而提升 sql 查询的效率。
  • 针对热点数据,可以引入更为高效的分布式数据库,比如 Redis、MongoDB 等,他们可以很好的缓解 Mysql 的访问压力,同时还能提升数据检索性能
(3)MySQL 程序配置优化

MySQL 是一个经过互联网大厂验证过的生产级别的成熟数据库,对于 Mysql 数据库本身的优化,一般是通过 Mysql 中的配置文件 my.cnf 来完成的,比如

  • Mysql5.7 版本默认的最大连接数是 151 个,这个值可以在 my.cnf 中修改。 binlog 日志,默认是不开启
  • 缓存池 bufferpoll 的默认大小配置等。

由于这些配置一般都和用户安装的硬件环境以及使用场景有关系,因此这些配置官方只会提供一个默认值,具体情况还得由使用者来修改。关于配置项的修改,需要关注两个方面

  • 配置的作用域,分为会话级别和全局
  • 是否支持热加载

因此,针对这两个点,我们需要注意的是:

  • 全局参数的设定对于已经存在的会话无法生效
  • 会话参数的设定随着会话的销毁而失效
  • 全局类的统一配置建议配置在默认配置文件中,否则重启服务会导致配置失效
(4)SQL 优化

SQL 优化又能分为三步曲

  • 第一、慢 SQL 的定位和排查。我们可以通过慢查询日志和慢查询日志分析工具得到有问题的 SQL 列表
  • 慢查询日志例子:

  • 第二、执行计划分析。针对慢 SQL,我们可以使用关键字 explain 来查看当前 sql 的执行计划.可以重点关注 type key rows filterd 等字段 ,从而定位该 SQL 执行慢的根本原因。再有的放矢的进行优化
  • 第三、使用 show profile 工具 。Show Profile 是 MySQL 提供的可以用来分析当前会话中,SQL 语句资源消耗情况的工具,可用于 SQL 调优的测量。在当前会话中.默认情况下处于 show profile 是关闭状态,打开之后保存最近 15 次的运行结果。针对运行慢的 SQL,通过 profile 工具进行详细分析,可以得到 SQL 执行过程中所有的 资源开销情况,如 IO 开销,CPU 开销,内存开销等

常见的 SQL 优化规则:

  • SQL 的查询一定要基于索引来进行数据扫描
  • 避免索引列上使用函数或者运算,这样会导致索引失效
  • where 字句中 like %号,尽量放置在右边
  • 使用索引扫描,联合索引中的列从左往右,命中越多越好.
  • 尽可能使用 SQL 语句用到的索引完成排序,避免使用文件排序的方式
  • 查询有效的列信息即可,少用 * 代替列信息
  • 永远用小结果集驱动大结果集

14、MySQL 数据库 cpu 飙升的话,要怎么处理呢?

(1)第一步,排查问题

  • 使用 top 命令,找到 cpu 占用过高的进程是否是 mysqld
  • 如果是mysqld导致的,可以在 mysql 中通过 show processlist 查看当前的会话情况,确定是否有消耗资源的 SQL 正在运行
  • 找到消耗过高的 SQL,通过执行计划进行具体的分析

(2)第二步,处理方式

  • 如果确定是 SQL 问题,可以通过 SQL 的优化手段进行调整
  • 重新执行 SQL 分析确认是否有达到优化的目的

(3)第三步,其他情况

如果不是 SQL 的问题导致,那就需要分析 CPU 飙高的这个时间段,Mysql 的整体并发连接数。如果有大量的请求连接进来,那我们就需要分析这个时间段业务的情况,再做出相应的调整。最后,如果是 Mysql 本身的参数并不是最优状态,那我们可以对 Mysql 服务节点的配置进行调整,比如缓存大小、线程池大小等

15、知道MySQL的WAL、LSN、Checkpoint 吗?

(1)WAL (预写式日志)技术

WAL的全称是 Write-Ahead Logging。修改的数据要持久化到磁盘,会先写入磁盘的文件系统缓存,然后可以由后台线程异步慢慢地刷回到磁盘。所以WAL技术修改数据需要写两次写入。

两次写入

  • 内存写入:第一次,修改在缓冲池中的页, 随机IO
  • 磁盘写入:第二次,再以一定的频率刷新到磁盘上,顺序IO

WAL的好处

节省了随机写磁盘的 IO 消耗(转成顺序写)。而且顺序IO性能比较高

(2)LSN(日志序列号)

LSN是Log Sequence Number的缩写,即日志序列号,表示Redo Log 的序号。

(3)Checkpoint(检查点)

缓冲池的容量和重做日志(redo log)容量是有限的,Checkpoint所做的事就是把脏页给刷新回磁盘。

定义

一个时间点,由一个LSN值(Checkpoint LSN)表示的整型值,在checkpoint LSN之前的每个数据(buffer pool中的脏页)的更改都已经落盘(刷新到数据文件中),checkpoint 完成后,在checkpoint LSN之前的Redo Log就不再需要了

所以:checkpoint是通过LSN实现的。

分类

  • Sharp Checkpont

该机制下,在数据库发生关闭时将所有的脏页都刷新回磁盘。

  • Fuzzy Checkpoint

在该机制下,只刷新一部分脏页,而不是刷新所有脏页回磁盘。

数据库关闭时,使用 Sharp Checkpont 机制刷新脏页。 数据库运行时,使用 Fuzzy Checkpoint 机制刷新脏页。 

检查点触发时机

  • Master Thread Checkpoint

后台异步线程以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘。

  • FLUSH_LRU_LIST Checkpoint

为了保证LRU列表中可用页的数量(通过参数innodb_lru_scan_depth控制,默认值1024),后台线程定期检测LRU列表中空闲列表的数量,若不满足,就会将移除LRU列表尾端的页,若移除的页为脏页,则需要进行Checkpoint。

show VARIABLES like 'innodb_lru_scan_depth'
  • Async/sync Flush Checkpoint 当重做日志不可用(即redo log写满)时,需要强制将一些页刷新回磁盘,此时脏页从脏页列表中获取。
  • Dirty Page too much Checkpoint

即脏页数量太多,会强制推进CheckPoint。目的是保证缓冲区有足够的空闲页。 innodb_max_dirty_pages_pct的默认值为75,表示当缓冲池脏页比例达到该值时,就会强制进行 Checkpoint,刷新一部分脏页到磁盘。

show VARIABLES like 'innodb_max_dirty_pages_pct'

解决的问题

  • 缩短数据库的恢复时间。
  • 缓冲池不够用时,刷新脏页到磁盘。
  • 重做日志满时,刷新脏页。

LSN与checkpoint的联系

LSN号串联起一个事务开始到恢复的过程。

重启 innodb 时,Redo log 完不完整,采用 Redo log 相关知识。用 Redo log 恢复,启动数据库时,InnoDB 会扫描数据磁盘的数据页 data disk lsn 和日志磁盘中的 checkpoint lsn。

两者相等则从 checkpoint lsn 点开始恢复,恢复过程是利用 redo log 到 buffer pool,直到checkpoint lsn 等于 redo log file lsn,则恢复完成。如果 checkpoint lsn 小于 data disk lsn,说明在检查点触发后还没结束刷盘时数据库宕机了。

因为 checkpoint lsn 最新值是在数据刷盘结束后才记录的,检查点之后有一部分数据已经刷入数据磁盘,这个时候数据磁盘已经写入部分的部分恢复将不会重做,直接跳到没有恢复的 lsn 值开始恢复。

总结

日志空间中的每条日志对应一个LSN值,而在数据页的头部也记录了当前页最后一次修改的LSN号,每次当数据页刷新到磁盘后,会去更新日志文件中checkpoint,以减少需要恢复执行的日志记录。

极端情况下,数据页刷新到磁盘成功后,去更新checkpoint时如果宕机,则在恢复过程中,由于checkpoint还未更新,则数据页中的记录相当于被重复执行,不过由于在日志文件中的操作记录具有幂等性,所以同一条redo log执行多次,不影响数据的恢复

16、聊聊:现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?

(1)停机迁移方案

我先给你说一个最 low 的方案,就是很简单,大家伙儿凌晨 12 点开始运维,网站或者 app 挂个公告,

说 0 点到早上 6 点进行运维,无法访问。

接着到 0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个导数的一次性工具,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。

导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。

验证一下,ok了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。

但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。

(2)双写迁移方案

这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨 4 点的风景。

简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,除了对老库增删改,都加上对新库的增删改,这就是所谓的双写,同时写俩库,老库和新库。

然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据 gmt_modified 这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。简单来说,就是不允许用老数据覆盖新数据。导完一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。

接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。

17、聊聊:如何设计可以动态扩容缩容的分库分表方案?

(1)停机扩容(不推荐)

这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数的工具,是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿,有点不太靠谱,因为既然分库分表就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。

从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1小时数据就导完了。这没有问题。

如果 3 个库 + 12 个表,跑了一段时间了,数据量都 1~2 亿了。光是导 2 亿数据,都要导个几个小时,6 点,刚刚导完数据,还要搞后续的修改配置,重启系统,测试验证,10 点才可以搞完。所以不能这么搞

(2)32库*32表

一开始上来就是 32 个库,每个库 32 个表,那么总共是 1024 张表。

我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。

每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5万/s 的写入并发,前面再加一个MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。

有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128个库,256个库,512个库。1024 张表,假设每个表放 500 万数据,在 Mysql 里可以放 50 亿条数据。

每秒的 5 万写并发,总共 50 亿条数据,对于国内大部分的互联网公司来说,其实一般来说都够了。谈分库分表的扩容,第一次分库分表,就一次性给他分个够,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了。一个实践是利用 32 * 32 来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。

18、七种日志的详细分析

进入正题前,可以先简单介绍一下,MySQL的逻辑架构

MySQL的逻辑架构大致可以分为三层:

  • 第一层:处理客户端连接、授权认证,安全校验等。
  • 第二层:服务器 server 层,负责对SQL解释、分析、优化、执行操作引擎等。
  • 第三层:存储引擎,负责MySQL中数据的存储和提取。

我们要知道MySQL的服务器层是不管理事务的,事务是由存储引擎实现的,

而MySQL中支持事务的存储引擎又属 InnoDB 使用的最为广泛,所以后续文中提到的存储引擎都以 InnoDB 为主。

而且,可以再简单介绍一下,MySQL数据更新流程,作为铺垫,具体如下图:

(1)redo log(重做日志)

redo *log 属于MySQL存储引擎*InnoDB的事务日志。

MySQL的数据是存放在磁盘中的,每次读写数据都需做磁盘IO操作,如果并发场景下性能就会很差。为此MySQL提供了一个优化手段,引入缓存Buffer Pool。 这个缓存中包含了磁盘中部分数据页(page)的映射,以此来缓解数据库的磁盘压力。当从数据库读数据时,首先从缓存中读取,如果缓存中没有,则从磁盘读取后放入缓存;

当向数据库写入数据时,先向缓存写入,此时缓存中的数据页数据变更,这个数据页称为脏页,BufferPool中修改完数据后会按照设定的更新策略,定期刷到磁盘中,这个过程称为刷脏页

如何保证数据不丢失 ,实现高可靠,实现事务持久性 ? 如果刷脏页还未完成,可MySQL由于某些原因宕机重启,此时Buffer Pool中修改的数据还没有及时的刷到磁盘中,就会导致数据丢失,无法保证事务的持久性。为了解决这个问题引入了redo log,redo Log如其名侧重于重做! 它记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页,且只能恢复到最后一次提交的位置。redo log用到了WAL(Write-Ahead Logging)技术,这个技术的核心就在于修改记录前,一定要先写日志,并保证日志先落盘,才能算事务提交完成。 有了redo log再修改数据时,InnoDB引擎会把更新记录先写在redo log中,再修改Buffer Pool中的数据,当提交事务时,调用fsync把redo log刷入磁盘。至于缓存中更新的数据文件何时刷入磁盘,则由后台线程异步处理。 注意:此时redo log的事务状态是prepare,还未真正提交成功,要等bin log日志写入磁盘完成才会变更为commit,事务才算真正提交完成。 这样一来即使刷脏页之前MySQL意外宕机也没关系,只要在重启时解析redo log中的更改记录进行重放,重新刷盘即可。

redo log 大小固定 redo log采用固定大小,循环写入的格式,当redo log写满之后,重新从头开始如此循环写,形成一个环状。 那为什么要如此设计呢? 因为redo log记录的是数据页上的修改,如果Buffer Pool中数据页已经刷磁盘后,那这些记录就失效了,新日志会将这些失效的记录进行覆盖擦除。

上图中的write pos表示redo log当前记录的日志序列号LSN(log sequence number),写入还未刷盘,循环往后递增; check point表示redo log中的修改记录已刷入磁盘后的LSN,循环往后递增,这个LSN之前的数据已经全落盘。 write pos到check point之间的部分是redo log空余的部分(绿色),用来记录新的日志; check point到write pos之间是redo log已经记录的数据页修改数据,此时数据页还未刷回磁盘的部分。 当write pos追上check point时,会先推动check point向前移动,空出位置(刷盘)再记录新的日志。

注意:redo log日志满了,在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求的,此刻MySQL的性能会下降。所以在并发量大的情况下,合理调整redo log的文件大小非常重要。

crash-safe 因为redo log的存在使得Innodb引擎具有了crash-safe的能力,即MySQL宕机重启,系统会自动去检查redo log,将修改还未写入磁盘的数据从redo log恢复到MySQL中。MySQL启动时,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。会先检查数据页中的LSN,如果这个 LSN 小于 redo log 中的LSN,即write pos位置,说明在redo log上记录着数据页上尚未完成的操作,接着就会从最近的一个check point出发,开始同步数据。

简单理解,比如:redo log的LSN是500,数据页的 LSN 是300,表明重启前有部分数据未完全刷入到磁盘中,那么系统则将redo log中LSN序号300到500的记录进行重放刷盘。

(2)undo log(回滚日志)

undo log也是属于MySQL存储引擎InnoDB的事务日志。

undo log属于逻辑日志,如其名主要起到回滚的作用,它是保证事务原子性的关键。

记录的是数据修改前的状态,在数据修改的流程中,同时会记录一条与当前操作相反的逻辑日志到undo log中。

(3)bin log(归档日志)

bin****log是一种数据库Server层(和什么引擎无关),以二进制形式存储在磁盘中的逻辑日志。

bin****log记录了数据库所有 DDL 和 DML 操作(不包含 SELECT 和 SHOW 等命令,因为这类操作对数据本身并没有修改)

默认情况下,二进制日志功能是关闭的。

可以通过以下命令查看二进制日志是否开启:

mysql> SHOW VARIABLES LIKE 'log_bin'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | log_bin | OFF | +---------------+-------+

bin log主要应用于MySQL主从模式(master-slave)中,主从节点间的数据同步;以及基于时间点的数据还原。


通过下图MySQL的主从复制过程,来了解下bin log在主从模式下的应用。

  • 用户在主库master执行 DDL 和 DML 操作,修改记录顺序写入bin log;
  • 从库slave的I/O线程连接上Master,并请求读取指定位置position的日志内容;
  • Master收到从库slave请求后,将指定位置position之后的日志内容,和主库bin log文件的名称以及在日志中的位置推送给从库;
  • slave的I/O线程接收到数据后,将接收到的日志内容依次写入到relay log文件最末端,并将读取到的主库bin log文件名和位置position记录到master-info文件中,以便在下一次读取用;
  • slave的SQL线程检测到relay log中内容更新后,读取日志并解析成可执行的SQL语句,这样就实现了主从库的数据一致;

基于时间点还原

我们看到bin log也可以做数据的恢复,而redo log也可以,那它们有什么区别?

  • 层次不同:redo log 是InnoDB存储引擎实现的,bin log 是MySQL的服务器层实现的,但MySQL数据库中的任何存储引擎对于数据库的更改都会产生bin log。
  • 作用不同:redo log 用于故障恢复(crash recovery),保证MySQL宕机也不会影响持久性;bin log 用于时间点恢复(point-in-time recovery),保证服务器可以基于时间点恢复数据和主从复制。
  • 内容不同:redo log 是物理日志,内容基于磁盘的页Page;bin log的内容是二进制,可以根据binlog_format参数自行设置。
  • 写入方式不同:redo log 采用循环写的方式记录;binlog 通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上。
  • 刷盘时机不同:bin log在事务提交时写入;redo log 在事务开始时即开始写入

bin log 与 redo log 功能并不冲突而是起到相辅相成的作用,需要二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。

(4)relay log(中继日志)

relay log日志文件具有与bin log日志文件相同的格式,从上边MySQL主从复制的流程可以看出,relay log起到一个中转的作用,slave先从主库master读取二进制日志数据,写入从库本地,后续再异步由SQL线程读取解析relay log为对应的SQL命令执行。

(5)slow query log

慢查询日志(slow query log): 用来记录在 MySQL 中执行时间超过指定时间的查询语句,在 SQL 优化过程中会经常使用到。通过慢查询日志,我们可以查找出哪些查询语句的执行效率低,耗时严重。

出于性能方面的考虑,一般只有在排查慢SQL、调试参数时才会开启,默认情况下,慢查询日志功能是关闭的。可以通过以下命令查看是否开启慢查询日志:

mysql> SHOW VARIABLES LIKE 'slow_query%'; +---------------------+--------------------------------------------------------+ | Variable_name | Value | +---------------------+--------------------------------------------------------+ | slow_query_log | OFF | | slow_query_log_file | /usr/local/mysql/data/iZ2zebfzaequ90bdlz820sZ-slow.log | +---------------------+--------------------------------------------------------+
(6)general query log

一般查询日志( general query log ):用来记录用户的所有操作,包括客户端何时连接了服务器、客户端发送的所有 SQL 以及其他事件,比如 MySQL 服务启动和关闭等等。

MySQL 服务器会按照它接收到语句的先后顺序写入日志文件。

由于一般查询日志记录的内容过于详细,开启后 Log 文件的体量会非常庞大,所以出于对性能的考虑,默认情况下,该日志功能是关闭的,通常会在排查故障需获得详细日志的时候才会临时开启。

我们可以通过以下命令查看一般查询日志是否开启,命令如下:

mysql> show variables like 'general_log'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | general_log | OFF | +---------------+-------+
(7)error log

错误日志(error log): 应该是 MySQL 中最好理解的一种日志,主要记录 MySQL 服务器每次启动和停止的时间以及诊断和出错信息。默认情况下,该日志功能是开启的,通过如下命令查找错误日志文件的存放路径。

mysql> SHOW VARIABLES LIKE 'log_error'; +---------------+--------------------------------------------------------------- -+ | Variable_name | Value | + ---------------+--------------------------------------------------------------- -+ | log_error | /usr/local/mysql/data/LAPTOP-UHQ6V8KP.err | +---------------+--------------------------------------------------------------- -+

错误日志中记录的可并非全是错误信息,像 MySQL 如何启动 InnoDB 的表空间文件、如何初始化自己的存储引擎,初始化 buffer pool 等等,这些也记录在错误日志文件中。

六、Innodb引擎

1、逻辑存储结构

  1. 表空间:一个mysql实例对应多个表空间,用于存储索引和数据
  2. 段:分为数据段、索引段、回滚段。一个段包含多个区
  3. 区:表空间的单元结构,每个区的大小为1M,默认情况下,页大小为16K,即一个区中有64个连续的页
  4. 页:innodb存储引擎中磁盘的最小存储单元
  5. 行:存储事务id,回滚指针等

2、内存结构

  1. Buffer pool:缓存池,增删改查首先操作的是缓存池中的数据,之后再以一定频率刷新磁盘
  2. Change buffer:更改缓冲区(针对非唯一的二级索引页),执行DML语句时,如果Buffer pool不存在对应数据,会先将数据变更存储在Change buffer,在未来,数据被读取时,在合并到Buffer pool中,一起刷新到磁盘
  3. Log buffer:日志缓冲区,用于保存redolog,undolog,定期刷新到磁盘中,默认大小16M
  4. 自适应hash索引:用于优化对Buffer pool数据查询
标签: java 面试 mysql

本文转载自: https://blog.csdn.net/u011241051/article/details/136251299
版权归原作者 码农滴自我修养 所有, 如有侵权,请联系我们删除。

“JAVA工程师面试专题-《Mysql》篇”的评论:

还没有评论