【MySQL系列】MySQL的事务管理的学习(二)_ 再次理解隔离性

news2024/11/22 16:19:38

「前言」文章内容大致是MySQL事务管理,续上一篇。

「归属专栏」MySQL

「主页链接」个人主页

「笔者」枫叶先生(fy)

MySQL

目录

  • 七、再次理解隔离性
    • 7.1 数据库并发的场景有
    • 7.2 多版本并发控制(MVCC)
    • 7.3 三个隐藏字段列
    • 7.4 undo日志
    • 7.5 模拟MVCC
    • 7.6 Read View
    • 7.7 Read View理论验证
  • 八、RR与RC的本质区别

七、再次理解隔离性

7.1 数据库并发的场景有

数据库并发的场景有以下三种:

  • 读-读 :不存在任何问题,也不需要并发控制
  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

其中读-写并发是数据库当中最高频的场景,下面讨论的就是这个,读-读并发不存在任何问题,写-写并发不谈

7.2 多版本并发控制(MVCC)

多版本并发控制(Multi-Version Concurrency Control)是一种用来解决读-写冲突的无锁并发控制,主要依赖记录中的3个隐藏字段列、undo日志和Read View实现

MVCC为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题:

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数 据库并发读写的性能
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

下面先介绍3个隐藏字段列、undo日志和Read View

7.3 三个隐藏字段列

数据库表中的每条记录都会有如下3个隐藏字段列:

  • DB_TRX_ID:6byte,最近修改(修改/插入)事务ID,记录创建这条记录/最后一次修改该记录的事 务ID
  • DB_ROLL_PTR:7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就 行,这些数据一般在undo log中)
  • DB_ROW_ID:6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB会自动以
    DB_ROW_ID产生一个聚簇索引

补充

  • 实际还有一个删除flag隐藏字段列,用于记录该条数据是否被删除,便于进行数据回滚(删除数据并不是真的删除了,只是修改了flag字段)

例如,有一个测试表

create table if not exists student(
name varchar(11) not null,
age int not null
);

插入一条数据

insert into student (name, age) values ('张三', 28);

在这里插入图片描述
查询该表的数据
在这里插入图片描述
查出来的记录不仅包含name和age字段,还包含三个隐藏字段
在这里插入图片描述
说明

  • 假设插入该记录的事务的事务ID为1,那么该记录的DB_TRX_ID字段填的就是1,没有就为null
  • 这是表中的第一条记录,所以隐式主键DB_ROW_ID字段填的就是1
  • 记录是新插入的,没有历史版本,所以回滚指针DB_ROLL_PTR的值设置为null

7.4 undo日志

  • undo log是MySQL数据库中的一种日志,用于记录事务的回滚信息
  • 在MySQL中,事务的回滚是通过undo log来实现的

undo log简单理解成就是 MySQL 中的一段内存缓冲区,用来保存日志数据的,必要时会将缓冲区中的数据刷新到磁盘

7.5 模拟MVCC

事务ID为10的事务

假设现在有一个事务ID为10的事务,要将刚才插入学生表中的记录的学生姓名“张三”改为“李四”:

  • 因为是要进行写操作,所以需要先给该记录加行锁

  • 在这里插入图片描述

  • 修改前,现将改行记录拷贝到undo log中,所以,undo log中就有了一行副本数据(原理就是写时拷贝)

  • 所以现在MySQL中有两行同样的记录

  • 在这里插入图片描述

  • 现在修改原始记录中的name,改成 ‘李四’,并且修改原始记录的隐藏字段 DB_TRX_ID为当前 事务10 的ID,我们默认从10开始,之后递增

  • 而原始记录的回滚指针DB_ROLL_PTR 列,里面写入undo log中副本数据的地址,从而指向副本记录,既表示我的上一个版本就是它

  • 最后当事务10提交后释放锁,这时最新的记录就是学生姓名为“李四”的那条记录

  • 在这里插入图片描述

现在又有一个事务11

现在又有一个事务11,对student表中记录进行修改(update):将age(28)改成age(38)

  • 因为是要进行写操作,所以需要先给该记录(最新的记录)加行锁
  • 修改前,先将该行记录拷贝到undo log中,此时undo log中就又有了一行副本数据
  • 然后再将原始记录中的学生年龄改为38,并将该记录的DB_TRX_ID改为11,回滚指针DB_ROLL_PTR设置成刚才拷贝到undo log中的副本数据的地址,从而指向该记录的上一个版本
  • 最后当事务11提交后释放锁,这时最新的记录就是学生年龄为38的那条记录

在这里插入图片描述

  • 这样,我们就有了一个基于链表记录的历史版本链
  • 所谓的回滚,无非就是用历史数据,覆盖当前数据

上面的一个一个版本,我们可以称之为一个一个的快照

insert和delete的记录如何维护版本链

上面已经谈了update,update可以形成版本链,那insert和delete呢?

  • 对于delete,删除记录并不是真的把数据删除了,而是先将该记录拷贝一份放入undo log中,然后将该记录的删除flag隐藏字段设置为1,这样回滚后该记录的删除flag隐藏字段就又变回0了,相当于删除的数据又恢复了
  • 对于insert,新插入的记录是没有历史版本的,但是一般为了回滚操作,新插入的记录也需要拷贝一份放入undo log中,只不过被拷贝到undo log中的记录的删除flag隐藏字段被设置为1,这样回滚后就相当于新插入的数据就被删除了
  • updatedeleteinsert可以形成版本链

select不会对数据做任何修改,所以,为select维护多版本,没有意义

select读取,是读取最新的版本呢?还是读取历史版本?

先说两个概念,当前读 VS 快照读:

  • 当前读:读取最新的记录,就叫做当前读
  • 快照读:读取历史版本,就叫做快照读

事务在进行增删查改的时候,并不是都需要进行加锁保护:

  • 事务对数据进行增删改的时候,操作的都是最新记录,即当前读,需要进行加锁保护
  • 事务在进行select查询的时候,既可能是当前读也可能是快照读,如果是当前读,那也需要进行加锁保护,但如果是快照读,那就不需要加锁,因为历史版本不会被修改,也就是可以并发执行,提高了效率,这也就是MVCC的意义所在

而select查询时应该进行当前读还是快照读,则是由隔离级别决定的,在读未提交和串行化隔离级别下,进行的都是当前读,而在读提交和可重复读隔离级别下,既可能进行当前读也可能进行快照读

undo log中的版本链何时才会被清除?

  • 提交事务:当一个事务成功提交后,数据库系统会认为该事务的操作是永久性的,不再需要回滚。因此,与该事务相关的undo log版本链就会被清除
  • 还有其他情况就不补充了

如何保证,不同的事务,看到不同的内容呢?也就是如何实现隔离级别?

  • 下面Read View再谈
  • Read View本质是用来进行可见性判断的
  • 解决在读提交和可重复读隔离级别下,应当是当前读还是快照读的问题(select语句查询)

7.6 Read View

  • Read View就是事务进行快照读操作的时候生产的读视图 (Read View),(即使用select查看数据的时候才会产生读视图
  • 在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID,这个ID是递增的,所以最新的事务,ID值越大)
  • Read View在MySQL源码中就是一个类,本质是用来进行可见性判断的,当事务对某个记录执行快照读的时候,对该记录创建一个Read View,根据这个Read View来判断,当前事务能够看到该记录的哪个版本的数据

ReadView类的源码如下:

class ReadView {
	// 省略...
private:
	/** 高水位:大于等于这个ID的事务均不可见*/
	trx_id_t m_low_limit_id;
	
	/** 低水位:小于这个ID的事务均可见 */
	trx_id_t m_up_limit_id;
	
	/** 创建该 Read View 的事务ID*/
	trx_id_t m_creator_trx_id;
	
	/** 创建视图时的活跃事务id列表*/
	ids_t m_ids;
	
	/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
	* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
	trx_id_t m_low_limit_no;
	
	/** 标记视图是否被关闭*/
	bool m_closed;
	
	// 省略...
};

上述四个成员说明:

m_ids; //一张列表(集合),用来维护Read View生成时刻,系统正活跃的事务ID
m_up_limit_id; //记录m_ids列表中事务ID最小的ID
m_low_limit_id; //ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
m_creator_trx_id //创建该ReadView的事务ID

由于事务ID是单向增长的,因此根据Read View中的m_up_limit_idm_low_limit_id,可以将事务ID分为三个部分:

  • 事务ID小于m_up_limit_id的事务
  • 事务ID位于m_up_limit_idm_low_limit_id之间的事务
  • 事务ID大于等于m_low_limit_id的事务

ReadView就是一个对象,初始化一次之后就不再改变了(只初始化一次)

在这里插入图片描述

  • 事务ID小于m_up_limit_id的事务:一定是生成Read View时已经提交的事务,因为m_up_limit_id是生成Read View时刻系统中活跃事务ID中的最小ID,因此事务ID比它小的事务在生成Read View时一定已经提交了
  • 事务ID位于m_up_limit_idm_low_limit_id之间的事务:该区间的事务,在生成Read View时可能正处于活跃状态,也可能已经提交了,这时需要通过判断事务ID是否存在于m_ids中来判断该事务是否已经提交(所有活跃的事务都在m_ids中)
  • 事务ID大于等于m_low_limit_id的事务:一定是生成Read View时还没有启动的事务,因为m_low_limit_id是生成Read View时刻,系统尚未分配的下一个事务ID

注意:事务ID不一定是连续的,比如事务ID10,15,16,17…

上述对应的隐藏字段:

  • 第一个区间,如果 m_creator_trx_id(创建该ReadView的事务ID)== DB_TRX_ID 或者 DB_TRX_ID< m_up_limit_id,则说明该事务是历史已经提交了的(已commit),应该被当前事务看到
  • 第二个区间,如果DB_TRX_ID不在m_ids列表中,说明该事务已经提交了(已commit),应该被当前事务看到。如果在的话m_ids列表中,说明该事务与当前事务都处于活跃状态(没有commit),不应该被当前事务看到
  • 第三个区间,DB_TRX_ID >= m_low_limit_id,说明该事务是快照之后才提交的事务,不应该被当前事务看到

对应的源码策略如下:

bool changes_visible(trx_id_t id, const table_name_t& name) const 
	MY_ATTRIBUTE((warn_unused_result))
{
	ut_ad(id > 0);
	//1、事务id小于m_up_limit_id(已提交)或事务id为创建该Read View的事务的id,则可见
	if (id < m_up_limit_id || id == m_creator_trx_id) {
		return(true);
	}
	check_trx_id_sanity(id, name);
	//2、事务id大于等于m_low_limit_id(生成Read View时还没有启动的事务),则不可见
	if (id >= m_low_limit_id) {
		return(false);
	}
	//3、事务id位于m_up_limit_id和m_low_limit_id之间,并且活跃事务id列表为空(即不在活跃列表中),则可见
	else if (m_ids.empty()) {
		return(true);
	}
	const ids_t::value_type* p = m_ids.data();
	//4、事务id位于m_up_limit_id和m_low_limit_id之间,如果在活跃事务id列表中则不可见,如果不在则可见
	return (!std::binary_search(p, p + m_ids.size(), id));
}

注意Read View是一个可见性的一个类,并不是事务创建出来就有Read View,而是当这个事务(已经存在)进行快照读的时候,MySQL才会形成Read View(进行select的时候,会自动形成)

7.7 Read View理论验证

下面进行 Read View的理论验证,即 Read View的整体流程

假设当前有条记录
在这里插入图片描述
有四个事务并发进行对该记录进行操作,事务4先进行修改,事务4进行修改完成之后,事务2进行快照读
在这里插入图片描述

  • 事务4:修改name(张三) 变成name(李四)
  • 事务2对某行数据执行了快照读,数据库为该行数据生成一个 Read View 读视图
//事务2的 Read View
m_ids; 		   // 1,3
m_up_limit_id;   // 1
m_low_limit_id;  // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
m_creator_trx_id // 2

此时版本链是:
在这里插入图片描述
只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务
在这里插入图片描述
事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟m_up_limit_idm_low_limit_id和活跃事务ID列表(m_ids) 进行比较,判断当前事务2能看到该记录的版本

//事务2的 Read View
m_ids; 			 // 1,3
up_limit_id;     // 1
low_limit_id;    // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
creator_trx_id   // 2

//事务4提交的记录对应的事务ID
DB_TRX_ID=4

//比较步骤
DB_TRX_ID(4< up_limit_id(1) ? 不小于,下一步
DB_TRX_ID(4>= low_limit_id(5) ? 不大于,下一步
m_ids.contains(DB_TRX_ID) ? 不包含,说明,事务4不在当前的活跃事务中

//结论
故,事务4的更改,应该看到
所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

八、RR与RC的本质区别

RR是可重复读的缩写,RC是读提交的缩写

我们之前的查询语句都是快照读,如果想进行当前读,执行下列语句:

-- 以加共享锁方式进行读取,对应的就是当前读
select * from 表名字 lock in share mode; -- 当前读

实验1

启动两个终端,将隔离级别都设置为可重复读,并查看此时表中的数据
在这里插入图片描述
两个终端各自启动一个事务,在左终端中的事务操作之前,先让右终端中的事务查看一下表中的信息
在这里插入图片描述
左终端中的事务对表中的信息进行修改并提交,右终端中的事务看不到修改后的数据
在这里插入图片描述
左边终端提交事务;右边终端进行当前读,可以看到最新的数据

select * from account lock in share mode;

在这里插入图片描述

实验2(进行同样的操作,只是SQL语句执行顺序不同)

  • 启动两个终端,将隔离级别都设置为可重复读,并查看此时表中的数据
  • 两个终端各自启动一个事务,在左终端中的事务操作之前,右边的终端不查看表中数据

在这里插入图片描述
左终端中的事务对表中的信息进行修改并提交,然后再让右终端中的事务进行查看,这时右终端中的事务就直接看到了修改后的数据
在这里插入图片描述
右边终端进行当前读,可以看到刚才读取到的确实是最新的数据
在这里插入图片描述

实验对比

上面两次实验的唯一区别在于,右终端中的事务在左终端中的事务修改数据之前是否进行过快照读

实验一的操作流程:
在这里插入图片描述

实验2的操作流程:

事务B在事务A修改age前没有进行过快照读
在这里插入图片描述

结论

  • RR级别下要求事务内每次读取到的结果必须是相同的,因此事务首次进行快照读的地方,决定了该事务后续快照读结果的能力

RR与RC的本质区别

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

  • 此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更
    新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见
  • 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事 务的修改对于当前事务都是不可见的
  • 而早于Read View创建的事务所做的修改均是可见
  • 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View,这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
  • 总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View
  • 而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View
  • 正是RC每次快照读,都会形成Read View,所以,RC才会有不可重复读问题。

--------------------- END ----------------------

「 作者 」 枫叶先生
「 更新 」 2023.9.10
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Latex]公式编辑,编号、对齐【持】

导言区 \documentclass{article} \usepackage{amsmath,amssymb,amsfonts,}%math-数学公式&#xff1b;symb-数学符号&#xff1b;fonts-字号&#xff1b;环境是否进入数学模式是否接受可选参数是否占满整行是否产生编号备注align是否是是align* 不产生编号&#xff0c;其他与 …

阿里云oss上传视频测试,出现了413错误

阿里云oss上传视频测试&#xff0c;出现了413错误 &#xff08;1&#xff09;nginx抛出问题&#xff0c;请求体过大 &#xff08;2&#xff09;修改nginx配置&#xff0c;重新加载生效 client_max_body_size 1024m;在cmd下运行命令&#xff1a;nginx.exe -s reload

基于canvas实现图片文字水印生成器

目录 介绍 1.静态页面结构 2.给生成水印按钮绑定点击事件 3.生成水印的函数 总结 介绍 在前端开发中时常会遇到需要给图片加上水印的功能&#xff0c;就像在创作csdn的文章时上传的图片都会打上传作者的水印&#xff0c;我们来探讨一下这个水印是如何生成的。 首先生成的文…

【大数据】CDC 技术:变化数据捕获

CDC 技术&#xff1a;变化数据捕获 1.什么是 CDC &#xff1f;2.批处理 vs CDC3.四种 CDC 的实现方法3.1 表元信息 Table metadata3.2 表求差 Table differences3.3 数据库触发器 Trigger-based CDC3.4 数据库事务日志 Log-based CDC 4.Oracle CDC 详解4.1 Oracle CDC 机制4.1.…

leetcode 52. N 皇后 II

2023.9.10 本题是皇后问题的变式&#xff0c;让求出不同解决方案的数量&#xff0c;和之前做过的 N皇后 基本一样&#xff0c;最终返回ans里棋盘的数量即可。 当复习一下皇后问题了&#xff0c;代码如下&#xff1a; class Solution { private:vector<vector<string&g…

无涯教程-JavaScript - AMORDEGRC函数

描述 AMORDEGRC函数返回每个会计期间的折旧。此功能是为法国会计系统提供的。如果在会计期间的中间购买资产,则会考虑按比Example折旧。 该功能类似于AMORLINC,不同之处在于,根据资产的寿命在计算中使用了折旧系数。 语法 AMORDEGRC (cost, date_purchased, first_period, …

SAP MM学习笔记29 - 供给元(供货源)的Block(拉黑)

前面学习了 供给元 的知识。 可以参考如下的URL SAP MM学习笔记28- 供给元&#xff08;供货源&#xff09;决定_东京老树根的博客-CSDN博客 有时候还有什么业务需求呢&#xff1f;就是比如突发要拉黑某个供应商 或 拉黑某个供应商的某个产品&#xff0c; 那又该如何做呢&…

202331读书笔记|《我笨拙地爱着这个世界(“外卖诗人”王计兵自选集)》——脚在泥泞,心有繁花

202331读书笔记|《我笨拙地爱着这个世界&#xff08;“外卖诗人”王计兵自选集&#xff09;》——脚在泥泞&#xff0c;心有繁花 《我笨拙地爱着这个世界&#xff08;“外卖诗人”王计兵自选集&#xff09;》作者王计兵。这是读的他的第二本书&#xff0c;比较有烟火气&#xf…

Spring Messaging远程命令执行漏洞复现(CVE-2018-1270)

一、漏洞说明 Spring Messaging为Spring框架提供消息支持&#xff0c;用户使用受影响版本的Spring Framework时&#xff0c;允许应用程序通过Spring Messaging模块内存中STOMP代理创建WebSocket。由于selector用SpEL表达式编写&#xff0c;并使用StandardEvaluationContext解析…

springBoot对接Apache POI 实现excel下载和上传

搭建springboot项目 此处可以参考 搭建最简单的SpringBoot项目_Steven-Russell的博客-CSDN博客 配置Apache POI 依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.2</version> </…

Flink JobManager的高可用配置

背景 在flink执行中&#xff0c;jobManager是一个负责执行流式应用执行和检查点生成的组件&#xff0c;一旦发生故障&#xff0c;那么其负责的所有应用都会被取消&#xff0c;所以我们需要对JobManager配置高可用的模式 JobManager高可用配置 配置JobManager的高可用需要使用…

微信小程序云开发数据懒加载+打破云数据库返回数据条数限制

目录 数据懒加载 打破数据表返回条数限制 数据懒加载 show.wxml <view wx:for="{{Adata}}" wx:key="index" style="padding: 80rpx 10rpx 140rpx;border-bottom: rgb(109, 134, 134) 2px solid;"><view style="margin-left: 20…

Notpad++常用正则表达式替换案例集锦

1、在每行的开头加上单引号 2、在每行的结尾加上单引号 3、“删除”某个关键字之前字符串 原始字符串&#xff1a; 注&#xff1a;仅保留含有"[条件日志]:"之后的内容&#xff0c;“日志:”前面的内容“删除”掉&#xff0c;即替换为“”。 4、“删除”某个关键字…

Discourse 可以支持的存储类型

根据官方的这个主题&#xff1a;Configure an S3 compatible object storage provider for uploads - sysadmin - Discourse Meta Discourse 可以支持很多不同的对象存储。 感觉上是只要和 S3 兼容的基本上都能用。 建议 从对象存储的角度考虑&#xff0c;还是建议使用 S3。…

UG\NX CAM二次开发 设置工序检查体 UF_CAMGEOM_append_items

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 设置工序检查体 UF_CAMGEOM_append_items 效果: 代码: static int init_proc(UF_UI_selection_p_t select, void* user_data) { int errorCode = 0; int num_triples = 1;//UF_UI_mask_t…

Python学习笔记:导入txt、xlsx文件并做简单函数处理

1.txt文件 1.1路径 file_path "E:\Python Project\temp.txt" with open(file_path) as f:content1 f.read() 导入文件时&#xff0c;如果直接放文件绝对路径上去会报错&#xff0c;这是因为\P是转义字符 所以在绝对路径前面加r可以避免将引号内的内容识别成转义…

视觉识别数字、十字路口和T字路口,巡线于一体的基于openmv的解决方案(2021年电赛f题)

普通二本生&#xff08;大二&#xff09;没获奖&#xff0c;因为驱动方面和视觉协同问题没有做好(驱动方面跑太快&#xff0c;速度降不下来)只跑了最初级的&#xff0c;这个文章就是去记录一下我的成长过程吧。 目录 1.使用神经网络来进行识别2.使用模板匹配来进行识别1.1 将这…

Idea上传gitee注意事项,push reject错误

一、 你在项目所在文件夹的空白处&#xff0c;鼠标右键&#xff0c;点击git bash here 会自动进入该目录下 二、 如果你遇到push reject 输入下面的命令&#xff1a; git pull origin master –allow-unrelated-historiesgit push -u origin master -f再次push就好了。 三、 …

教你怎么爬元气桌面的壁纸和视频

开发语言&#xff1a;我大前端必备的nodejs 看成果先&#xff1a; 这次爬下来的是手机端视频壁纸&#xff0c;共848个视频 -----------------------------------下边正式开始---------------------------------- 1、用fiddler抓包&#xff0c;查看接口地址 接口地址为&#…

springboot~自定义favicon加载问题

影响自定义favicon加载的原因 1、浏览器缓存问题2、由于favicon图标是在一个session会话中&#xff0c;所以需要关闭重开浏览器3、favicon源文件格式问题 1、浏览器缓存问题 清空浏览器缓存&#xff0c;或者是在network请求中停用缓存 2、由于favicon图标是在一个session会话中…