自己搭建个人网站的注意事项,网站rar文件,国外企业网络会议的组织与优化,长沙公司网站建立2024小结#xff1a;在写作分享上#xff0c;这里特别感谢CSDN社区提供平台#xff0c;支持大家持续学习分享交流#xff0c;共同进步。社区诚意满满的干货#xff0c;让大家收获满满。 对我而言#xff0c;珍惜每一篇投稿分享#xff0c;每一篇内容字数大概6000字左右在写作分享上这里特别感谢CSDN社区提供平台支持大家持续学习分享交流共同进步。社区诚意满满的干货让大家收获满满。 对我而言珍惜每一篇投稿分享每一篇内容字数大概6000字左右加上画图以及案例demo代码编写、实战撰稿时长平均3小时左右。由于年底工作特别忙晚上下班回家有时候娃已经睡着了如果娃没睡还得陪娃玩直到她睡着才有空继续写作。每天空闲时间非常少经常一篇文章从周一写到周末才能完成。 近5个月以来投稿并不多仅29篇。新的一年争取有更多时间和大家交流学习分享包括家庭、日常、职场其他非技术性内容。 一、前言背景
二、通俗演义-MVCC多版本并发控制核心原理
2.1 解密-基于undoLog实现的数据版本链
2.2 弯弯绕绕看不懂的readView视图-一句话总结看懂
三、MVCC解决脏读、不可重复读、幻读问题demo详解
3.1 验证MVCC解决脏读、不可重复读问题【并发事务一个重复查另一个改】
3.2 验证MVCC解决幻读、不可重复读问题【并发事务一个重复查一个新增】
四、脏写是什么如何解决 【公众号搜索拉丁解牛说技术】欢迎同学们关注交流讨论。 一、前言背景
之前系列4文章说过MySQL InnoDB存储引擎默认事务隔离级别是可重复读repeatable-read。我们可通过命令查看SELECT SESSION.tx_isolation; 而且也说到MySQL的可重复读事务隔离级别可以解决脏读、幻读、不可重复读三大事务并发问题。当时也留了一个思考题MySQL是如何做到的答案是MVCC锁。核心在于MVCC。
那MySQL如何让实现一个事务多次读不受另一个事务的增、改、删的影响。带着这个问题我们一步步解密MySQL的MVCC多版本并发控制核心机制。 二、通俗演义-MVCC多版本并发控制核心原理
MVCC全称是Multi Version Concurrency Control多版本并发控制。MySQL innoDB存储引擎在新增修改删除数据的时候并没有真正用新数据直接更新覆盖而是采用版本链方式去保存数据修改记录。每个读事务对应一个版本的数据快照。每个写事务在事务提交之前该事务内做的任何更新操作未提交之前其他任何事务不可见该更新。
举一个通俗的案例也是我们日常实践的数据版本管理。比如用户信息user (id,name,age,city,cs_level)修改我们不会简单的直接进行update通常会进行数据历史记录。比如最简单的user表增加一个is_valid字段利用主键id自增的特性把它当做用户信息更新版本号。每次更新用户信息将原信息is_valid置为false。然后用新信息去构建一条is_validtrue数据。这样就可以完成版本记录追溯。
MVCC的数据快照、数据版本链就是是类似效果。但是在并发事务里读写具体原理会复杂很多。
2.1 解密-基于undoLog实现的数据版本链
在MySQL表里有2个隐藏的字段一个是事务的IDtrx_id这个事务id就是最近一次更新该数据的事务id另一个是回滚指针roll_pointer 该指针指向的就是更新该数据之前的undoLog可以通俗理解为修改前数据。据此隐藏的事务id和回滚指针的意义一目了然一个表示哪个事务更新了该数据一个表示该数据更新前的样子。
比如下图事务trx_id98的操作新增了name【拉丁解牛说技术】的这行数据。新增数据的时候roll_pointer是空。此后事务99对该数据进行修改把name改为【老牛】。此时如下图回滚指针指向老版本事务98的数据。这样数据版本链清晰可见。 另外说一下在MVCC里只有更新、删除、新增操作有让事务ID新增查询是不会让数据事务发生变化。 2.2 弯弯绕绕看不懂的readView视图-一句话总结看懂
readview顾名思义是读视图当开启一个事务MySQL会根据当前事务隔离级别给你这个事务开启独立的review视图空间。这里很多博文在讲解readview机制时候会对当前最大max_trx_id最小,min_trx_id当前事务this_trx_id视图开启时候活跃的事务id组等多个事务id进行比较说明讲的非常细。不过这里的判断规则说这么细如果读者些微没跟上或者失去耐心可能就错失理解掌握readview的核心机制。
我们坚持大道至简的方法总结readview视图核心机制最直接一句话每个事务只能读到对应事务隔离级别的数据。
我们简单举例说一下
如下图当前事务隔离级别是可重复读RP并发事务100要重复多次查询事务101 要更新name为【zhangsan】。事务并发开始前如下 接下来具体操作
1、事务id100进行查询首先查到了事务99的数据发现自己的事务ID100比99大直接返回读到该数据【老牛】。
2、接下来事务id101把数据更新为【zhangsan】并提交更新trx_id101回滚指针指向了之前trx_id99的老数据。 3、事务100继续重复读这时候读到了trx_id101的最新数据发现比自己的trx_id100还大。在当前每个事务只能读到对应事务隔离级别的数据原则下,而且当前事务隔离级别是可重复读RP。按规则不好意思这个101事务修改的数据我不能读得继续遍历undoLog版本链找到了下一个事务id99的数据【老牛】发现99 100非常好符合事务隔离级别要求。那这次重复读读的还是之前的数据【老牛】。
同样道理如果是事务隔离级别在读已提交、读未提交判断规则也只是对应判断当前自己的事务ID与读到的数据事务id大小关系是否满足事务隔离级别即可。
当然这里核心再详细展开确实有很多细节比如读已提交隔离级别下每次查询就是开启一个新的readview。这个和可重复读隔离级别不一样。
理解了MVCC核心原理我们设计场景在InnoDB默认的事务隔离级别「可重复读RP」下一步步实践验证解决脏读、幻读、不可重复读问题。 三、MVCC解决脏读、不可重复读、幻读问题demo详解
新建一个user_mvcc_demo表多个事务并发读、修改name值、以及新增写入来具体验证mvcc核心机制。
CREATE TABLE user_mvcc_demo (id int(11) NOT NULL AUTO_INCREMENT,name varchar(16) DEFAULT NULL,PRIMARY KEY (id)) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8;
-- 新增一条数据id1
insert into user_mvcc_demo(name)values (拉丁解牛说技术001); 3.1 验证MVCC解决脏读、不可重复读问题【并发事务一个重复查另一个改】
之前说过脏读特指的就是一个事务里select查询到另一个事务update语句未提交的脏数据场景。
本demo模拟场景事务1里面多次重复查询id1的name值而事务2并发修改了name值并提交。
预期结果在事务1里每次查询都是数据快照‘拉丁解牛说技术001’事务2提交事务前、后事务1均读不到name的新值zhangsan。 具体如下
事务1 SQL
begin;
select * from user_mvcc_demo where id1;
select now();-- 时间 2025-01-03 16:04:46
select * from user_mvcc_demo where id1;
多次查询
..... 事务2 SQL
begin;
select * from user_mvcc_demo where id1;
select now();-- 时间 2025-01-03 16:04:46
update user_mvcc_demo setzhangsan where id1;-- 更新name 为zhangsan
select * from user_mvcc_demo where id1;
select * from user_mvcc_demo where id1;--- 此时未提交但事务1读不到脏数据zhangsan
commit--提交后事务1仍然读不到新快照数据zhangsan
..... 实践结果
在事务1内多次查结果都是拉丁解牛说技术001。实际上在另一个事务2 时间 2025-01-03 16:04:26 已经修改name为zhangsan。 且事务2提交事务后在事务1里继续多次查询查到的也是事务1开启事务后对应的快照数据拉丁解牛说技术001验证MVCC解决脏读、不看重复读问题完成。
如下图两个会话 3.2 验证MVCC解决幻读、不可重复读问题【并发事务一个重复查一个新增】
系列3具体说过不可重复读问题一个事务读到了另一个事务已提交的数据。主要针对的是一个事务里select到另一个事务update或者delete语句的更新结果。
而幻读一个事务读到另一个事务新增的数据特指一个事务里select查询查到了另一个事务insert数据。
本demo模拟场景事务1里面多次重复查询全表而事务2新增了一条数据并提交。
预期结果在事务1里每次查询都只有一条数据事务2提交事务前、后事务1均读不到新增那条数据。 仍然是user_mvcc_demo表里面只有一条id1name‘zhangsan’的数据。
事务1重复读select * from user_mvcc_demo 。
事务2新增1条数据。insert into user_mvcc_demo(name)values(拉丁解牛说技术);
具体如下
事务1 SQL
begin;
select * from user_mvcc_demo;
select now();-- 时间 2025-01-03 16:40:45
select * from user_mvcc_demo;
多次查询
.....
事务2 SQL
begin;
select * from user_mvcc_demo;
select now();-- 时间 2025-01-03 16:40:22
insert into user_mvcc_demo(name)values(拉丁解牛说技术);;-- 新增了一条拉丁解牛说技术的数据
select * from user_mvcc_demo;
commit--提交后事务1仍然读不到新快照数据拉丁解牛说技术
..... 实践结果与预期一致在事务1里每次查询都只有一条数据zhangsan而事务2提交事务前、后事务1均读不到新增那条数据拉丁解牛说技术。 四、脏写是什么如何解决
脏读说过很多次脏写大家听的很少。所谓脏写就是并发事务更新同一个数据比如2个并发事务修改id1的name值之前name值是【拉丁解牛说技术】。事务1此时想改成zhansan并提交而事务2改成lisi但是中途回滚了回滚为老数据【拉丁解牛说技术】。对于事务1来说这就是脏写问题。
MySQL是通过锁机制来保障并发事务串行化执行避免事务并发脏写问题。简单的说更新事务读到数据后需要先加锁加锁成功才能开始执行更新事务。未拿到锁的事务需要等待锁。这个和Java并发编程的锁机制类似。
篇幅有限具体锁相关类型、以及具体场景锁分析我们下一篇继续分享。