一、数据库基本概念

1.事务

事务的定义:事务是指一系列满足ACID特性的数据库操作,这一系列操作要么全部成功,要么全部失败。

ACID特性:

  • 原子性:事务是最小的操作单位,不可分割,要么全部成功然后commit,要么全部失败rollback到事务执行前的状态。回滚操作可以由回滚日志来实现,回滚日志中记载了事务执行了的全部操作,回滚是把这些操作返着执行一遍就好了。
  • 一致性:事务 的执行结果必须使数据从一个一致性状态变成另一个一致性状态。例如:对于一个用户表单中的用户名字段我们规定它不可重复,当一个事务要么执行成功要么失败回滚,最终结果都不能使用户名字段出现重复值,负责就破坏了一致性原则。
  • 隔离性:各个事务的执行相互不干扰,任何事务的内部操作对其他事务都是隔离的。
  • 持久性:事务执行的结果commit以后要持久化,保存到存储器中不能因为系统的奔溃而导致数据消失。

2.数据库并发一致性问题

在并发的情况下事务的隔离性并不好保证会出现一系列的问题:

2.保证事务隔离性的机制——加锁

乐观锁与悲观锁

  • 悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

  • 乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

锁粒度

数据库中的锁有行锁、页锁、表锁;

一般来说锁的粒度越小越好,最好只锁住可能并发操作的那一部分数据,这样发生锁争用的可能性越来越小以此来提高并发程度。但是锁操作(检查锁状态、获取锁、释放锁)也需要消耗系统资源,所以当一个表中有大量行锁时会消耗很多系统资源,所以我们要在并发程度和系资源开销之间做一个权衡。

在MySQL数据库中:MYISAM使用表锁,innodb使用行锁和表锁,默认为行锁。

锁类型

  1. 读写锁:阻塞的是对行、表数据的读写操作。

    • 排它锁(X):又称写锁。事务A对某数据加了排它锁之后可以对其进行更新、读取操作,同时其他事务不能对该数据再加任何锁。
    • 共享锁(S):又称读锁。事务A对某数据加了共享锁之后可以对其进行读取操作,同时其他事务也可以对其加共享锁。
  2. 意向锁:是表级别的锁,不会阻塞除全表扫描以外的任何请求。它更多的只是一个标识。
    当数据库中行锁与表锁共存时,当一个事务T需要对表A加读写锁时就需要判读该表是否已经加了S/X锁或者全表扫描去判断该表中的每个行是否有被加锁,这样以来效率就会非常低。所以当事务对一个表中数据加锁之前先对该表加意向锁IX或IS,这样当另一个事务需要给表加读写锁时就不需要去做全表扫描了,只需要判断一下该表是否加了意向锁IX/IS和读写锁X/S就行。

锁协议——解决并发一致性问题

  1. 三级加锁协议

    • 一级加锁协议:事务T对数据进行更新时必须加X锁,更新完毕后才释放。这样就不会出现两个事务对同一个数据进行修改的情况,也就解决了更新丢失问题。
    • 二级加锁协议:在一级的基础上要求事务T在读取数据时立马加上S锁读完后又立即释放。这样就不会读到另一个事务正在修改还未提交的数据,解决了脏读问题。
    • 三级加锁协议:在二级的基础上要求事务T读数据加了S锁后不要立即释放,直到T结束之后再释放,就不会出现前后读的数据不一致的问题,解决了不可重复读的问题。
  2. 两段锁协议
    简而言之就是加锁和解锁过程分阶段执行实现可串行化调度。也解决了并发一致性问题。
    可串行化调度:指通过并发控制,使得并发执行的事务结果与串行执行的事务结果一致。

3.事务的隔离级别

4.多版本并发控制(MVCC)

什么是MVCC?

MVCC叫做多版本并发控制,是一种通过保存数据在某个时间点的快照来实现的。一般用在数据库的并发访问管理中。它的优点是能够实现大部分访问不加锁,非阻塞并发读,避免了过多的开销,写操作也只需要锁定特定的行。

  • 版本号

    • 系统版本号:系统版本号会随着每创建一个事务而自增。
    • 事务版本号:事务版本号是创建事务时的系统版本号。
  • 隐藏列:每行数据的后面都有两行隐藏的列

    • 创建版本号:数据创建时的系统版本号
    • 删除版本号:数据删除时的系统版本号
  • Undo日志:MVCC 使用到的快照存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。

MVCC工作原理

1.插入数据(insert):记录的版本号即当前事务的版本号。

执行一条数据语句:insert into testmvcc values(1,”test”);

假设事务id为1,那么插入后的数据行如下:

数据库 - 图1

2、在更新操作的时候,采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。

比如,针对上面那行记录,事务Id为2 要把name字段更新

update table set name= ‘new_value’ where id=1;

数据库 - 图2

3、删除操作的时候,就把事务版本号作为删除版本号。

比如 delete from table where id=1;

数据库 - 图3

4、查询操作:

从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来:

1) 删除版本号未指定或者大于当前事务版本号,即查询事务开启后确保读取的行未被删除。(即上述事务id为2的事务查询时,依然能读取到事务id为3所删除的数据行)

2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在当前事务中(等于的情况)或者在当前事务启动之前的其他事物进行的insert。(即事务id为2的事务只能读取到create version<=2的已提交的事务的数据集)

补充:

1.MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read).

2.Read uncimmitted由于存在脏读,即能读到未提交事务的数据行,所以不适用MVCC.原因是MVCC的创建版本和删除版本只要在事务提交后才会产生。

3.串行化由于是会对所涉及到的表加锁,并非行锁,自然也就不存在行的版本控制问题。

4.通过以上总结,可知,MVCC主要作用于事务性的,有行锁控制的数据库模型。

快照读与当前读

1. 快照读

使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。

  1. select * from table ...;

2. 当前读

读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。

  1. select * from table where ? lock in share mode;
  2. select * from table where ? for update;
  3. insert;
  4. update;
  5. delete;

Next-Key Locks解决MVCC幻读问题

Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。

在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。

  • Record Locks

锁定一个记录上的索引,而不是记录本身。

如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用。

  • Gap Locks

锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。

  1. SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
  • Next-Key Locks

它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。例如一个索引包含

以下值:10, 11, 13, and 20,那么就需要锁定以下区间:

  1. (-∞, 10]
  2. (10, 11]
  3. (11, 13]
  4. (13, 20]
  5. (20, +∞)

5.三范式

数据库的设计范式是数据库设计所需要满足的规范,满足这些规范的数据库是简洁的、结构明晰的。

不符合范式的关系,会产生很多异常,主要有以下四种异常:

  • 冗余数据

  • 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。

  • 删除异常:删除一个信息,那么也会丢失其它信息。

  • 插入异常

范式

1.第一范式(确保每列保持原子性)

第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。

第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。

数据库 - 图4

上表所示的用户信息遵循了第一范式的要求,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。

2.第二范式(确保表中的每列都和主键相关)

第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。

比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下表所示。

订单信息表

数据库 - 图5

这样就产生一个问题:这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。

而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。如下所示。

数据库 - 图6

这样设计,在很大程度上减小了数据库的冗余。如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。

3.第三范式(确保每列都和主键列直接相关,而不是间接相关)

第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。如下面这两个表所示的设计就是一个满足第三范式的数据库表。

数据库 - 图7

这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必在订单信息表中多次输入客户信息的内容,减小了数据冗余。

二、MySQL数据库

1.实现索引的数据结构——常见的那些树

在此介绍InnoDB 存储引擎中的 B+ 树索引。要介绍 B+ 树索引,就不得不提二叉查找树,平衡二叉树和 B 树这三种数据结构。B+ 树就是从他们仨演化来的。

二叉查找树(BST)

二叉查找树的特点就是任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。

数据库 - 图8

如果我们需要查找id==12的记录利用二叉查找树我们只需要 3 次即可找到匹配的数据。如果在表中一条条的查找的话,我们需要 6 次才能找到。

平衡二叉树(AVL)

AVL 树在满足二叉查找树特性的基础上,要求每个节点的左右子树的高度差不能超过 1。

AVL树解决了BST树在极端不平衡条件下变成线性查找到的情况。 AVL相比BST来说,查找效率更稳定,总体的查找速度也更快。

下面是平衡二叉树和非平衡二叉树的对比:数据库 - 图9

红黑树(R-B Tree)

R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意
从根到叶子的最长路径不多于最短路径的两倍长,因而红黑树是相对是接近平衡的二叉树。

它可以在O(log n)时间复杂度做查找,插入和删除。通过左旋和右旋来保证红黑树的特性不会被破坏。相对于要求严格的AVL树来说,它的旋转次数变少,所以对于搜索,插入,删除操作多的情况下,我们就用红黑树。

数据库 - 图10

B /B-树(Balanced-Tree)

因为内存的易失性。一般情况下,我们都会选择将 user 表中的数据和索引存储在磁盘这种外围设备中。但是和内存相比,从磁盘中读取数据的速度会慢上百倍千倍甚至万倍,所以,我们应当尽量减少从磁盘中读取数据的次数。另外,从磁盘中读取数据时,都是按照磁盘块来读取的,并不是一条一条的读。如果我们能把尽量多的数据放进磁盘块中,那一次磁盘读取操作就会读取更多数据,那我们查找数据的时间也会大幅度降低。如果我们用树这种数据结构作为索引的数据结构,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块。我们都知道平衡二叉树可是每个节点只存储一个键值和数据的。那说明什么?说明每个磁盘块仅仅存储一个键值和数据!那如果我们要存储海量的数据呢?可以想象到二叉树的节点将会非常多,高度也会极其高,我们查找数据时也会进行很多次磁盘 IO,我们查找数据的效率将会极低!

为了解决平衡二叉树的这个弊端,我们应该寻找一种单个节点可以存储多个键值和数据的平衡树。也就是我们接下来要说的 B 树。

B 树(Balance Tree)即为平衡树的意思,下图即是一棵 B 树:

数据库 - 图11

图中的 p 节点为指向子节点的指针,二叉查找树和平衡二叉树其实也有,因为图的美观性,被省略了。

图中的每个节点称为页,页就是我们上面说的磁盘块,在 MySQL 中数据读取的基本单位都是页,所以我们这里叫做页更符合 MySQL 中索引的底层数据结构。

从上图可以看出,B 树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子节点,子节点的个数一般称为阶,上述图中的 B 树为 3 阶 B 树,高度也会很低。

基于这个特性,B 树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。

假如我们要查找 id=28 的用户信息,那么我们在上图 B 树中查找的流程如下:

  • 先找到根节点也就是页 1,判断 28 在键值 17 和 35 之间,那么我们根据页 1 中的指针 p2 找到页 3。
  • 将 28 和页 3 中的键值相比较,28 在 26 和 30 之间,我们根据页 3 中的指针 p2 找到页 8。
  • 将 28 和页 8 中的键值相比较,发现有匹配的键值 28,键值 28 对应的用户信息为(28,bv)。

B+ 树

B+ 树是对 B 树的进一步优化。让我们先来看下 B+ 树的结构图:

数据库 - 图12

根据上图我们来看下 B+ 树和 B 树有什么不同:

①B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。

之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。另外,B+ 树的阶数是等于键值的数量的,如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。

②因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。

那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。

有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。

其实上面的 B 树我们也可以对各个节点加上链表。这些不是它们之前的区别,是因为在 MySQL 的 InnoDB 存储引擎中,索引就是这样存储的。

也就是说上图中的 B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。

通过上图可以看到,在 InnoDB 中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。

MyISAM 中的 B+ 树索引实现与 InnoDB 中的略有不同。在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。

2.聚集索引 VS 非聚集索引

在上节介绍 B+ 树索引的时候,我们提到了图中的索引其实是聚集索引的实现方式。

那什么是聚集索引呢?在 MySQL 中,B+ 树索引按照存储方式的不同分为聚集索引和非聚集索引。

这里我们着重介绍 InnoDB 中的聚集索引和非聚集索引:

①聚集索引(聚簇索引):以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。

这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。

这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。

②非聚集索引(非聚簇索引):以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。

非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。

明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。

3.利用聚集索引和非聚集索引查找数据

前面我们讲解 B+ 树索引的时候并没有去说怎么在 B+ 树中进行数据的查找,主要就是因为还没有引出聚集索引和非聚集索引的概念。

下面我们通过讲解如何通过聚集索引以及非聚集索引查找数据表中数据的方式介绍一下 B+ 树索引查找数据方法。

利用聚集索引查找数据

数据库 - 图13

还是这张 B+ 树索引图,现在我们应该知道这就是聚集索引,表中的数据存储在其中。

现在假设我们要查找 id>=18 并且 id<40 的用户数据。对应的 sql 语句为:

MySQL

1 select * from user where id>=18 and id <40

其中 id 为主键,具体的查找过程如下:

①一般根节点都是常驻内存的,也就是说页 1 已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。

从内存中读取到页 1,要查找这个 id>=18 and id <40 或者范围值,我们首先需要找到 id=18 的键值。

从页 1 中我们可以找到键值 18,此时我们需要根据指针 p2,定位到页 3。

②要从页 3 中查找数据,我们就需要拿着 p2 指针去磁盘中进行读取页 3。

从磁盘中读取页 3 后将页 3 放入内存中,然后进行查找,我们可以找到键值 18,然后再拿到页 3 中的指针 p1,定位到页 8。

③同样的页 8 页不在内存中,我们需要再去磁盘中将页 8 读取到内存中。

将页 8 读取到内存中后。因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值 18。

此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值 18 对应的数据。

因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页 8 中的键值依次进行遍历查找并匹配满足条件的数据。

我们可以一直找到键值为 22 的数据,然后页 8 中就没有数据了,此时我们需要拿着页 8 中的 p 指针去读取页 9 中的数据。

④因为页 9 不在内存中,就又会加载页 9 到内存中,并通过和页 8 中一样的方式进行数据的查找,直到将页 12 加载到内存中,发现 41 大于 40,此时不满足条件。那么查找到此终止。

最终我们找到满足条件的所有数据,总共 12 条记录:

(18,kl), (19,kl), (22,hj), (24,io), (25,vg) , (29,jk), (31,jk) , (33,rt) , (34,ty) , (35,yu) , (37,rt) , (39,rt) 。

下面看下具体的查找流程图

数据库 - 图14

利用非聚集索引查找数据

数据库 - 图15

读者看到这张图的时候可能会蒙,这是啥东西啊?怎么都是数字。如果有这种感觉,请仔细看下图中红字的解释。

什么?还看不懂?那我再来解释下吧。首先,这个非聚集索引表示的是用户幸运数字的索引(为什么是幸运数字?一时兴起想起来的:-)),此时表结构是这样的。

数据库 - 图16数据库 - 图17

在叶子节点中,不再存储所有的数据了,存储的是键值和主键。对于叶子节点中的 x-y,比如 1-1。左边的 1 表示的是索引的键值,右边的 1 表示的是主键值。

如果我们要找到幸运数字为 33 的用户信息,对应的 sql 语句为:

MySQL

1 select * from user where luckNum=33

查找的流程跟聚集索引一样,这里就不详细介绍了。我们最终会找到主键值 47,找到主键后我们需要再到聚集索引中查找具体对应的数据信息,此时又回到了聚集索引的查找流程。

下面看下具体的查找流程图:

数据库 - 图18

在 MyISAM 中,聚集索引和非聚集索引的叶子节点都会存储数据的文件地址。

转载:刘召考的博客 MySQL索引-B+树(看完你就明白了)

三、MySQL数据库面试题

作者:擎宇要努力努力再努力
链接:https://www.nowcoder.com/discuss/593943?channel=-1&source_id=profile_follow_post_nctrack
来源:牛客网

数据库概念

1. ACID

ACID是事物的四个特性。分别是 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability) 。

  • 原子性是指事物是一个不可分割的工作单位,事物中的操作要么都发生,要么都不发生。最经典的就是转账案例,我们把转入和转出当做一个事物的话,就需要在SQL中显式指定开启事务。

  • 一致性是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性 。我们可以从数据库层面和业务层面两方面来保证,数据库层面我们可以设置触发器,外键,表,行约束等来保证,业务层面就是我们Java工程师的工作啦

  • 隔离性指的是多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。 多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。 这个点又引申出了下面两道题,以及后边的加锁和阻塞

  • 持久性意味着即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中,不会被回滚

2. 说一下脏读,不可重复读和幻读

  • 脏读: 意味着一个事务读取了另一个事务未提交的数据,而这个数据是有可能回滚的。即这个事物读取的数据是不正确的

  • 不可重复读: 在数据库访问中,一个事务范围两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。即这个事物在读的过程中被修改了

  • 幻读:当一个事物对整个table进行修改之后,第二个事物向表中插入了一行数据,此时第一个事物发现了新插入的没有修改的数据行,好像发生了幻觉一样

3. 数据库的隔离级别

读未提交RU

  • 一个事务还没提交时,它做的变更就能被别的事务看到

  • 会出现幻读,不可重复读,脏读

  • 更新数据时加上行级共享锁,事物结束即释放

读已提交RC

  • 一个事务提交之后,它做的变更才会被其他事务看到

  • 会出现幻读,不可重复读,不会出现脏读

  • 写数据加行级排他锁,这样写过程是无法读取的,直到事务处理完毕才释放排他锁,给读的数据加行级共享锁,这样读的时候也是无法写的,但是一旦读完该行就释放共享锁

  • MySQL会在SQL语句开始执行时创建一个视图

可重复读RR

  • 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的

  • 会出现幻读,不会出现不可重复读,脏读

  • 给写的数据加行级排他锁,事务结束释放,给读的数据加行级共享锁,事务结束后释放

  • MySQL会在事物开始时创建一个一致性视图(接下面的MVCC),事物结束时销毁

可串行化S

  • 当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行

  • 不会出现幻读,不可重复读,脏读

  • 事务读数据则加表级共享锁,事务写数据则加表级排他锁

  • 不区分快照度与当前读

其中,Oracle和SQLServer都是读已提交,但MySQL默认的隔离级别是可重复读 ,这是一个MySQL5.0之前的上古遗留版本问题。当时的binlog只有STATEMENT格式,用RC会出现bug。

4. 三大范式

  • 第一范式: 所有字段值都是不可分解的原子值 。例如有一个列是电话号码一个人可能有一个办公电话一个移动电话。第一范式就需要拆开成两个属性。

  • 第二范式:非主属性完全函数依赖于候选键。如PersonID,ProductID,ProductName,PersonName可以看到,OrderID和ProductID是联合主键,但是ProductName是依赖于ProductID的,只依赖了部分主键,没有依赖全部主键。需要拆分成三个表:PersonID, PersonName,ProductID, ProductName和PersonID, ProductID

  • 第三范式: 每一列数据都和主键直接相关,而不能间接相关

  • 如OrderID,ProductID,ProductName,OrderID是主键,但是ProductID依赖了OrderID,而ProductName依赖了ProductID,等于说是间接依赖了OrderID,所以需要拆分为两个表:OrderID, ProductID和ProductID, ProductName

这里需要指明范式不是最好的,我们需要混合使用范式和反范式

  • 范式的优点:因为相对来说有较少的重复数据,范式化的更新操作要比反范式快。同时范式化需要更少的distinct和order by

  • 范式化缺点:通常需要关联,不仅代价昂贵,也可能会使的一些索引无效

常用的反范式方法

  • 复制:在两个表中根据实际业务情况存储部分相同的字段列,即有利于查询,也不会把表搞的太大

  • 缓存:对于需要多次join查询的表,可以在一个表中加入一个缓存列,用来缓存所join表的部分常用数据,如count等,我们需要实时更新该缓存

5. 说一下内连接和外连接

内连接也叫自然连接,只有两个表相匹配的行才能在结果集中出现。返回的结果集选取两个表中所匹配的数据,舍弃不匹配的数据

  • | 1 | select fieldlist from table1 [inner] join table2 on table1.column = table2.column | | —- | —- | | | |

内连接保证两个表中的所有行都满足条件,而外连接则不然,外连接不仅仅包含符合连接条件的行,而且还包括左表(左外连接),右表(右外连接),或者两个边表(全外连接)中的所有数据行

1 select fieldlist from table1 left/ right outer join table2 on table1.column = table2.column

MySQL索引

索引是一种数据结构,用于帮助我们在大量数据中快速定位到我们想要查找的数据。可以加快查的速度,但是会增加容量,降低增,删,改的速度

1. MySQL有哪几种索引类型,各自特点

常见的MySQL索引结构有B-树索引,B+树索引,Hash索引和全文索引

B-Tree索引

  • 因为存储引擎不用进行全表扫描来获取数据,直接从索引的根节点开始搜索,从而能加快访问数据的速度

  • B-Tree对索引是顺序组织存储的,很适合查找范围数据

  • 适用于全键值、键值范围或者键前缀查找(根据最左前缀查找)

  • 限制:对于联合索引来说,如果不是从最左列开始查找,则无法使用索引;不能跳过索引中的列

B+Tree索引

  • 是B-Tree索引的变种,现在主流的存储引擎都不用单纯的B-Tree,而是其变种B+Tree或者T-Tree等等

  • 和B-Tree最主要的区别就是B+Tree的内节点不存储data,只存储key,叶子节点不存储指针

Hash

  • 基于Hash表实现,只有Memory存储引擎显式支持哈希索引

  • 适合等值查询,如=、in()、<=>,不支持范围查询

  • 因为不是按照索引值顺序存储的,就不能像B+Tree索引一样利用索引完成排序

  • Hash索引在查询等值时非常快

  • 因为Hash索引始终索引的所有列的全部内容,所以不支持部分索引列的匹配查找

  • 如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题

  • 程序员可以在B+Tree索引的基础上创建自适应Hash索引

全文索引

  • MyISAM和InnoDB都支持全文索引

  • 有三种模式:自然语言模式,布尔模式和查询扩展模式

R-Tree索引

  • MyISAM支持R-Tree索引,这个和全文索引基本不问

2. B+树索引和hash索引的区别

  • B+树索引适合返回查找,而hash索引适合等值查询

  • hash索引无法利用索引完成排序,但是B+树索引可以

  • hash索引不支持多了联合索引的最左匹配规则,但是B+树索引支持

  • 如果有大量重复键值的情况下,因为存在hash碰撞,hash索引的效率会很低

3. B树和B+树的区别

这个题其实偏向于数据结果了,这里不多讲,只说一下针对数据库来说,面试该怎么回答。派生问题:二叉树,二叉搜索树,B树,B+树。遇到坑爹的面试官可能让你写一个B+树…

二叉树

  • 任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值

平衡二叉树/AVL树

  • 二叉树非常极端,变成一个链表后,它就没有了二叉树的相关优秀性质了。所以我们在insert节点的时候,需要不断的旋转,来使二叉树平衡,最终使得其查询效率最高。调整一共分为四种情况:LL,RR,LR,RL

B-树

因为数据库中大部分数据都存在于磁盘,但是IO一次磁盘的代价相对来说比较大,我们需要尽可能的减少AVL树的深度,即增加每个节点的数据量。这便是B-树的由来

  • 每一个节点称为页,也就是一个磁盘块。 B树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子节点

B+树

  • 是B-树的变形,相对于B-树来说,B+树最主要的不同之处就是其非叶子节点上是不存储数据的,数据全在叶子节点存储。这就意味着B+树比B-树更胖

  • 因为B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而B树因为数据分散在各个节点,要实现这一点是很不容易的

4. InnoDB为什么要使用B+树作为索引

这时说出B+树索引的优点即可,同时也可能会引出Hash索引和全文索引

InnoDB中索引即数据,数据即索引

5. 怎么查看MySQL语句有没有用到索引

通过explain,如

1 EXPLAIN SELECT * FROM employees.titles WHERE emp_no=’10001’ AND title=’Senior Engineer’ AND from_date=’1986-06-26’;
id select_type table partitions type possible_keys key key_len ref filtered rows Extra
1 SIMPLE titles null const PRIMARY PRIMARY 59 const,const,const 10 1

id:在⼀个⼤的查询语句中每个SELECT关键字都对应⼀个唯⼀的id ,如explain select * from s1 where id = (select id from s1 where name = ‘egon1’);第一个select的id是1,第二个select的id是2。有时候会出现两个select,但是id却都是1,这是因为优化器把子查询变成了连接查询

select_type:select关键字对应的那个查询的类型,如SIMPLE,PRIMARY,SUBQUERY,DEPENDENT,SNION

table:每个查询对应的表名

type:执行查询的访问方法,如const(主键索引或者唯一二级索引进行等值匹配的情况下),ref(普通的⼆级索引列与常量进⾏等值匹配),index(扫描全表索引的覆盖索引)

possiblekey:查询中可能用到的索引(可以把用不到的删掉,降低优化器的优化时间)_

key:查询中用到的索引

filtered:查询器预测满足下一次查询条件的百分比

extra:表示额外信息,如Using where,Start temporary,End temporary,Using temporary等

6. 聚簇索引与非聚簇索引

  • 聚簇索引和非聚簇索引是建立在B+树的基础上

  • 聚簇索引:key为主键,value为其余列的数据。一个表只能有一个聚簇索引

非聚簇索引:除了聚簇索引外的都叫非聚簇索引

  • 对于MyISAM的主键索引来说,它的非聚簇索引是key为主键,value为行号(不一定)

  • 对于MyISAM的二级索引来说,它的非聚簇索引是key为其他列,value为行号(不一定)

  • 对于InnoDB的二级索引来说,它的非聚簇索引是key为其他列,value是主键

  • 非聚簇索引也叫二级索引

  • 非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键(行号)

  • 对于InnoDB来说,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。第一次索引一般是顺序IO,回表的操作属于随机IO。需要回表的次数越多,即随机IO次数越多,我们就越倾向于使用全表扫描

  • 通常情况下, 主键索引查询只会查一次,而非主键索引(非聚簇索引)需要回表查询多次。当然,如果是覆盖索引的话,查一次即可

  • 注意:MyISAM无论主键索引还是二级索引都是非聚簇索引,而InnoDB的主键索引是聚簇索引,二级索引是非聚簇索引。我们自己建的索引基本都是非聚簇索引

7. 覆盖索引

如果一个索引包含(覆盖)所有需要查询字段的值,我们就称之为”覆盖索引”。如select id from tab where id = 1,并且id是tab的索引,这就是一个覆盖索引。

当非聚簇索引是覆盖索引的时候,就只用查询一次即可

8. 组合索引

因为每个select只能选择一个索引,当where条件过多时,我们可以考虑建立联合索引,即把多个列作为索引:

1 create index inx_col1_col2 on tab (col1,col2);

问这个问题主要是引出下面的最左前缀原则

9. MySQL的最左前缀原则

这里主要是向面试官说明组合索引在B+树上如何被创建的,对于索引(a,b,c),引擎会先按照a排序,当a相等时,再按照b排序,当b相等时,再按照c排序

对于索引(a,b,c)来说,能命中的where语句有

  1. where a = 1,where a = 1 and b = 1和where a = 1 and b = 1 and c = 1
  2. where a like ‘1%’,对于这个,可能会引出前缀索引

10. 前缀索引

因为可能我们索引的字段非常长,这既占内存空间,也不利于维护。所以我们就想,如果只把很长字段的前面的公共部分作为一个索引,就会产生超级加倍的效果。但是,我们需要注意,order by不支持前缀索引

流程是:

先计算完整列的选择性

1 select count(distinct col_1)/count(1) from table_1

再计算不同前缀长度的选择性

1 select count(distinct left(col_1,4))/count(1) from table_1

找到最优长度之后,创建前缀索引

1 create index idx_front on table_1 (col_1(4))

11. 索引下推

MySQL 5.6引入了索引下推优化。默认开启,使用SET optimizer_switch = ‘index_condition_pushdown=off’;可以将其关闭。

  • 有了索引下推优化,可以在减少回表次数

  • 在InnoDB中只针对二级索引有效

譬如,在 people_table中有一个二级索引(zipcode,lastname,firstname),查询是SELECT * FROM people WHERE zipcode=’95054′ AND lastname LIKE ‘%etrunia%’ AND address LIKE ‘%Main Street%’;

  1. 如果没有使用索引下推技术,则MySQL会通过zipcode=’95054’从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastname LIKE ‘%etrunia%’ and address LIKE ‘%Main Street%’来判断数据是否符合条件

  2. 如果使用了索引下推技术,则MYSQL首先会返回符合zipcode=’95054’的索引,然后根据lastname LIKE ‘%etrunia%’ and address LIKE ‘%Main Street%’来判断索引是否符合条件。如果符合条件,则根据该索引来定位对应的数据,如果不符合,则直接reject掉

12. 查询在什么情况下不走索引

首先,我们可以说通过explain去排查一个慢查询,进而找到它的索引(参看第五题),当创建索引却不走索引时,我们就需要考虑到优化器的问题。

在一条单表查询语句真正执行之前,MySQL的查询优化器会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案。这个成本最低的方案就是所谓的执行计划。 优化过程大致如下:

  1. 根据搜索条件,找出所有可能使用的索引
  2. 计算全表扫描的代价
  3. 计算使用不同索引执行查询的代价
  4. 对比各种执行方案的代价,找出成本最低的那一个

那么,有哪几种情况明明设了索引却不走索引呢?

假设索引为(a,b,c)

  • ASC和DESC索引混合使用的排序:select * from tab order by a, b desc limit 10;

  • 违背最左前缀原则:select * from tab where b = ‘1’;

  • WHERE⼦句中出现非排序使⽤到的索引列:select * from tab d = ‘1’ order by a limit 10;

  • 排序列包含非同⼀个索引的列:select * from tab order by a, d limit 10;

  • WHERE子句中出现计算:select from tab where a 4 = 2;

  • WHERE子句中出现null值:select * from tab where a = null;

  • WHERE子句中使用!=或<>操作符:select * from tab where a != 1;

13. MySQL如何为表字段添加索引

下面是添加索引的SQL语法

1.添加主键索引

1 ALTER TABLE table_name ADD PRIMARY KEY ( column )

2.添加唯一索引

1 ALTER TABLE table_name ADD UNIQUE ( column )

3.添加普通索引

1 ALTER TABLE table_name ADD INDEX index_name ( column )

4.添加全文索引(适用于MyISAM,InnoDB 5.6+)

1 ALTER TABLE table_name ADD FULLTEXT ( column)

5.添加联合索引

1 ALTER TABLE table_name ADD INDEX index_name ( column1, column2, column3 )

14. 如何选择索引

  1. 只为⽤于搜索、排序或分组的列创建索引
  2. 考虑列的基数 ,基数越大,创建索引的效果越好
  3. 索引列的类型尽量⼩,这样B+树中每个页存储的数据就会更多
  4. 写多读少尽量不要建立索引
  5. 可以使用倒叙索引或者hash索引
  6. InnoDB的主键尽量用MySQL的自增主键

15. 唯一索引和普通索引选择哪个?

  • 唯一索引和普通索引在读取的时候效率基本差不多,普通索引差了一点点。主要是判断和特殊情况下的一次IO

  • 写入的时候,普通索引可以利用change buffer,适合写多读少,比唯一索引要快

以业务为前提,如果要求唯一,就要选择唯一索引。如果已经保证列的唯一,我们尽量选择普通索引,然后把change buffer调大

MySQL的优化

1. MySQL的查询优化

  • 上文中的如何选择索引

  • 使用连接代替子查询

  • 尽量用IN代替OR,OR的效率是n级别,IN的效率是log(n)级别,IN的个数建议控制在200以内

  • 能用BETWEEN不用IN

  • 尽量用LIMIT,同时尽量不用count *

  • 切分一个连接时间很长的查询,或返回数据量很大的查询

  • 分解关联查询,在应用层做关联,可以更容易对数据库进行拆分,减少锁的竞争,减少冗余记录的查询

2. MySQL大数据量下的优化

建表时

  • 此处考察如何建立索引

  • 字段类型尽量精确,尽量小,能用int不要用bigint

  • 尽量不要用null,声明not null,如果是null用0代替

  • 尽量使用TIMESTAMP而非DATETIME

  • 用整型来存ip

  • 注意反范式和范式的优化

查询时

  • 参考查询优化

  • 参考前面的查询在什么情况下不走索引

加缓存NoSQL

  • Memcached

  • Redis

分区

  • MySQL内置的是水平分区,分为range,list,hash,key

  • 在分区的基础上还可以有子分区,一个分区创建子分区,其他分区也要创建子分区;每个分区的子分区数必须相同;所有分区必须使用相同的存储引擎

  • 分区表的数据还可以分布在不同的物理设备上

分表 & 分表

  • 垂直分表:把表中的一些字段移到其他表或者新建表

  • 水平分表:和分区类似

  • 垂直分库:把不同数据表分到不同库,不同服务器上

  • 可以使用MyCat等中间件来实现

  • 换Hadoop家族

MySQL存储引擎

1. MySQL常用的引擎

MySQL常见的存储引擎主要有三个,分别是InnoDB,Memory和MyISAM

2. InnoDB和MySIAM的区别

  • 面试官常常会问到如何选择MyISAM和Innodb,这其实是一个早期DBA的问题,但是现在在事实上早已经不存在这个问题了,Innodb不断完善,从各个方面赶超了MyISAM,成为了MySQL默认的存储引擎

相关差异

差异 MyISAM InnoDB
文件格式 数据和索引是分别存储的,数据.MYD,索引.MYI 数据和索引是集中存储的.ibd
文件能否移动 能,一张表就对应.frm、MYD、MYI3个文件 否,因为关联的还有data下的其它文件
记录存储顺序 按记录插入顺序保存 按主键大小有序插入
空间碎片(删除记录并flush table table_name之后,表文件大小不变) 产生。定时整理:使用命令optimize table table_name实现 不产生
事务 不支持 支持
外键 不支持 支持
锁支持 表级锁定 行级锁定、表级锁定

锁差异

  • MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待

  • MyISAM因为是表锁,只有读读之间是并发的,写写之间和读写之间是串行的

  • 而在InnoDB中,锁是逐步获得的,就造成了死锁的可能

  • Innodb的行级锁不是直接锁记录,而是锁索引,即行锁是锁索引的,如果没有索引,InnoDB也会锁表

MyISAM和InnoDB的B+树索引实现机制不一样

  • MyISAM使用前缀压缩技术使得索引变得更小,但InnoDB则按照原数据格式进行存储

  • MyISAM索引通过数据的物理位置引出被索引的行,但InnoDB则根据主键引用被索引的行;即MyISAM索引文件和数据文件是分离的,但是InnoDB主键索引的key就是数据表的主键,data就是表中的数据

  • MyISAM的二级索引和主索引结构没有区别,但是二级索引的key可以不唯一;InnoDB二级索引data域存储相应记录主键的值而不是地址

  • 对于InnoDB来说,其主键尽量使用MySQL自带的与业务无关的自增主键

  • MyISAM引擎把一个表的总行数存在了磁盘上,因此执行count()的时候会直接返回这个数,效率很高;而InnoDB引擎就麻烦了,它执行count()的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数,这是因为InnoDB的MVCC

  • MyISAM的一般查询效率比InnoDB高,但是InnoDB的主键索引查询除外

  • InnoDB独有redo log,但MyISAM之后binlog,下面有详细讲这两种log

如何选择

  • 默认Innodb

  • MyISAM:以读为主的应用程序,比如博客系统、新闻门户网站

  • Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键保证数据完整性。比如OA自动化办公系统。

各种锁

MySQL内部的锁管理是极为透明的。

同时锁与并发关系较大,这篇博客扯的比较少,后续的并发面经会有说到

1. 乐观、悲观锁,行、表锁,读、写锁,间隙锁 (重要)

根据加锁范围

  • 全局锁:是对整个数据库实例加锁。常见的场景是全库逻辑备份(也就是把整库每个表都select出来存成文本),对于InnoDB可以用可重复读这一隔离级别进行备份,但是对于MyISAM只能用全局锁

表级锁

  • 表锁:lock tables t1 read, t2 write;那么该线程只能读t1,写t2,其他线程只能写t1,读t2

  • 元数据锁:即MDL,MySQL5.5版本引入。当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。读锁之间不互斥,读写锁之间、写锁之间是互斥的

  • MySQL所有引擎都支持表锁

行级锁

  • 由各个引擎自己实现

  • 即锁定某个表中的特定行,并发度高,锁粒度低

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议

  • 行锁容易产生死锁,此时需要使用InnoDB的主动死锁检测

  • 在InnoDB中,行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住

间隙锁(GAP)

  • 是一个范围小于表锁,大于行锁的锁,主要是为了防止幻读

  • 如果查询条件没有建立索引或者不是唯一索引,则会加上间隙锁(普通查询是快照读,这里不考虑)

  • 加锁范围是查询条件的两侧

根据锁的读写方式

  • 共享锁又称为读锁,简称S锁。共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

  • 排他锁又称为写锁,简称X锁。排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改

  • SS锁不互斥,SX和XX锁都互斥

根据锁的特征

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现

  • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号,如果不是,则重新更新。在JDK中的一些包里,如java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的

  • 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能

  • 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方

2. CAS和MVCC

CAS

即compare and swap,交换比较。CAS涉及到了3个操作符:

  • 需要读写的内存值V

  • 需要比较的值A

  • 拟写入的新值B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,需要通过自旋锁不断重试

  • 优点:是非阻塞的轻量级乐观锁

  • 缺点:会出现ABA问题,即如果一个值被修改后又被重新修改回来,我们能确定它没有被修改过吗?

当问到CAS的时候,基本上就转到Java并发了

MVCC

  • 数据库多版本并发控制,即每一行数据都是有多个版本的,每个版本有自己的row trx_id,即当时修改该行的transaction_id

  • 需要用到一致性读视图,即consistent read view,用于支持RC和RR隔离级别的实现,它没有物理结构,作用是事务执行期间用来定义“我能看到什么数据”,它其实是一个视图数组,和数据库中显式创建的create view …不一样

一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

  • 版本未提交,不可见;

  • 版本已提交,但是是在一致性视图创建后提交的,不可见;

  • 版本已提交,而且是在一致性视图创建前提交的,可见

在MVCC中有两种读,上面三种是快照读,还有一种是当前读

  • 当普通的select是快照读

  • 插入,删除,更新属于当前读,需要加锁,遵从两阶段锁协议

3. MySQL如何加锁

这个问题是十分的宽泛啊,如何加锁,基本就是关于MySQL的事物,锁,两阶段锁协议,以及不同引擎对锁和事物的处理,当然主要还是InnoDB,面试官会看你主要说什么东西,进而继续往下提问,所以这个地方尽量往自己懂的地方说就完事了。

  • MySQL普通读和修改等读是不一样的,普通读是利用了MVCC机制,而修改读是利用锁

  • 当前读 (select * from tab where a = 1 lock in share mode;)加S锁,而insert,update,delete加排他锁

  • 我们要注意,聚簇索引和非聚簇索引加锁的次数是不一样的

其他

1. MySQL中一条语句的执行过程

  • 我们首先要知道MySQL处理不同语句,如DDL,DML等是不一样的

  • MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行计划、执行器,同时还有binlog,引擎层包括了InnoDB,Memory,MyISAM等引擎插件,其中,InnoDB还有redo log

一条查询语句的执行过程

  1. client先发送一条查询给服务器,#连接器#(此时的通信协议的半双工的)

  2. 服务器先检查缓存,如果命中缓存,则返回结果,如果没有,进入下一阶段(查询缓存是通过大小写敏感的hash表实现的,但是MySQL8.0之后把缓存删除了)*

  3. 进行SQL解析#解析器#*,预处理#预处理器#,再由查询优化器#优化器#*生成对应的查询执行计划

  4. 根据优化计划来调用存储引擎的API来查询,并将结果返回给客户端

譬如如下查询:

1 select * from tb_student A where A.age=’18’ and A.name=’ 张三 ‘;

结合上面的说明,我们分析下这个语句的执行流程:

  1. 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
  2. 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id=’1’。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。

    接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:

    a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18

    b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生

  3. 优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)

  4. 进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果

redo log和binlog的区别

  1. redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用
  2. redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”
  3. redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志
  4. binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用