【Java面试】五、MySQL篇(下)

文章目录

  • 1、事务的特性
  • 2、并发事务问题
  • 3、事务的隔离级别
  • [4、undo log 和 redo log](#4、undo log 和 redo log)
    • [4.1 底层结构](#4.1 底层结构)
    • [4.2 redo log](#4.2 redo log)
    • [4.3 undo log](#4.3 undo log)
  • 5、MVCC
    • [5.1 隐式字段](#5.1 隐式字段)
    • [5.2 undo log 版本链](#5.2 undo log 版本链)
    • [5.3 ReadView](#5.3 ReadView)
    • [5.4 ReadView的匹配规则实现事务隔离](#5.4 ReadView的匹配规则实现事务隔离)
  • 6、MySQL的主从同步原理
  • 7、分库分表
    • [7.1 垂直分库](#7.1 垂直分库)
    • [7.2 垂直分表](#7.2 垂直分表)
    • [7.3 水平分库](#7.3 水平分库)
    • [7.4 水平分表](#7.4 水平分表)
    • [7.5 分库分表之后的问题](#7.5 分库分表之后的问题)
  • 8、面试

1、事务的特性

一组操作,同成功,同失败。开启事务后,所有操作作为一个整体去提交或撤销。

事务的特性:ACID

  • 原子性A:事务是最小的操作单元,不可分割
  • 一致性C:事务完成后,所有的数据保持一致的状态
  • 隔离性I:数据库的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
  • 持久性D:落盘,事务一旦提交,改变就是永久的

具体以银行转账的例子描述ACID:A向B转500块,这个操作不可再分割,转账过程,A账户扣除了500,B账户必须增加500,数据要一致。A向B转账的过程中,不能受其他事务和操作的干扰,即隔离性。最后事务提交,数据落盘,即持久化

2、并发事务问题

  • 脏读
  • 不可重复读
  • 幻读

脏读即:事务B更新了数据,但还没commit,A事务就给读走了

sql 复制代码
解决:将隔离级别由读未提交 -> 改为 -> 读已提交

隔离级别改为读已提交后,发现会出现两次读取的结果不一致的情况 ⇒ 不可重复读即:A刚开始读的id=1的数据,值为x,期间事务B又更新并commit了id=1的这条数据,此时A事务第二次读,发现两次数据不一样

sql 复制代码
解决:将隔离级别由读已提交 -> 改为 -> 可重复读

改为可重复读的隔离级别后,又出现了幻读 ⇒ 幻读即:事务A先查id=1数据,发现没有。然后准备insert id=1的数据,但期间事务B写入了id=1的数据,事务Ainsert时失败,于是事务A再查id=1数据,发现还是没有(可重复读的隔离级别),事务A自己感觉见鬼了

3、事务的隔离级别

并发事务有脏读、不可重复读、幻读三个问题。以下四种隔离级别,对应的问题如下:(√即存在这个问题,x即不存在这个问题)

总之:

  • 读未提交 ⇒ 存在脏读问题
  • 读已提交 ⇒ 存在不可重复读问题(Oracle默认的隔离级别)
  • 可重复读 ⇒ 存在幻读问题(MySQL默认的隔离级别)
  • 串行化 ⇒ 无并发事务的三大问题,但性能不高

4、undo log 和 redo log

4.1 底层结构

缓冲池:

  • buffer pool,主内存的一个区域,里面存了磁盘上经常操作的那些数据
  • 增删改查时,先去缓冲池找,没找到再去磁盘加载
  • 以一定频率把池里的数据刷回磁盘,减少IO次数,提高速度

数据页:

  • InnoDB存储引擎管理磁盘的最小单元
  • 每页大小默认16KB
  • 每页中存的是行数据

该架构下,存在问题:如果从缓冲池刷数据到磁盘时,磁盘所在服务器宕机或IO异常,缓冲池的数据在内存,断电会丢失 ⇒ redo log

4.2 redo log

  • 重做日志,包括redo log buffer(存缓冲池中)、redo log file(存磁盘中)
  • 用来保证事务的持久性的

增删改的事务提交后,写到缓冲池、缓冲池的redo log、磁盘的redo log,此时缓冲池数据刷回磁盘失败时,可用磁盘的redo log去做恢复。问:你有空往磁盘的redo log写数据,没空直接把增删改的数据写回磁盘吗?⇒ 前者是追加,顺序写,后者是磁盘寻道,随机写,性能差别很大。

最后,如果缓冲池的数据成功刷回磁盘了,那磁盘里的redo log就没用了,会自动定期清理。

rodo log,简言之,用于保证持久化的,当缓冲池数据刷回磁盘失败时,靠redo log恢复。

4.3 undo log

作用:

  • 用于做回滚
  • 用于MVCC(多版本并发控制)

undo log是逻辑日志,客户端做delete,undo log中记录insert。客户端update,undo log也update,但update成前一版数据。总之就是逆操作。rollback时,执行undo log即可。undo log保证的是事务的一致性和原子性。

5、MVCC

Multi-Version Concurrency Control,多版本并发控制。即维护了一条数据的多个版本,使得并发事务下读写操作没有冲突(读哪个版本,是隔离级别决定的,但多个版本的维护,是MVCC支持的),或者说是保证事务隔离性的。

MVCC的实现,依赖三个东西:

  • 隐式字段
  • undo log日志
  • readView

5.1 隐式字段

每张表会有三个额外字段:

一个字段记录最近操作这条数据的事务ID,一个字段记录上一个版本的数据的内存地址:

5.2 undo log 版本链

insert、update、delete时,产生undo log,用于回滚和版本控制。刚开始的状态:

事务2修改id = 30数据的age为3,得到版本1的数据,表里的指针指向undo log最开始的初始数据:

事务3将id = 30数据的name改为A3:此时,表里存第二个版本在undo log的内存地址,这个地址指向undo log里存版本1的数据

事务4将id = 30数据的age改为10:

不同事务或者相同事务对同一条记录进行修改,针对该记录,在undo log中生成一条数据记录,并由db_roll_prt字段形成版本链表。链表的头部是最近那个版本的数据,链表尾部是最早的原始数据。

5.3 ReadView

  • 当前读:读取的是记录的最新版本
  • 快照读:读已提交的隔离级别下,每次select,生成一个快照去读。可重复读的隔离级别下,开启事务后的第一个select执行,会记录一个快照,后面每次select都是在这个快照读

ReadView是快照读时,MVCC提取哪个版本数据的依据,记录并维护当前未提交的事务ID。包含字段:

以下图为例,事务5在第一次读数据的时候:活跃事务Id(即未提交的事务)有三个(3、4、5),因此m_ids数量为3,最小事务ID为3,预分配事务ID为6(5+1),creator_trx_id为5

不同的隔离级别,生成 ReadView 的时机不同:

  • READ COMMITTED:在事务中每一次执行快照读时生成 ReadView
  • REPEATABLE READ:仅在事务中第一次执行快照读时生成 ReadView,后续复用该 ReadView

5.4 ReadView的匹配规则实现事务隔离

前面undo log中生成了一个版本链,而一个事务可以访问哪个版本的数据,由每个版本数据的trx_id和ReadView里的值决定。规则如下(trx_id表示这个版本的数据的db_trx_id):

以上规则很好理解,比如trx_id < min_trx_id,说明产生这个版本数据的事务ID,比当前未提交的所有事务里面最小的事务ID还要小(事务ID小,说明这个事务开启的早,持续时间更久),即这个版本的数据已经提交了,那当前事务自然是可以访问的。相反trx_id > max_trx_id,说明产生这个版本的事务Id,比所有未提交的事务里最年轻的事务,还要年轻,因此,当前事务不可访问这个版本的数据

sql 复制代码
读已提交的隔离级别下:

分析事务5的两次查询,其readview如下,每次select都会产生一个select:

以第一个readview为例:并发事务产生了四个版本的数据,最近的一条数据trx_id为4,带入右边规则:

sql 复制代码
4 == 5,不成立
4 < 3,不成立
4 > 6,不成立
3 <= 4 <= 6成立,但4在3,4,5中的一个,整体不成立

都不成立,因此,事务5当前不可以访问这个版本的数据。再看上一个版本的数据,trx_id为3

sql 复制代码
3 == 5,不成立
3 < 3,不成立
3 > 6,不成立
3 <= 3 <= 6成立,但3在3,4,5中的一个,整体不成立

都不成立,因此,这个版本的数据,事务5当前还是不能访问。再看上一个版本的数据,trx_id为2

sql 复制代码
2 == 5,不成立
2 < 3,成立

成立,可以访问。根据读已提交的隔离策略,可以看到。读到的正是事务2提交的那一版,符合"读已提交"的策略。

同理,事务5在第二次select时,读已提交策略下会生成了新的ReadView,ReadWiew各个字段如图中所标:

最近的一条数据trx_id为4,带入右边规则:

sql 复制代码
4 == 5,不成立
4 < 4,不成立
4 > 6,不成立
4 <= 4 <= 6成立,但4在3,4,5中的一个,整体不成立

都不成立,因此,事务5当前不可以访问这个版本的数据。再看上一个版本的数据,trx_id为3

sql 复制代码
3 == 5,不成立
3 < 4,成立

因此,当前(第二次select时)事务5可以看到DB_TRX_ID为3的数据,正好是事务3提交的那一版本,符合"读已提交"的策略。最后,看下可重复读事务隔离策略下情况:

带入4条规则,可以得出,事务5两次select,可以拿到的版本都是事务2提交的那个版本。

由此,可以看到MVCC保证了MySQL事务隔离性。 当然,事务的隔离性还有锁在处理,比如一个事务获取了一行数据的排他锁,那其他事务就不能再获取该行的其他锁,其他事务如果尝试获取这行数据的共享锁或排他锁,都会被阻塞,直到持有排他锁的事务释放锁或事务结束。

6、MySQL的主从同步原理

一个Java服务,配置连接了主库和从库,其中,主库用于写,从库用于读

主从之间实现数据同步的核心 ⇒ 二进制日志binlog(记录了DDL,如create,以及DML,如insert)

insert一条数据,实现步骤:

  • insert后,主库将变更写进Binlog
  • 从库有线程IoThread去读主库的Binlog,并将变化写入到从库自己的中继日志Relay Log
  • 从库重做中继日志的时间,使用线程SQLThread进行insert
  • 同步完成

7、分库分表

主从结构下,分担了读数据的压力,但解决不了海量数据的存储问题。 ⇒ 分库分表

需要分库分表的时机:项目的业务数据越来越多或做的产品突然火了,单表数据量达1000万行或者20G以后。此时,正常的SQL优化或者加索引解决不了性能问题。

拆分策略:

  • 垂直分库
  • 垂直分表
  • 水平分库
  • 水平分表

7.1 垂直分库

以表为依据,按业务的不同,将不同业务的表分配到不同的库中。不同的微服务,连接自己的库,如下:

改成多个库,如此,在高并发下,提高了磁盘的IO以及数据库连接数

7.2 垂直分表

以字段为依据,根据字段属性将不同字段拆分到不同表中。拆分规则:

  • 把不常用的字段单独放一个表
  • 把text、blob等大数据类型的字段分出来放到一个附表

如下,商品订单表,id、name、category、brand、title字段,属于常用的字段,List或者点开购物应用就要展示的数据。而description属于点开某个商品看详情的时候才能用到的字段,且属于大字段,可做如下拆分:将description单独分一个表,注意id做关联(注意,下面垂直分表后,图中画的是分后的两个表在两个库,这个没限制,也可能在同一个库):

如此,冷热数据分离,减少了不必要的IO(在tb_sku表select * 也不会把大字段description从磁盘读出来),两表互不影响。

7.3 水平分库

将一个库的数据拆分到多个库中,表结构一样,存的数据不一样。

正常一个Java微服务连接一个库,查数据时,想成功路由到数据所在的库,可以考虑:

  • 根据id节点取模
  • 按id也就是范围路由,节点1(1-100万),节点2(100万-200万)

7.4 水平分表

将一个表的数据拆分到多个表中(这几个表也可以放在同一个库)

数据的查询则和水平分库一样,可以考虑取模。如此,可以解决单一表数据量过大而产生的性能问题。

7.5 分库分表之后的问题

  • 分布式事务一致性问题(一个业务操作,需要更新多个数据库,控制事务,同成功同失败)
  • 跨节点关联查询
  • 跨节点分页、排序函数
  • 主键避重

⇒ 分库分表后,引入分库分表中间件解决以上问题以及路由问题:

  • sharding-sphere
  • mycat

最后:

  • 水平分库,将一个库的数据拆分到多个库中,解决海量数据存储和高并发的问题
  • 水平分表,解决单表存储和性能的问题
  • 垂直分库,根据业务进行拆分,高并发下提高磁盘IO和网络连接数
  • 垂直分表,冷热数据分离,多表互不影响

8、面试




相关推荐
2401_8572979124 分钟前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
福大大架构师每日一题35 分钟前
23.1 k8s监控中标签relabel的应用和原理
java·容器·kubernetes
金灰44 分钟前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
菜鸟一皓44 分钟前
IDEA的lombok插件不生效了?!!
java·ide·intellij-idea
爱上语文1 小时前
Java LeetCode每日一题
java·开发语言·leetcode
看山还是山,看水还是。1 小时前
MySQL 管理
数据库·笔记·mysql·adb
bug菌1 小时前
Java GUI编程进阶:多线程与并发处理的实战指南
java·后端·java ee
momo小菜pa1 小时前
【MySQL 09】表的内外连接
数据库·mysql
Jasonakeke1 小时前
【重学 MySQL】四十九、阿里 MySQL 命名规范及 MySQL8 DDL 的原子化
数据库·mysql
程序猿小D1 小时前
第二百六十九节 JPA教程 - JPA查询OrderBy两个属性示例
java·开发语言·数据库·windows·jpa