第 21 章 一条记录的多幅面孔——事务的隔离级别与 MVCC
21.1 事前准备
CREATE TABLE hero ( number INT, NAME VARCHAR ( 100 ), country VARCHAR ( 100 ), PRIMARY KEY ( number )
) ENGINE = INNODB CHARSET = utf8;INSERT INTO hero VALUES ( 1, '刘备', '蜀' );
21.2 事务隔离级别
在保证事务隔离性的前提下,使用不同的隔离级别,来尽量提高多个事务访问同一数据的性能。
21.2.1 事务并发执行遇到的问题
- 脏写(Dirty Write):一个事务修改了另一个未提交事务修改过的数据。
- 脏读(Dirty Read):一个事务读到了另一个未提交事务修改过的数据。
- 不可重复读(Non-Repeatable Read):一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交,该事务都能查询得到最新值。
- 幻读(Phantom):一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次查询时,能把另一个事务插入的记录也读出来。
21.2.2 SQL 标准中的四种隔离级别
按严重性:
脏写 > 脏读 > 不可重复读 > 幻读
隔离级别 | 中文名 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ UNCOMMITTED | 读未提交 | Possible | Possible | Possible |
READ COMMITTED | 读已提交 | Not Possible | Possible | Possible |
REPAEATABLE READ | 可重复读 | Not Possible | Not Possible | Possible |
SERIALIZABLE | 串行化 | Not Possible | Not Possible | Not Possible |
不论哪种隔离级别,都不允许脏写的情况发生。
21.2.3 MySQL 中支持的四种隔离级别
MySQL 的默认隔离级别为 REPEATABLE READ ,我们可以手动修改一下事务的隔离级别。
# 如何设置事务的隔离级别
# SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;# 查看当前会话默认的隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
21.3 MVCC 原理
21.3.1 版本链
对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:
- trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给 trx_id 隐藏列。
- roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo log 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
Tips:实际上 insert undo 只在事务回滚时起作用,当事务提交后,该类型的 undo log 就没用了。
假设之后有两个事务 id 分别为100和200的事务对这条记录进行 UPDATE 操作:
对该记录每次更新后,都会将旧值放到一条 undo log 中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称为 版本链,它的头节点就是当前记录最新的值,另外,每个版本还包含生成该版本时对应的事务 id。
21.3.2 ReadView
- 对于 READ UNCOMMITTED 级别的事务来说,直接读取记录的最新版本;
- 对于 SERIALIZABLE 级别的事务来说,使用加锁的方式来访问;
- 对于 READ COMMITED 和 REPAEATABLE READ 级别的事务来说,必须保证讲到已经提交了的事务修改过的记录,而不能直接读取最新版本版本的记录。那么怎么判断版本链中哪个版本是当前事务可见的?于是便有了 ReadView 的概念。
ReadView 主要包括以下4个重点:
- m_ids:表示在生成 ReadView 时当前系统中活跃的读写事务的**事务 id **列表
- min_trx_id:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的**事务 id **,也就是 m_ids 中的最小值。
- max_trx_id:表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
- creator_trx_id:表示生成该 ReadView 的事务的 事务 id。
TIPS:只有在对表中的记录做改动时才会为事务分配事务 id,否则在一个只读事务中的事务 id 都默认为 0。
通过 ReadView 就可以判断访问某条记录时,某个版本是否可见:
- 如果被访问版本的 trx_id 与 ReadView 中的 creator_trx_id 相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的 trx_id 小于 ReadView 中的 min_trx_id,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 trx_id 大于 ReadView 中的 max_trx_id,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
public boolean isShow(long trx_id, long creator_trx_id, long min_trx_id, long max_trx_id, List<Long> m_ids) {if (trx_id == creator_trx_id) {return true;}if (trx_id < min_trx_id) {return true;}if (trx_id > max_trx_id) {return false;}return !m_ids.contains(trx_id);}
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本,直到最后一个版本。
READ COMMITTED 和 REPEATABLE READ 生成 ReadView 的时机不同。
21.3.2.1 READ COMMITTED——每次读取数据前都生成一个ReadView
# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;# Transaction 200
BEGIN;# 更新了一些别的表的记录
...
此时 number=1
的记录版本链:
使用 READ COMMITED 隔离级别的事务开始执行查询:
# 使用READ COMMITTED隔离级别的事务
BEGIN;# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'
SELECT1 的执行过程如下:
-
在执行 SELECT 语句时会先生成一个 ReadView:
{m_ids:[100, 200],min_trx_id: 100,max_trx_id: 201,creator_trx_id: 0 }
-
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 name = ‘张飞’ ,trx_id = 100 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
-
下一个版本的列 name = ‘关羽’ ,trx_id = 100 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
-
下一个版本的列 name = ‘刘备’ ,trx_id = 80 ,小于 ReadView 中的 min_trx_id 值100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 name 为 ‘刘备’ 的记录。
之后,把事务id=100的事务提交,并使用事务id=200的事务继续更新
# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;
COMMIT;# Transaction 200
BEGIN;# 更新了一些别的表的记录
...UPDATE hero SET name = '赵云' WHERE number = 1;
UPDATE hero SET name = '诸葛亮' WHERE number = 1;
此时 number=1
的记录版本链:
再次使用 READ COMMITED 隔离级别的事务开始执行查询:
# 使用READ COMMITTED隔离级别的事务
BEGIN;# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'张飞
SELECT2 的执行过程如下:
-
在执行 SELECT 语句时会先生成一个 ReadView:
{m_ids:[200],min_trx_id: 200,max_trx_id: 201,creator_trx_id: 0 }
-
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 name = ‘诸葛亮’ ,trx_id = 200 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
-
下一个版本的列 name = ‘赵云’ ,trx_id = 200 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
-
下一个版本的列 name = ‘张飞’ ,trx_id = 100 ,小于 ReadView 中的 min_trx_id 值200 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 name 为 ‘张飞’ 的记录
每次 SELECT 查询都可以读到最新已提交的记录,这就是读已提交
21.3.2.2 REPAEATABLE READ——在第一次读取数据时生成一个ReadView
# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;# Transaction 200
BEGIN;# 更新了一些别的表的记录
...
此时 number=1
的记录版本链:
使用 REPAEATABLE READ 隔离级别的事务开始执行查询:
# 使用REPEATABLE READ隔离级别的事务
BEGIN;# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备
SELECT1 的执行过程如下:
-
在执行 SELECT 语句时会先生成一个 ReadView:
{m_ids:[100, 200],min_trx_id: 100,max_trx_id: 201,creator_trx_id: 0 }
-
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 name = ‘张飞’ ,trx_id = 100 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
-
下一个版本的列 name = ‘关羽’ ,该版本的 trx_id = 100 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
-
下一个版本的列 name = ‘刘备’ ,该版本的 trx_id = 80 ,小于 ReadView 中的 min_trx_id 值100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 name 为 ‘刘备’ 的记录。
之后,把事务id=100的事务提交,并使用事务id=200的事务继续更新
# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;
COMMIT;# Transaction 200
BEGIN;# 更新了一些别的表的记录
...UPDATE hero SET name = '赵云' WHERE number = 1;
UPDATE hero SET name = '诸葛亮' WHERE number = 1;
此时 number=1
的记录版本链:
再次使用 REPAEATABLE READ 隔离级别的事务开始执行查询:
# 使用 REPEATABLE READ 隔离级别的事务
BEGIN;# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值仍为'刘备
SELECT2 的执行过程如下:
-
使用 REPEATABLE READ 隔离级别时,因为之前执行 SELECT1 时已经生成过 ReadView 了,所以此时不再重新生成,而是直接复用之前的 ReadView
{m_ids:[100, 200],min_trx_id: 100,max_trx_id: 201,creator_trx_id: 0 }
-
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 name = ‘诸葛亮’ ,trx_id = 200 ,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
-
下一个版本 name = ‘赵云’ ,trx_id = 200 ,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
-
下一个版本的列 name = ‘张飞’ ,trx_id = 100 ,也在 m_ids 列表内,所以也不符合要求,同理下一个列 name 的内容是 ‘关羽’ 的版本也不符合要求。继续跳到下一个版本。
-
下一个版本的列 name = ‘刘备’ ,trx_id = 80 ,小于 ReadView 中的 min_trx_id 值
100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列 c 为 ‘刘备’ 的记录。
两次 SELECT 查询得到的结果都是相同的,这就是所谓的可重复读。
21.3.3 二级索引与MVCC
前面使用的 trx_id 和 roll_pointer 隐藏列都只存在于聚簇索引,当使用二级索引查询时,该如何判断可见性?
BEGIN;SELECT name FROM hero WHERE name = '刘备';
- 二级索引页面的 Page Header 部分有一个名为 PAGE_MAX_TRX_ID 的属性,每次有事务增删改本页面中的记录时,如果该事务的 id 大于 PAGE_MAX_TRX_ID ,就把这个 id 赋值给 PAGE_MAX_TRX_ID。这也就意味 PAGE_MAX_TRX_ID 是修改该二级索引页面的最大事务 id。
- 当 SELECT 语句访问某个二级索引记录时,如果对应的 ReadView 的 min_trx_id 大于该页面的 PAGE_MAX_TRX_ID,说明该页面的所有记录都对该 ReadView 可见,否则执行步骤 3。
- 利用二级索引进行回表,得到对应的聚簇索引记录后按照前面说的方式判断是否可见。
21.3.4 MVCC 小结
- 所谓 MVCC(Multi-Version Concurrency Control,多版本并发控制)指的就是在使用 READ COMMITED、REPEATABLE READ 这两种隔离级别的事务在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
- READ COMMITED、REPEATABLE READ 这两种隔离级别主要不同在于生成 ReadView 的时机。
- 只有在进行普通 SELECT 查询时,MVCC 才生效。
21.4 关于 purge
在合适的时候把 update undo 日志以及仅仅被标记为删除的记录彻底删除掉,这个删除操作就称为 purge。
21.5 总结
- 并发的事务在运行过程中会出现一些可能的引发一致性问题
- SQL 标准中有4种隔离级别
- 版本链
- ReadView
相关文章:

第 21 章 一条记录的多幅面孔——事务的隔离级别与 MVCC
21.1 事前准备 CREATE TABLE hero ( number INT, NAME VARCHAR ( 100 ), country VARCHAR ( 100 ), PRIMARY KEY ( number ) ) ENGINE INNODB CHARSET utf8;INSERT INTO hero VALUES ( 1, 刘备, 蜀 );21.2 事务隔离级别 在保证事务隔离性的前提下,使用不同的隔…...

javaScript操作dom的事件(3个案例+代码+效果图)
目录 1.焦点事件 案例:登录表单的验证 1.代码 2.效果 3.解释 2.鼠标事件 案例:单击鼠标使小球跳跃 1.代码 2.效果 3.解释 3.键盘事件 案例:使用左右键控制小球左右移动 1.代码 2.效果 编辑 3.解释 1.焦点事件 focus 当获得焦点时出发(不会冒泡)blur 当失去焦点时出发(不会…...

国庆期间的问题,如何在老家访问杭州办公室的网络呢
背景:国庆期间的问题,如何在老家访问杭州办公室的网络呢 实现方案:异地组网 实现语言:Java 环境:三个网络,一台拥有公网IP的服务器、一台杭州本地机房内服务器、你老家所在网络中的一台电脑(…...

动态规划算法——三步问题
1.题目解析 2.算法原理 本题可以近似看做泰波那契数列,即小孩到第一个台阶需要一步,到第二个台阶则是到第一个台阶的步数加上第一阶到第二阶的步数,同理第三阶就是第二阶的步数加上第二阶到第三阶的步数,由于小孩只能走三步&#…...

【鸿蒙学习】深入解析鸿蒙应用与元服务:含义、区别、应用场景及创建方法
文章目录 鸿蒙应用(HarmonyOS App)含义用于干什么优缺点 元服务(Atomic Service)含义用于干什么优缺点 鸿蒙应用与元服务的区别创建方法鸿蒙应用的创建元服务的创建 总结 随着科技的不断进步,操作系统也在不断迭代更新…...
React学习01 jsx、组件与组件的三大属性
文章目录 jsx的介绍与语法1.真实DOM和虚拟DOM2.jsx语法 模块与模块化,组件与组件化模块与模块化组件与组件化 React组件React事件绑定函数式组件类式组件组件属性state组件属性props组件属性ref 尚硅谷react教程官方文档学习记录笔记01 jsx的介绍与语法 1.真实DOM和…...

项目——超级马里奥——Day(3)
一、游戏开发思路: 1.Frame--->BackGround--->Obstacle---->BufferedImage,人物等 2.BackGround的构造函数: 只要记住窗口里边的每一个场景,只要游戏一开始就已经出现在屏幕里边的,都是在构造函数里边 3.绘…...

测试-BUG篇
文章目录 软件测试的生命周期BUGbug的概念描述bug的要素bug级别bug的生命周期 与开发产生争执怎么办(高频考题) 软件测试的生命周期 软件测试贯穿于软件的整个生命周期 BUG bug的概念 是指计算机程序中存在的一个错误(error)、缺陷(flaw)、疏忽(mista…...

vue2中 vue-count-to组件让数字从某个数字动态的显示到某个数字(后附vue3的用法)
1、首先安装 npm install vue-count-to2、使用 2.1、先导入组件 import countTo from ‘vue-count-to’2.2、注册组件 components: { countTo },2.3、使用组件 <countTo> <template><div class"home"><countTo class"count-to&qu…...

AI模型部署初认识
AI部署这个词儿大家肯定不陌生,可能有些小伙伴还不是很清楚这个是干嘛的,但总归是耳熟能详了。 近些年来,在深度学习算法已经足够卷卷卷之后,深度学习的另一个偏向于工程的方向–部署工业落地,才开始被谈论的多了起来…...
在线生成论文的网站有哪些?分享5款AI一键原创论文免费网站
一、千笔-AIPasspaper 千笔-AIPasspaper是一款备受推荐的AI写作助手,它集成了多种功能,包括论文大纲生成、内容填充、文献引用和查重修改等。这款工具基于最新的自然语言处理技术,能够帮助用户快速生成高质量的论文内容。 AI论文࿰…...

考研论坛平台|考研论坛小程序系统|基于java和微信小程序的考研论坛平台小程序设计与实现(源码+数据库+文档)
考研论坛平台小程序 目录 基于java和微信小程序的考研论坛平台小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂…...
Pandas 时间序列处理
Pandas 时间序列处理 说明: 请回答以下问题,以展示您对 pandas 中时间序列处理的全面理解。请在适用时提供代码示例。 问题 1 如何将日期字符串列表 [2023-01-01, 2023-01-02, 2023-01-03] 转换为 pandas 的 DatetimeIndex? 问题 2 给定一…...

PCL 1.8.1 + VTK 1.8.0 + QT5.14.2+ VS2017 环境搭建
先看看效果: PCL 1.8.1下载安装: Tags PointCloudLibrary/pcl GitHub 安装完成后: 如果VTK想重新编译的,可以看我的这篇博客:...

微信小程序和抖音小程序的分享和广告接入代码
开发完成小程序或者小游戏之后,我们为什么要接入分享和广告视频功能,主要原因有以下几个方面。 微信小程序和抖音小程序接入分享和广告功能主要基于以下几个原因: 用户获取与增长:分享功能可以帮助用户将小程序内容传播给更多人&…...

中断系统的原理
一、介绍 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的。中断是指CPU在正常运行程序时,由于内部或外部事件的发生,导致CPU中断当前运行的程序,转而去执行其他程序的过程。 中断可以是硬件产生的,也可以是…...
安装Rust
Rust 是一种系统级编程语言,旨在提供高性能和内存安全,同时避免常见的编程错误。 由 Mozilla Research 推出,Rust 自推出以来因其独特的设计理念和强大的功能而在开发者社区中迅速获得了广泛的关注和采用。 curl --proto ‘https’ --tlsv1.2…...

vite学习教程05、vite+vue2构建本地 SVG 图标
文章目录 前言一、构建本地SVG图标详细步骤1、安装开发依赖2、配置vite2.1、配置vite.config.js2.2、封装vite引入插件脚本 解决报错:can not find package fast-glob imported 二、实际应用应用1:未封装,直接vue应用应用2:封装vu…...
机器学习——自监督学习与无监督学习
# 机器学习中的自监督学习与无监督学习 在机器学习的世界中,监督学习、无监督学习和自监督学习都是重要的学习方法。本文将聚焦于自监督学习与无监督学习,探讨它们的原理、应用场景以及技术细节,并通过大量代码示例来揭示这些方法的内在工作…...

2003经典绝版100%仿盛大服务端火炬引擎原版
;中国游戏制作 本版本为中国游戏制作小组推出的第一个版本,有什么不完美的地方还请大家多多指教!!! 与盛大逼真!!! 本版本M2Server采用“梦幻风”以及“冰眼”M2Server修改器修改的 2004/1/…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...