1.Mysql架构
从MySQL的架构图,我们可以看出MySQL的架构自顶向下大致可以分为网络连接层、数据库服务层、存储引擎层和系统文件层四大部分。
网络连接层
网络连接层位于整个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数据之前的状态语句
隔离性
定义:每个并发事务执行过程中,数据之间互不影响
实现原理:
- (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
- (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性
持久性
定义:事务一旦提交,对数据的保存是永久性的,不会因为异常原因丢失
实现原理:redolog
redolog属于innodb的一种事务日志。因为mysql的所存储的数据最终是以页的形式落在磁盘上的,因此为了提高效率,mysql定义了缓存池的概念。在每次读取数据时,先查询缓存池,缓存池没有才会查询硬盘,并将查询到的结果写入缓存池。同理,在写入数据时,也是先将数据写入缓存池,在定期将数据刷新到磁盘,这一动作成为刷脏
那么就会出现一个问题,当数据写入缓存但还没写入磁盘时,数据库宕机。当再次重启时就会发生数据丢失的情况
因此mysql会在记录缓存之前,会将这条操作记录在redolog上(预加载日志),然后再写入缓存池。这样即便数据库宕机,也可以读取redolog日志进行数据恢复。因为redolog是记录在硬盘上的
那么有一个问题,为什么不直接记录数据在硬盘上,而是要基redolog日志
因为innodb的数据存储形式是聚簇索引,也就是一个b+树。众所周知b+树是有序的,每层按照主键ID从小到大自左向右排序。那么一旦表主键ID不是有序的(例如UUID),那么当新增数据需要插入到之前的数据时,维护数据的平衡需要造成较大的IO开销。就算数据只插入一个节点(页)。页内部的数据也是有序的,也需要进行数组那样的数据后移或前移,开销很大。但是redolog是增量型日志,每次只需在文件末尾追加就好,因此IO开销很小一致性
定义:事务执行前后,数据库的完整性约束并没有被破坏。数据库中的数据时一致的,不会出现数据丢失或数据新增的情况
实现原理:
在数据库层面保证原子性、隔离性、持久性。同时在应用层面保证逻辑的安全性。因为不论数据层做的多完美,应用层出现逻辑问题还是会造成数据不一致的情况4.Mysql的锁机制
从锁粒度区分,分为行锁和表锁。
- 表锁:锁住整张表,效率低但安全性高
- 行锁:锁住当前行,粒度低但效率高
从功能区分,分为读锁和写锁
- 读锁:在读取时加锁
- 写锁:就是在增删改时加锁
从排他性区分,分为共享锁和排它锁
- 共享锁:在操作数据时,其他事务可以读取数据,但无法进行删改操作
- 排它锁:在修改数据时,其他事务无法堆当前数据读取和增删改操作
5.脏读,不可重复读,幻读的概念
脏读
事务A读取到了事务B未提交的数据。如果此时事务B进行回滚,那么事务A获取到的数据就是异常数据
不可重复读
事务A在一个事务过程在,前后读取同一条数据不一致。也就是说有可能事务A读取完数据后,事务B对数据进行了修改,那么此时事务A在读取数据,就出现了数据不一致的情况
幻读
事务A在一个事务过程在,前后读取同一范围数据不一致。也就是说有可能事务A读取完数据后,事务B对数据进行了新增或删除,那么此时事务A在读取数据,就出现了数据不一致的情况
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可以解决快照读情况下的幻读问题,但是如果当前事务操作了其他事务新增的数据后,就会发生幻读的情况
功能
- 读写互不阻塞:查询 和 更新、删除、插入操作互相不阻塞
开始一个查询后,读到的数据总是查询开始时那个时间点的快照。 查询开始后,发生的变更(即使已经提交),这次查询也是看不到的。一个事务无论运行多长时间,看到的数据都是相同的,不同开始时间的事务中相同的查询,返回的数据可能不同。 - 提供一致性读 :事务在开始后的数据读取,始终为事务开始时数据的状态
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原理
如何判定当前行对于当前事务(还未提交)是否可见,原理如下:
- 如果当前行当前事务ID <= read view的最小版本号,那么代表当前行的最后事务提交是在当前事务开启之前,因此可见
- 如果当前行事务ID > read view的最大版本号,代表当前行的最后事务提交是在当前事务开启之后,因此不可见。需要根据回滚指针去undolog中获取修改之前的事务ID以及具体数据,依次递归,如递归结束依然不可见,那就是不可见。如递归过程中undolog存储的事务ID可见,那么返回对应的快照数据即可
- 如果当前行事务ID > read view的最小版本号 && 如果当前行事务ID < read view的最大版本号,需要判定是否readview中是否存在当前行事务ID。
- 不存在的话,代表当前行的最后修改已提交。当前事务ID >= 当前行事务ID,代表在当前事务开启前,当前行事务已提交,可见
- 不存在的话,代表当前行的最后修改已提交。当前事务ID < 当前行事务ID,代表在当前事务开启前,当前行事务未提交,不可见,需要根据回滚指针进行undolog递归回滚
- 存在的话,代表当前行的最后修改未提交,不可见,需要根据回滚指针进行undolog递归回滚
此时,如果这条记录的delete_flag为true,说明这条记录已被删除,不返回。如果delete_flag为false,说明此记录可以安全返回给客户端
12.间隙锁
概念:
间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。锁定一个范围,但不包含记录本身。
适用于RR级别(RR级别才有,范围左开右闭)
- Next-Key Lock :行锁和间隙锁组合起来就叫Next-Key Lock。 锁定一个范围,并且锁定记录本身(RR级别才有)
作用:
在一定的“间隙”内防止其他事务的插入操作,以此防止幻读的发生:
- 防止间隙内有新数据被插入
- 防止已存在的数据,更新成间隙内的数据
当where条件后没有索引列,不会使用Next-Key Lock
只使用唯一索引查询,并且只锁定一条记录时,innoDB会使用行锁
只使用唯一索引查询,但是检索条件是范围检索,或者是唯一检索然而检索结果不存在(试图锁住不存在的数据)时,会产生 Next-Key Lock
使用普通索引检索时,不管是何种查询,只要加锁,都会产生间隙锁
同时使用唯一索引和普通索引时,由于数据行是优先根据普通索引排序,再根据唯一索引排序,所以也会产生间隙锁
13.springboot开启事务步骤
- 启动类添加@EnableTransactionManagement注解
- ServiceImpl上添加@Transactional(rollbackFor = Exception.class)注解,或者在具体方法上添加@Transactional(rollbackFor = Exception.class)注解也可以。其中rollbakcFor具体指定当发生什么异常时进行回滚操作。注意,请不要再Controller层添加此注解,不然会导致多数据源配置失效。而且也不符合mvc三层规范