MySQL索引.png

1、请说下你对 MySQL 架构的了解?

image.png
大体来说, MySQL 可以分为 Server 层和存储引擎两部分。
Server 层包括:连接器、查询缓存、分析器、优化器、执行器等,涵盖了 MySQL 的大多数核心服务功能,以及所有的内置函数(如:日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如:存储过程、触发器器、视图等等。
存储引擎层:负责数据的存储和提取。其架构是插件式的,支持 InnoDB、 MyISAM 等多个存储引擎。MySQL5.5 版本开始默认的是InnoDB,但是在建表时可以通过 engine = MyISAM 来指定存储引擎。不同存储引擎的表数据存取方式不不同,支持的功能也不不同。
从上图中可以看出,不同的存储引擎共用一个 Server 层,也就是从连接器到执行器的部分。

2、一条 SQL 语句在数据库框架中的执行流程?

  1. 应用程序把查询 SQL 语句发送给服务器端执行;
  2. 查询缓存,如果查询缓存是打开的,服务器在接收到查询请求后,并不会直接去数据库查询,而是在数据库的查询缓存中找是否有相对应的查询数据,如果存在,则直接返回给客户端。只有缓存不存在时,才会进行下面的操作;
  3. 查询优化处理,生成执行计划。这个阶段主要包括解析 SQL、预处理、优化 SQL 执行计划;
  4. MySQL 根据相应的执行计划完成整个查询;
  5. 将查询结果返回给客户端。

    3、数据库的三范式是什么?

  • 第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项;
  • 第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性;
  • 第三范式:任何非主属性不依赖于其它非主属性。 在满足2NF的前提下,不存在传递依赖。(A -> B, B -> C, A->C)

    4、char 和 varchar 的区别?

  • char固定长度类型,比如:订阅 char(10),当你输入”abc”三个字符的时候,它们占的空间还是 10 个字节,其他 7 个是空字节。

    • 优点:效率高;
    • 缺点:占用空间;
    • 适用场景:存储密码的 md5 值,固定长度的,使用char 非常合适。
  • varchar : 可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。
    • 从空间上考虑 varcahr 比较合适;
    • 从效率上考虑 char 比较合适,二者使用需要权衡。

      5、varchar(10) 和 varchar(20) 的区别?

      varchar(10) 中 10 的涵义最多存放 10 个字符;
      varchar(10) 和 varchar(20) 存储 hello 所占空间一样,但后者在排序时会消耗更更多内存,因为 order by col 采用 fixed_length 计算 col 长度。

      6、谈谈你对索引的理解?

      索引的出现是为了提高数据的查询效率,就像书的目录一样。一本500页的书,如果你想快速找到其中的某一个知识点,在不借助目录的情况下,那我估计你可得找一会儿。同样,对于数据库的表而言,索引其实就是它的“目录”。**同样索引也会带来很多负面影响:创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;索引需要占用物理理空间,不光是表需要占用数据空间,每个索引也需要占用物理理空间;当对表进行增、删、改、的时候索引也要动态维护,这样就降低了了数据的维护速度。

索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。而且索引是一个文件,它是要占据物理空间的。
MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。比如我们在查字典的时候,前面都有检索的拼音和偏旁、笔画等,然后找到对应字典页码,这样然后就打开字典的页数就可以知道我们要搜索的某一个key的全部值的信息了
**建立索引的原则:

  1. 在最频繁使用的、用以缩小查询范围的字段上建立索引;
  2. 在频繁使用的、需要排序的字段上建立索引。

不适合建立索引的情况:

  1. 对于查询中很少涉及的列或者重复值比较多的列,不宜建立索引;
  2. 对于一些特殊的数据类型,不宜建立索引,比如:文本字段(text)等。

    7、索引有哪些优缺点?

    索引的优点
    可以大大加快数据的检索速度,这也是创建索引的最主要的原因。通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
    索引的缺点

  3. 时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;

  4. 空间方面:索引需要占物理空间。

    8、MySQL有哪几种索引类型?

  • 从存储结构上来划分:BTree索引(B-Tree或B+Tree索引),Hash索引,full-index全文索引,RTree索引。这里所描述的是索引存储时保存的形式,
  • 从应用层次来分:普通索引,唯一索引,复合索引。
    • 普通索引:即一个索引只包含单个列,一个表可以有多个单列索引;
    • 唯一索引:索引列的值必须唯一,但允许有空值;
    • 复合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并;
    • 聚簇索引(聚集索引):聚集索引就是按照每张表的主键构造一棵B+树,同时叶子节点中存放的即为整张表的行记录数,也将聚集索引的叶子节点称为数据页。聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。

由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。

  • 非聚簇索引: 不是聚簇索引,就是非聚簇索引;
    • 根据中数据的物理顺序与键值的逻辑(索引)顺序关系: 聚集索引,非聚集索引。

      9、讲一讲聚簇索引与非聚簇索引?

      在 InnoDB 里,索引B+ Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引,即将数据存储与索引放到了一块,找到索引也就找到了数据。
      而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引、二级索引。
      聚簇索引与非聚簇索引的区别:
  1. 聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。
  2. 对于InnoDB来说,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。第一次索引一般是顺序IO,回表的操作属于随机IO。需要回105表的次数越多,即随机IO次数越多,我们就越倾向于使用全表扫描 。
  3. 通常情况下, 主键索引(聚簇索引)查询只会查一次,而非主键索引(非聚簇索引)需要回表查询多次。当然,如果是覆盖索引的话,查一次即可;

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

10、联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
具体原因为:
MySQL使用索引时需要索引有序,假设现在建立了”name,age,school”的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。

11、讲一讲MySQL的最左前缀原则?

最左前缀原则就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。

  1. 一个 2 列的索引 (name, age)对 (name)、 (name, age) 上建立了索引;
  2. 一个 3 列的索引 (name, age, sex),对 (name)、 (name, age)、 (name, age, sex) 上建立了索引。
  1. 最左前缀匹配原则会一直向右匹配直到遇到范围查询(>、 <、 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 的顺序可以任意调整。
  2. = 和 in 可以乱序,比如: a = 1 and b = 2 and c = 3 建立 (a, b ,c) 索引可以任意顺序, MySQL 的优化器会优化成索引可以识别的形式。

    12、说一说索引的实现原理

    在MySQL中,索引是在存储引擎层实现的,不同存储引擎对索引的实现方式是不同的,下面我们探讨一下MyISAM和InnoDB两个存储引擎的索引实现方式。
    MyISAM索引实现:
    MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,MyISAM索引的原理图如下。这里假设表一共有三列,假设我们以Col1为主键,则上图是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。
    MySQL - 图4
    如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示。同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
    MySQL - 图5
    InnoDB索引实现:
    虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。
    第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
    下图是InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
    MySQL - 图6
    第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。下图为定义在Col3上的一个辅助索引。这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
    MySQL - 图7
    了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

    13、MySQL的索引为什么用B+树?

    B+树由B树和索引顺序访问方法演化而来,它是为磁盘或其他直接存取辅助设备设计的一种平衡查找树,在B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过指针进行链接。如下图:
    MySQL - 图8
    B+树索引在数据库中的一个特点就是高扇出性,例如在InnoDB存储引擎中,每个页的大小为16KB。在数据库中,B+树的高度一般都在2~4层,这意味着查找某一键值最多只需要2到4次IO操作,这还不错。因为现在一般的磁盘每秒至少可以做100次IO操作,2~4次的IO操作意味着查询时间只需0.02~0.04秒。

B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B(B-)树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对 IO读写次数就降低 了。
由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在 区间查询 的情况,所以通常B+树用于数据库索引。
Hash
虽然可以快速定位,但是没有顺序,IO复杂度高;
基于Hash表实现,只有Memory存储引擎显式支持哈希索引 ;
适合等值查询,如=、in()、<=>,不支持范围查询 ;
因为不是按照索引值顺序存储的,就不能像B+Tree索引一样利用索引完成排序;
Hash索引在查询等值时非常快 ;
因为Hash索引始终索引的所有列的全部内容,所以不支持部分索引列的匹配查找 ;
如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题 。
二叉树: 树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且IO代价高。
红黑树: 树的高度随着数据量增加而增加,IO代价高。

14、InnoDB存储结构

image.png
1.表空间
表空间可以看作是InnoDB引擎的最高层,所有数据都存放在表空间中。
2.段
表空间是由各个段组成的,常见的段有数据段,索引段,回滚段等。因为InnoDB存储引擎表是索引组织的,因此数据即索引,索引即数据。数据段存放的就是B+树的叶子节点,索引段存放的就是B+树的非叶子节点.
3.区
区是由连续的页组成的空间,在任何情况下每个区的大小都为1MB,为了保证区中页的连续性,InnoDB一次会从磁盘中申请4~5个区。在默认情况下,InnoDB存储引擎页的大小为16KB,即一个区中又64个连续的页。
4.页
页是InnoDB磁盘管理的最小单位,在InnoDB存储引擎中,默认每个页的大小为16KB。
B+树索引只能找到记录所在的页,但不能定位到记录在页中的具体位置,这需要通过 page directory 的二分查找得到具体的记录。然而,由于通过B+树索引得知所在的页后,InnoDB存储引擎会将页加载到缓冲池中,二分查找在内存中完成,速度很快,因此会忽略这个查询的开销。

15、匹配原则(案例)

B+树索引是基于B+树构建出来的有序结构,只有利用上它的有序性才能提高查询的效率。若不满足有序性的这个前提,则在这个索引中的查询是离散的,其效率反而更低。
假设有如下一张表:

  1. CREATE TABLE t (
  2. a VARCHAR(100),
  3. b VARCHAR(100),
  4. c VARCHAR(100),
  5. KEY idx_union(a,b,c)
  6. )ENGINE=INNODB;

与该索引匹配的SQL:
Y 表示会走索引,N 表示不会

  1. -- 匹配左前缀
  2. SELECT * FROM t WHERE a = ''; -- Y
  3. SELECT * FROM t WHERE b = ''; -- N
  4. -- 匹配列前缀
  5. SELECT * FROM t WHERE a LIKE 'x%'; -- Y
  6. SELECT * FROM t WHERE a LIKE '%x'; -- N
  7. SELECT * FROM t WHERE b LIKE 'x%'; -- N
  8. -- 全值匹配
  9. SELECT * FROM t WHERE a = '' AND b = '' AND c = ''; -- Y
  10. SELECT * FROM t WHERE c = '' AND b = '' AND a = ''; -- Y
  11. -- 匹配范围值
  12. SELECT * FROM t WHERE a BETWEEN '' AND ''; -- Y
  13. SELECT * FROM t WHERE b BETWEEN '' AND ''; -- N
  14. -- 全值匹配 + 范围匹配
  15. SELECT * FROM t WHERE a = '' AND b BETWEEN '' AND ''; -- Y
  16. SELECT * FROM t WHERE b = '' AND c BETWEEN '' AND ''; -- N
  17. SELECT * FROM t WHERE a BETWEEN '' AND '' AND b = ''; -- N

16、哪些情况需要创建索引

  1. 主键自动创建唯一索引;
  2. 频繁作为查询条件的字段应该创建索引;
  3. 查询中与其他表关联的字段,外键关系创建索引;
  4. 查询中统计或分组的字段;
  5. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度;如表t1中存在索引index_col1_col2_col3;select col1 from t1 where col1=’ac’ order by col3;该查询会使用到文件内排序,导致查询性能下降,因为排序字段与索引不匹配;select col1 from t1 where col1=’ac’ order by col2, col3;该查询性能不错。

    17、如何评估一个索引创建的是否合理?

    建议按照如下的原则来设计索引:

  6. 避免对经常更新的表进行过多的索引,并且索引中的列要尽可能少。应该经常用于查询的字段创建索引,但要避免添加不必要的字段。

  7. 数据量小的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
  8. 在条件表达式中经常用到的不同值较多的列上建立索引,在不同值很少的列上不要建立索引。比如在学生表的“性别”字段上只有“男”与“女”两个不同值,因此就无须建立索引,如果建立索引不但不会提高查询效率,反而会严重降低数据更新速度。
  9. 当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整性,以提高查询速度。
  10. 在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引。

    18、索引是越多越好吗?

    索引并非越多越好,一个表中如有大量的索引,不仅占用磁盘空间,还会影响INSERT、DELETE、UPDATE等语句的性能,因为在表中的数据更改时,索引也会进行调整和更新。

    19、数据库索引失效了怎么办?

    可以采用以下几种方式,来避免索引失效:

  11. 使用组合索引时,需要遵循“最左前缀”原则;

  12. 不在索引列上做任何操作,例如计算、函数、类型转换,会导致索引失效而转向全表扫描;
  13. 尽量使用覆盖索引(之访问索引列的查询),减少 select * 覆盖索引能减少回表次数;
  14. MySQL在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描;
  15. LIKE以通配符开头(%abc)MySQL索引会失效变成全表扫描的操作;
  16. 字符串不加单引号会导致索引失效(可能发生了索引列的隐式转换);
  17. 少用or,用它来连接时会索引失效。

    20、所有的字段都适合创建索引吗?

    不是。下列几种情况,是不适合创建索引的:

  18. 频繁更新的字段不适合建立索引;

  19. where条件中用不到的字段不适合建立索引;
  20. 数据比较少的表不需要建索引;
  21. 数据重复且分布比较均匀的的字段不适合建索引,例如性别、真假值;
  22. 参与列计算的列不适合建索引。

    21、什么是数据库事务?

    事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

    事务最经典也经常被拿出来说例子就是转账了。
    假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。

22、介绍一下事务具有的四个特征

事务就是一组原子性的操作,这些操作要么全部发生,要么全部不发生。事务把数据库从一种一致性状态转换成另一种一致性状态。

  1. 原子性。事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做;
  2. 一致性。事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
  3. 隔离性。一个事务的执行不能其它事务干扰。即一个事务内部的//操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持续性。也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

    23、可以从原理上聊一下ACID具体是怎么实现的么?

    对MySQL来说,逻辑备份日志(binlog)、重做日志(redolog)、回滚日志(undolog)、锁技术 + MVCC就是MySQL实现事务的基础。
  • 原子性:通过undolog来实现。
  • 持久性:通过binlog、redolog来实现。
  • 隔离性:通过(读写锁+MVCC)来实现。
  • 一致性:MySQL通过原子性,持久性,隔离性最终实现(或者说定义)数据一致性。

1、原子性原理
每条数据变更(INSERT/UPDATE/DELETE/REPLACE)等操作都会生成一条 undolog 记录,在SQL执行前先于数据持久化到磁盘。当事务需要回滚时,MySQL会根据回滚日志对事务中已执行的SQL做逆向操作,比如 DELETE 掉一行数据的逆向操作就是再把这行数据 INSERT回去,其他操作同理。
2、持久性原理
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;
MySQL表数据是持久化到磁盘中的,但如果所有操作都去操作磁盘,等并发上来了,那处理速度谁都吃不消,因此引入了缓冲池(Buffer Pool)的概念,Buffer Pool 中包含了磁盘中部分数据页的映射,可以当做缓存来用;这样当修改表数据时,我们把操作记录先写到Buffer Pool中,并标记事务已完成,等MySQL空闲时,再把更新操作持久化到磁盘里(你可能会问,到底什么时候执行持久化呢?1、MySQL线程低于高水位;2、当有其他查询、更新语句操作该数据页时),从而大大缓解了MySQL并发压力。
但是它也带来了新的问题,当MySQL系统宕机,断电时Buffer Pool数据不就丢了?
因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。

24、说一下MySQL 的四种隔离级别

Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(NonrepeatableRead),因为同一事务的其他实例在该实例处理其间可能会有新的 commit,所以同一 select 可能返回不同结果。
Repeatable Read(可重读)
这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。
Serializable(串行化)
通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

隔离级别 脏读 不可重复读 幻读
Read Uncommitted
Read Committed ×
Repeatable Read × ×
Serializable × × ×

MySQL 默认采用的 REPEATABLE_READ隔离级别,Oracle 默认采用的 READ_COMMITTED隔离级别。事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。
InnoDB 存储引擎在分布式事务的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。

25、日志

image.png
redo log
redo log 不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入 redo 中。具体的落盘策略可以进行配置 。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 MySQL 服务的时候,根据 redo log 进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。RedoLog 是为了实现事务的持久性而出现的产物。
image.png
undo log
undo log 用来回滚行记录到某个版本。事务未提交之前,Undo 保存了未提交之前的版本数据,undo中的数据可作为数据旧版本快照供其他并发事务进行快照读。是为了实现事务的原子性而出现的产物,在MySQL innodb 存储引擎中用来实现多版本并发控制。
image.png
binlog
MySQL的 binlog 是记录所有数据库表结构变更(例如 CREATE、ALTER TABLE)以及表数据修改(INSERT、UPDATE、DELETE)的二进制日志。binlog 不会记录 SELECT 和 SHOW 这类操作,因为这类操作对数据本身并没有修改,但你可以通过查询通用日志来查看 MySQL 执行过的所有语句。
MySQL binlog 以事件形式记录,还包含语句所执行的消耗的时间,MySQL 的二进制日志是事务安全型
的。
binlog 的主要目的是复制和恢复。binlog 有三种格式,各有优缺点:

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

    27、如何实现可重复读?

    为了实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。
    我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。
    如下图,一行记录现在有 3 个版本,每一个版本都记录这使其产生的事务 ID,比如事务 A 的 transaction id 是 100,那么版本 1 的 row trx_id 就是 100,同理版本 2 和版本 3。
    image.png
    可重复读是在事务开始的时候生成一个当前事务全局性的快照。对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:

  4. 当前事务内的更新,可以读到;

  5. 版本未提交,不能读到;
  6. 版本已提交,但是却在快照创建后提交的,不能读到;
  7. 版本已提交,且是在快照创建前提交的,可以读到。

    28、如何解决幻读问题?

    MySQL 已经在可重复读隔离级别下解决了幻读的问题,用的是间隙锁。MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key 锁。
    假设现在表中有两条记录,并且 age 字段已经添加了索引,两条记录 age 的值分别为 10 和 30。此时,在数据库中会为索引维护一套 B + 树,用来快速定位行记录。B + 索引树是有序的,所以会把这张表的索引分割成几个区间。
    image.png
    此时,在数据库中会为索引维护一套 B + 树,用来快速定位行记录。B + 索引树是有序的,所以会把这张表的索引分割成几个区间。如图所示,分成了 3 个区间,在这 3 个区间是可以加间隙锁的。
    image.png
    之后,我用下面的两个事务演示一下加锁过程。
    image.png
    在事务 A 提交之前,事务 B 的插入操作只能等待,这就是间隙锁起得作用。当事务 A 执行update user set name=’风筝2号’ where age = 10; 的时候,由于条件 where age = 10 ,数据库不仅在 age =10 的行上添加了行锁,而且在这条记录的两边,也就是 (负无穷, 10]、(10,30] 这两个区间加了间隙锁,从而导致事务 B 插入操作无法完成,只能等待事务 A 提交。不仅插入 age = 10 的记录需要等待事务 A 提交,age<10、10这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 age 是否大于等于 30,都要等待事务 A 提交才可以成功插入。

    29、MySQL事务如何回滚?

    在MySQL默认的配置下,事务都是自动提交和回滚的。当显示地开启一个事务时,可以使用ROLLBACK语句进行回滚。该语句有两种用法:
  • ROLLBACK:要使用这个语句的最简形式,只需发出ROLLBACK。同样地,也可以写为ROLLBACK WORK,但是二者几乎是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
  • ROLLBACK TO [SAVEPOINT] identifier :这个语句与SAVEPOINT命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。

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

    MySQL—事务、锁

    31、什么是 MVCC?

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

    32、MVCC 的实现原理

    对于 InnoDB ,聚簇索引记录中包含 3 个隐藏的列:
    ROW ID:隐藏的自增 ID,如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
    事务 ID:记录最后一次修改该记录的事务 ID。
    回滚指针:指向这条记录的上一个版本。

    我们拿上面的例子,对应解释下 MVCC 的实现原理,如下图:
    image.png
    如图,首先 insert 语句向表 t1 中插入了一条数据,a 字段为 1,b 字段为 1, ROW ID也为 1 ,事务ID假设为 1,回滚指针假设为 null。
    当执行 update t1 set b=666 where a=1 时,大致步骤如下:
  1. 数据库会先对满足 a=1 的行加排他锁;
  2. 然后将原记录复制到 undo 表空间中;
  3. 修改 b 字段的值为 666,修改事务 ID 为 2;
  4. 并通过隐藏的回滚指针指向 undo log 中的历史记录;
  5. 事务提交,释放前面对满足 a=1 的行所加的排他锁。

在前面实验的第 6 步中,session2 查询的结果是 session1 修改之前的记录,这个记录就是来自undo log中。
因此可以总结出 MVCC 实现的原理大致是:
InnoDB 每一行数据都有一个隐藏的「回滚指针」,「用于指向该行修改前的最后一个历史版本」,这个历史版本存放在「undo log」中。如果要执行更新操作,会将原记录放入 undo log 中,并通过隐藏的回滚指针指向 undo log 中的原记录。
「其它事务此时需要查询时,就是查询 undo log 中这行数据的最后一个历史版本。」
MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性。通过 MVCC,保证了事务 ACID 中的 I(隔离性)特性。

33、为什么要加锁?

当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。
保证多用户环境下保证数据库完整性和一致性。

34、按照「锁的粒度」分数据库锁有哪些?

  1. 行级锁:
    行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为「共享锁」和「排他锁」。
    开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  2. 表级锁:
    表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁。它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的 MYISAM 与 INNODB 都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
    开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
  3. 页级锁:
    页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般 。
  4. MyISAM和InnoDB存储引擎使用的锁:
    MyISAM 采用表级锁(table-level locking)。
    InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。

    35、从「锁的类别」上分MySQL都有哪些锁呢?

    从锁的类别上来讲,有 共享锁、排他锁、意向共享(读)锁、意向排他(写)锁
  • 共享锁:又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个
  • 排他锁:又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。

1.行级锁
InnoDB存储引擎实现了如下两种标准的行级锁。

  1. SELECT LOCK IN SHARE MODE;
  • 共享锁(S Lock),允许事务读一行数据;
  • 排他锁(X Lock),允许事务删除或更新一行数据;

如果一个事务 T1 已经获得了 行r 的共享锁,那么另外的事务 T2 可以立即获得 行r 的共享锁,因为读取并没有改变 行r 的数据,称这种情况为锁兼容。但若有其他的事务 T3 想获得 行r 的排他锁,则其必须等待事务 T1、T2 释放 行r 上的共享锁—-这种情况称为锁不兼容。


S X
S 兼容 不兼容
X 不兼容 不兼容

2.表级锁
InnoDB存储引擎支持多粒度锁定,这种锁允许事务在行级上的锁和表级上的锁同时存在。为了支持不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,称之为意向锁。
若将上锁的对象看成一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先要对粗粒度的对象上锁。

  1. SELECT FOR UPDATE;
  • 意向共享锁(IS Lock),事务想要获得一张表中「某几行的共享锁」;
  • 意向排他锁(IX Lock),事务想获得一张表中「某几行的排他锁」。 |
    | IS | IX | S | X | | —- | —- | —- | —- | —- | | IS | 兼容 | 兼容 | 兼容 | 不兼容 | | IX | 兼容 | 兼容 | 不兼容 | 不兼容 | | S | 兼容 | 不兼容 | 兼容 | 不兼容 | | X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |

36、数据库的乐观锁和悲观锁是什么?怎么实现的?

其实悲观锁和乐观锁,也并不是 MySQL 或者数据库中独有的概念,而是并发编程的基本概念。
主要区别在于,操作共享数据时,“悲观锁”即认为数据出现冲突的可能性更大,而“乐观锁”则是认为大部分情况不会出现冲突,进而决定是否采取排他性措施
MySQL的多版本并发控制 (MVCC),其本质就可以看作是种乐观锁机制,而排他性的读写锁、两阶段锁等则是悲观锁的实现。

  • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制
  • 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:一般会使用版本号机制或CAS算法实现。

两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

37、什么是死锁?怎么解决?

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法

  1. 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  2. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
  3. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;如果业务处理不好可以用分布式事务锁或者使用乐观锁。