1.Mysql架构

从MySQL的架构图,我们可以看出MySQL的架构自顶向下大致可以分为网络连接层、数据库服务层、存储引擎层和系统文件层四大部分。
image.png

网络连接层

网络连接层位于整个MySQL体系架构的最上层,主要担任客户端连接器的角色。提供与MySQL服务器建立连接的能力,几乎支持所有主流的服务端语言,例如:Java、C、C++、Python等,各语言都是通过各自的API接口与MySQL建立连接

数据库服务层

数据库服务层是整个数据库服务器的核心,主要包括了系统管理和控制工具、连接池、SQL接口、解析器、查询优化器和缓存等部分

存储引擎层

MySQL中的存储引擎层主要负责数据的写入和读取,与底层的文件进行交互。值得一提的是,MySQL中的存储引擎是插件式的,服务器中的查询执行引擎通过相关的接口与存储引擎进行通信,同时,接口屏蔽了不同存储引擎之间的差异。MySQL中,最常用的存储引擎就是InnoDB和MyISAM

系统文件层

系统文件层主要包括MySQL中存储数据的底层文件,与上层的存储引擎进行交互,是文件的物理存储层。其存储的文件主要有:日志文件、数据文件、配置文件、MySQL的进行pid文件和socket文件等
具体内容请参考:稀土掘金参考文档

2.事务的定义

数据库访问并更新数据的一个执行单元。一个事务中的SQL的执行,要么全部成功,要么一个都不执行。进而实现数据的安全一致性。

3.事务的特性(AIDC)

原子性

定义:事务中的sql执行要么全部成功,要么全部失败。如果一个sql执行失败,其他sql也必须回滚,将数据恢复成事务开始之前的状态
实现原理:undolog
undolog属于innodb的一种事务日志,当innodb执行增删改语句时,会记录相对应回滚的sql执行信息。当sql异常或事务rollback时,就会通过undolog执行对应的sql将数据恢复到事务开始之前的状态。
例如:insert回滚,会执行一条删除状态的语句。反之会执行一条insert状态的语句。而update,则会执行对应update数据之前的状态语句

隔离性

定义:每个并发事务执行过程中,数据之间互不影响
实现原理

  1. (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
  2. (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性

    持久性

    定义:事务一旦提交,对数据的保存是永久性的,不会因为异常原因丢失
    实现原理:redolog
    redolog属于innodb的一种事务日志。因为mysql的所存储的数据最终是以页的形式落在磁盘上的,因此为了提高效率,mysql定义了缓存池的概念。在每次读取数据时,先查询缓存池,缓存池没有才会查询硬盘,并将查询到的结果写入缓存池。同理,在写入数据时,也是先将数据写入缓存池,在定期将数据刷新到磁盘,这一动作成为刷脏
    那么就会出现一个问题,当数据写入缓存但还没写入磁盘时,数据库宕机。当再次重启时就会发生数据丢失的情况
    因此mysql会在记录缓存之前,会将这条操作记录在redolog上(预加载日志),然后再写入缓存池。这样即便数据库宕机,也可以读取redolog日志进行数据恢复。因为redolog是记录在硬盘上的
    那么有一个问题,为什么不直接记录数据在硬盘上,而是要基redolog日志
    因为innodb的数据存储形式是聚簇索引,也就是一个b+树。众所周知b+树是有序的,每层按照主键ID从小到大自左向右排序。那么一旦表主键ID不是有序的(例如UUID),那么当新增数据需要插入到之前的数据时,维护数据的平衡需要造成较大的IO开销。就算数据只插入一个节点(页)。页内部的数据也是有序的,也需要进行数组那样的数据后移或前移,开销很大。但是redolog是增量型日志,每次只需在文件末尾追加就好,因此IO开销很小

    一致性

    定义:事务执行前后,数据库的完整性约束并没有被破坏。数据库中的数据时一致的,不会出现数据丢失或数据新增的情况
    实现原理
    在数据库层面保证原子性、隔离性、持久性。同时在应用层面保证逻辑的安全性。因为不论数据层做的多完美,应用层出现逻辑问题还是会造成数据不一致的情况

    4.Mysql的锁机制

    从锁粒度区分,分为行锁和表锁。
  • 表锁:锁住整张表,效率低但安全性高
  • 行锁:锁住当前行,粒度低但效率高

从功能区分,分为读锁和写锁

  • 读锁:在读取时加锁
  • 写锁:就是在增删改时加锁

从排他性区分,分为共享锁和排它锁

  • 共享锁:在操作数据时,其他事务可以读取数据,但无法进行删改操作
  • 排它锁:在修改数据时,其他事务无法堆当前数据读取和增删改操作

Innodb中有行锁和表锁。MyIsam只有表锁的概念

5.脏读,不可重复读,幻读的概念

脏读

事务A读取到了事务B未提交的数据。如果此时事务B进行回滚,那么事务A获取到的数据就是异常数据
image.png

不可重复读

事务A在一个事务过程在,前后读取同一条数据不一致。也就是说有可能事务A读取完数据后,事务B对数据进行了修改,那么此时事务A在读取数据,就出现了数据不一致的情况
image.png

幻读

事务A在一个事务过程在,前后读取同一范围数据不一致。也就是说有可能事务A读取完数据后,事务B对数据进行了新增或删除,那么此时事务A在读取数据,就出现了数据不一致的情况
image.png

6.标准SQL事务的隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交 × × ×
读已提交 × ×
可重复读 ×
串行化

7.标准SQL不同隔离级别事务的实现方式

隔离级别 实现方式
读未提交(RU) 在读取数据时,不加锁。在更新数据的瞬间添加行共享锁,直到事务结束才释放锁
读已提交(RC) 在读取数据时,添加行级别共享锁在读取完毕后立即释放锁。在更新数据瞬间添加行排他锁,直到事务结束才释放
可重复读(RR) 在读取数据时,添加行级别共享锁直到事务结束才释放锁。在更新数据瞬间添加行排他锁,直到事务结束才释放
串行化(S) 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放; 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。

8.INNODB事务的隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交 × × ×
读已提交 ×
可重复读
串行化

9.INNODB不同隔离级别事务的实现方式

隔离级别 实现方式
读未提交(RU) 当前读,不加锁。在更新数据的瞬间添加行共享锁,直到事务结束才释放锁
读已提交(RC) 快照读,不加锁。在更新数据的瞬间添加行排他锁,直到事务结束才释放锁
可重复读(RR)
默认隔离级别
快照读,不加锁。在更新数据的瞬间添加行排他锁以及间隙锁,直到事务结束才释放锁
串行化 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放; 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。

当前读:

读取的是最新版本,像UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读:

读取的是事务的历史版本。通过mvcc版本比对机制搭配undolog日志,保证当前事务读取的所有数据都是事务开始时数据的状态。后续其他事务对数据的操作不会影响当前事务

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

定义

multiversion concurrency control 多版本并发控制。是数据库管理系统常用的并发控制方法。( MVCC 只在 RC 和RR 隔离级别下起作用,因为read uncommitted 总是读取最新的数据行,而 serializable 总是对所有读取加锁)
MVCC可以解决快照读情况下的幻读问题,但是如果当前事务操作了其他事务新增的数据后,就会发生幻读的情况

功能

  1. 读写互不阻塞:查询 和 更新、删除、插入操作互相不阻塞
    开始一个查询后,读到的数据总是查询开始时那个时间点的快照。 查询开始后,发生的变更(即使已经提交),这次查询也是看不到的。一个事务无论运行多长时间,看到的数据都是相同的,不同开始时间的事务中相同的查询,返回的数据可能不同。
  2. 提供一致性读 :事务在开始后的数据读取,始终为事务开始时数据的状态

    11.Innodb下MVCC实现原理

    隐藏列

    innodb会给每行数据生成3个隐藏列
  • DB_TRX_ID(最新事务ID), 6byte, 创建这条记录/最后一次更新这条记录的事务ID
  • DB_ROLL_PTR(指向undolog的回滚指针), 7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID(隐藏自增ID), 6byte,如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引。如果存在主键的话,那么没有这一列

另外,每条记录的头信息(record header)里都有一个专门的bit(deleted_flag)来表示当前记录是否已经被删除。

更新时undolog写入

UPDATE一行数据时,数据的变动:

  • 老记录被复制到rollback segment中形成undo log,老记录的DB_TRX_ID(最新事务ID)和DB_ROLL_PTR(回滚指针)不动
  • 新记录的DB_TRX_ID(最新事务ID)= 当前事务ID,DB_ROLL_PTR(回滚指针)指向老记录形成的undo log

这样就能通过DB_ROLL_PTR(回滚指针)找到这条记录的历史版本。如果对同一行记录执行连续的update操作,新记录与undo log会组成一个链表,遍历这个链表可以看到这条记录的变迁)

readView是什么

readView是指在创建那一时刻,当前所有未提交事务(活跃)的一个集合

  • up_limit_id:最先开始的事务,该SQL启动时,当前事务链表中最小的事务id编号,也就是当前系统中创建最早但还未提交的事务
  • low_limit_id:最后开始的事务,该SQL启动时,当前事务链表中最大的事务id编号,也就是最近创建的除自身以外最大事务编号
  • m_ids:当前活跃事务ID列表,所有事务链表中事务的id集合

    readView原理

    如何判定当前行对于当前事务(还未提交)是否可见,原理如下:
  1. 如果当前行当前事务ID <= read view的最小版本号,那么代表当前行的最后事务提交是在当前事务开启之前,因此可见
  2. 如果当前行事务ID > read view的最大版本号,代表当前行的最后事务提交是在当前事务开启之后,因此不可见。需要根据回滚指针去undolog中获取修改之前的事务ID以及具体数据,依次递归,如递归结束依然不可见,那就是不可见。如递归过程中undolog存储的事务ID可见,那么返回对应的快照数据即可
  3. 如果当前行事务ID > read view的最小版本号 && 如果当前行事务ID < read view的最大版本号,需要判定是否readview中是否存在当前行事务ID。
  4. 不存在的话,代表当前行的最后修改已提交。当前事务ID >= 当前行事务ID,代表在当前事务开启前,当前行事务已提交,可见
  5. 不存在的话,代表当前行的最后修改已提交。当前事务ID < 当前行事务ID,代表在当前事务开启前,当前行事务未提交,不可见,需要根据回滚指针进行undolog递归回滚
  6. 存在的话,代表当前行的最后修改未提交,不可见,需要根据回滚指针进行undolog递归回滚
  7. 此时,如果这条记录的delete_flag为true,说明这条记录已被删除,不返回。如果delete_flag为false,说明此记录可以安全返回给客户端

    12.间隙锁

    概念

  8. 间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。锁定一个范围,但不包含记录本身。

适用于RR级别(RR级别才有,范围左开右闭)

  1. Next-Key Lock :行锁和间隙锁组合起来就叫Next-Key Lock。 锁定一个范围,并且锁定记录本身(RR级别才有)

作用
在一定的“间隙”内防止其他事务的插入操作,以此防止幻读的发生:

  • 防止间隙内有新数据被插入
  • 防止已存在的数据,更新成间隙内的数据

当where条件后没有索引列,不会使用Next-Key Lock
只使用唯一索引查询,并且只锁定一条记录时,innoDB会使用行锁
只使用唯一索引查询,但是检索条件是范围检索,或者是唯一检索然而检索结果不存在(试图锁住不存在的数据)时,会产生 Next-Key Lock
使用普通索引检索时,不管是何种查询,只要加锁,都会产生间隙锁
同时使用唯一索引和普通索引时,由于数据行是优先根据普通索引排序,再根据唯一索引排序,所以也会产生间隙锁

13.springboot开启事务步骤

  1. 启动类添加@EnableTransactionManagement注解
  2. ServiceImpl上添加@Transactional(rollbackFor = Exception.class)注解,或者在具体方法上添加@Transactional(rollbackFor = Exception.class)注解也可以。其中rollbakcFor具体指定当发生什么异常时进行回滚操作。注意,请不要再Controller层添加此注解,不然会导致多数据源配置失效。而且也不符合mvc三层规范