《深入理解Mybatis原理》Mybatis中的缓存实现原理
一级缓存实现
什么是一级缓存? 为什么使用一级缓存?
每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
如下图所示,MyBatis一次会话: 一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。
MyBatis中的一级缓存是怎样组织的?
即SqlSession中的缓存是怎样组织的?由于MyBatis使用SqlSession对象表示一次数据库的会话,那么,对于会话级别的一级缓存也应该是在SqlSession中控制的。
实际上, MyBatis只是一个MyBatis对外的接口,SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作。当创建了一个SqlSession对象时,MyBatis会为这个SqlSession对象创建一个新的Executor执行器,而缓存信息就被维护在这个Executor执行器中,MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。SqlSession、Executor、Cache之间的关系如下列类图所示:
如上述的类图所示,Executor接口的实现类BaseExecutor中拥有一个Cache接口的实现类PerpetualCache,则对于BaseExecutor对象而言,它将使用PerpetualCache对象维护缓存。
综上,SqlSession对象、Executor对象、Cache对象之间的关系如下图所示:
由于Session级别的一级缓存实际上就是使用PerpetualCache维护的,那么PerpetualCache是怎样实现的呢?
PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v>
来实现的,没有其他的任何限制。如下是PerpetualCache的实现代码:
package org.apache.ibatis.cache.impl; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock; import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException; /** * 使用简单的HashMap来维护缓存 * @author Clinton Begin */
public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) { this.id = id; } public String getId() { return id; } public int getSize() { return cache.size(); } public void putObject(Object key, Object value) { cache.put(key, value); } public Object getObject(Object key) { return cache.get(key); } public Object removeObject(Object key) { return cache.remove(key); } public void clear() { cache.clear(); } public ReadWriteLock getReadWriteLock() { return null; } public boolean equals(Object o) { if (getId() == null) throw new CacheException("Cache instances require an ID."); if (this == o) return true; if (!(o instanceof Cache)) return false; Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } public int hashCode() { if (getId() == null) throw new CacheException("Cache instances require an ID."); return getId().hashCode(); } }
一级缓存的生命周期有多长?
MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
-
如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
-
如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
-
SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
SqlSession 一级缓存的工作流程
-
对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
-
判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
-
如果命中,则直接将缓存结果返回;
-
如果没命中:去数据库中查询数据,得到查询结果;将key和查询到的结果分别作为key,value对存储到Cache中;将查询结果返回;
-
结束。
Cache接口的设计以及CacheKey的定义
如下图所示,MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Service Provider Interface) ,所有的MyBatis内部的Cache缓存,都应该实现这一接口。MyBatis定义了一个PerpetualCache实现类实现了Cache接口,实际上,在SqlSession对象里的Executor对象内维护的Cache类型实例对象,就是PerpetualCache子类创建的。
(MyBatis内部还有很多Cache接口的实现,一级缓存只会涉及到这一个PerpetualCache子类,Cache的其他实现将会放到二级缓存中介绍)。
我们知道,Cache最核心的实现其实就是一个Map,将本次查询使用的特征值作为key,将查询结果作为value存储到Map中。现在最核心的问题出现了:怎样来确定一次查询的特征值?换句话说就是:怎样判断某两次查询是完全相同的查询?也可以这样说:如何确定Cache中的key值?
MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询:
-
传入的 statementId
-
查询时要求的结果集中的结果范围 (结果的范围通过rowBounds.offset和rowBounds.limit表示)
-
这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )
-
传递给java.sql.Statement要设置的参数值
现在分别解释上述四个条件:
-
传入的statementId,对于MyBatis而言,你要使用它,必须需要一个statementId,它代表着你将执行什么样的Sql;
-
MyBatis自身提供的分页功能是通过RowBounds来实现的,它通过rowBounds.offset和rowBounds.limit来过滤查询出来的结果集,这种分页功能是基于查询结果的再过滤,而不是进行数据库的物理分页;
-
由于MyBatis底层还是依赖于JDBC实现的,那么,对于两次完全一模一样的查询,MyBatis要保证对于底层JDBC而言,也是完全一致的查询才行。而对于JDBC而言,两次查询,只要传入给JDBC的SQL语句完全一致,传入的参数也完全一致,就认为是两次查询是完全一致的。
-
上述的第3个条件正是要求保证传递给JDBC的SQL语句完全一致;第4条则是保证传递给JDBC的参数也完全一致;即3、4两条MyBatis最本质的要求就是:调用JDBC的时候,传入的SQL语句要完全相同,传递给JDBC的参数值也要完全相同。
综上所述,CacheKey由以下条件决定:statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值;
-
CacheKey的创建
对于每次的查询请求,Executor都会根据传递的参数信息以及动态生成的SQL语句,将上面的条件根据一定的计算规则,创建一个对应的CacheKey对象。
我们知道创建CacheKey的目的,就两个:
-
根据CacheKey作为key,去Cache缓存中查找缓存结果;
-
如果查找缓存命中失败,则通过此CacheKey作为key,将从数据库查询到的结果作为value,组成key,value对存储到Cache缓存中;
CacheKey的构建被放置到了Executor接口的实现类BaseExecutor中,定义如下:
/** * 所属类: org.apache.ibatis.executor.BaseExecutor * 功能 : 根据传入信息构建CacheKey */
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) throw new ExecutorException("Executor was closed."); CacheKey cacheKey = new CacheKey(); //1.statementId cacheKey.update(ms.getId()); //2. rowBounds.offset cacheKey.update(rowBounds.getOffset()); //3. rowBounds.limit cacheKey.update(rowBounds.getLimit()); //4. SQL语句 cacheKey.update(boundSql.getSql()); //5. 将每一个要传递给JDBC的参数值也更新到CacheKey中 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } //将每一个要传递给JDBC的参数值也更新到CacheKey中 cacheKey.update(value); } } return cacheKey;
}
-
CacheKey的hashcode生成算法
刚才已经提到,Cache接口的实现,本质上是使用的HashMap<k,v>
,而构建CacheKey的目的就是为了作为HashMap<k,v>
中的key值。而HashMap是通过key值的hashcode 来组织和存储的,那么,构建CacheKey的过程实际上就是构造其hashCode的过程。下面的代码就是CacheKey的核心hashcode生成算法,感兴趣的话可以看一下:
public void update(Object object) { if (object != null && object.getClass().isArray()) { int length = Array.getLength(object); for (int i = 0; i < length; i++) { Object element = Array.get(object, i); doUpdate(element); } } else { doUpdate(object); }
} private void doUpdate(Object object) { //1. 得到对象的hashcode; int baseHashCode = object == null ? 1 : object.hashCode(); //对象计数递增 count++; checksum += baseHashCode; //2. 对象的hashcode 扩大count倍 baseHashCode *= count; //3. hashCode * 拓展因子(默认37)+拓展扩大后的对象hashCode值 hashcode = multiplier * hashcode + baseHashCode; updateList.add(object);
}
MyBatis认为的完全相同的查询,不是指使用sqlSession查询时传递给算起来Session的所有参数值完完全全相同,你只要保证statementId,rowBounds,最后生成的SQL语句,以及这个SQL语句所需要的参数完全一致就可以了。
一级缓存的性能分析
-
MyBatis对会话(Session)级别的一级缓存设计的比较简单,就简单地使用了HashMap来维护,并没有对HashMap的容量和大小进行限制
读者有可能就觉得不妥了:如果我一直使用某一个SqlSession对象查询数据,这样会不会导致HashMap太大,而导致 java.lang.OutOfMemoryError错误啊? 读者这么考虑也不无道理,不过MyBatis的确是这样设计的。
MyBatis这样设计也有它自己的理由:
-
一般而言SqlSession的生存时间很短。一般情况下使用一个SqlSession对象执行的操作不会太多,执行完就会消亡;
-
对于某一个SqlSession对象而言,只要执行update操作(update、insert、delete),都会将这个SqlSession对象中对应的一级缓存清空掉,所以一般情况下不会出现缓存过大,影响JVM内存空间的问题;
-
可以手动地释放掉SqlSession对象中的缓存。
-
一级缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念
MyBatis的一级缓存就是使用了简单的HashMap,MyBatis只负责将查询数据库的结果存储到缓存中去, 不会去判断缓存存放的时间是否过长、是否过期,因此也就没有对缓存的结果进行更新这一说了。
根据一级缓存的特性,在使用的过程中,我认为应该注意:
-
对于数据变化频率很大,并且需要高时效准确性的数据要求,我们使用SqlSession查询的时候,要控制好SqlSession的生存时间, SqlSession的生存时间越长,它其中缓存的数据有可能就越旧,从而造成和真实数据库的误差;同时对于这种情况,用户也可以手动地适时清空SqlSession中的缓存;
-
对于只执行、并且频繁执行大范围的select操作的SqlSession对象,SqlSession对象的生存时间不应过长。
二级缓存实现
MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
MyBatis的缓存机制整体设计以及二级缓存的工作模式
如图所示,当开一个会话时,一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章。如果用户配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后在返回给用户。
CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式,CachingExecutor和Executor的接口的关系如下类图所示:
MyBatis二级缓存的划分
MyBatis并不是简单地对整个Application就只有一个Cache缓存对象,它将缓存划分的更细,即是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象,具体如下:
-
为每一个Mapper分配一个Cache缓存对象(使用
<cache>
节点配置)
MyBatis将Application级别的二级缓存细分到Mapper级别,即对于每一个Mapper.xml,如果在其中使用了<cache>
节点,则MyBatis会为这个Mapper创建一个Cache缓存对象,如下图所示:
注:上述的每一个Cache对象,都会有一个自己所属的namespace命名空间,并且会将Mapper的 namespace作为它们的ID;
-
多个Mapper共用一个Cache缓存对象(使用
<cache-ref>
节点配置)
如果你想让多个Mapper公用一个Cache的话,你可以使用<cache-ref namespace="">
节点,来指定你的这个Mapper使用到了哪一个Mapper的Cache缓存。
使用二级缓存,必须要具备的条件
MyBatis对二级缓存的支持粒度很细,它会指定某一条查询语句是否使用二级缓存。
虽然在Mapper中配置了<cache>
,并且为此Mapper分配了Cache对象,这并不表示我们使用Mapper中定义的查询语句查到的结果都会放置到Cache对象之中,我们必须指定Mapper中的某条选择语句是否支持缓存,即如下所示,在<select>
节点中配置useCache="true",Mapper才会对此Select的查询支持缓存特性,否则,不会对此Select查询,不会经过Cache缓存。如下所示,Select语句配置了useCache="true",则表明这条Select语句的查询会使用二级缓存。
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" useCache="true">
总之,要想使某条Select查询支持二级缓存,你需要保证:
-
MyBatis支持二级缓存的总开关:全局配置变量参数 cacheEnabled=true
-
该select语句所在的Mapper,配置了
<cache>
或<cached-ref>
节点,并且有效 -
该select语句的参数 useCache=true
一级缓存和二级缓存的使用顺序
请注意,如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 ———> 一级缓存 ——> 数据库。
二级缓存实现的选择
MyBatis对二级缓存的设计非常灵活,它自己内部实现了一系列的Cache缓存实现类,并提供了各种缓存刷新策略如LRU,FIFO等等;另外,MyBatis还允许用户自定义Cache接口实现,用户是需要实现org.apache.ibatis.cache.Cache接口,然后将Cache实现类配置在<cache type="">
节点的type属性上即可;除此之外,MyBatis还支持跟第三方内存缓存库如Memecached的集成,总之,使用MyBatis的二级缓存有三个选择:
-
MyBatis自身提供的缓存实现;
-
用户自定义的Cache接口实现;
-
跟第三方内存缓存库的集成;
MyBatis自身提供的二级缓存的实现
MyBatis自身提供了丰富的,并且功能强大的二级缓存的实现,它拥有一系列的Cache接口装饰者,可以满足各种对缓存操作和更新的策略。
MyBatis定义了大量的Cache的装饰器来增强Cache缓存的功能,如下类图所示。
对于每个Cache而言,都有一个容量限制,MyBatis各供了各种策略来对Cache缓存的容量进行控制,以及对Cache中的数据进行刷新和置换。MyBatis主要提供了以下几个刷新和置换策略:
-
LRU:(Least Recently Used),最近最少使用算法,即如果缓存中容量已经满了,会将缓存中最近最少被使用的缓存记录清除掉,然后添加新的记录;
-
FIFO:(First in first out),先进先出算法,如果缓存中的容量已经满了,那么会将最先进入缓存中的数据清除掉;
-
Scheduled:指定时间间隔清空算法,该算法会以指定的某一个时间间隔将Cache缓存中的数据清空;
如何细粒度地控制二级缓存
关于MyBatis的二级缓存的实际问题
现有AMapper.xml中定义了对数据库表 ATable 的CRUD操作,BMapper定义了对数据库表BTable的CRUD操作;
假设 MyBatis 的二级缓存开启,并且 AMapper 中使用了二级缓存,AMapper对应的二级缓存为ACache;
除此之外,AMapper 中还定义了一个跟BTable有关的查询语句,类似如下所述:
<select id="selectATableWithJoin" resultMap="BaseResultMap" useCache="true"> select * from ATable left join BTable on ....
</select>
执行以下操作:
-
执行AMapper中的"selectATableWithJoin" 操作,此时会将查询到的结果放置到AMapper对应的二级缓存ACache中;
-
执行BMapper中对BTable的更新操作(update、delete、insert)后,BTable的数据更新;
-
再执行1完全相同的查询,这时候会直接从AMapper二级缓存ACache中取值,将ACache中的值直接返回;
好,问题就出现在第3步上:
由于AMapper的“selectATableWithJoin” 对应的SQL语句需要和BTable进行join查找,而在第 2 步BTable的数据已经更新了,但是第 3 步查询的值是第 1 步的缓存值,已经极有可能跟真实数据库结果不一样,即ACache中缓存数据过期了!
总结来看,就是:
对于某些使用了 join连接的查询,如果其关联的表数据发生了更新,join连接的查询由于先前缓存的原因,导致查询结果和真实数据不同步;
从MyBatis的角度来看,这个问题可以这样表述:
对于某些表执行了更新(update、delete、insert)操作后,如何去清空跟这些表有关联的查询语句所造成的缓存
当前MyBatis二级缓存的工作机制
MyBatis二级缓存的一个重要特点:即松散的Cache缓存管理和维护
一个Mapper中定义的增删改查操作只能影响到自己关联的Cache对象。如上图所示的Mapper namespace1中定义的若干CRUD语句,产生的缓存只会被放置到相应关联的Cache1中,即Mapper namespace2,namespace3,namespace4 中的CRUD的语句不会影响到Cache1。
可以看出,Mapper之间的缓存关系比较松散,相互关联的程度比较弱。
现在再回到上面描述的问题,如果我们将AMapper和BMapper共用一个Cache对象,那么,当BMapper执行更新操作时,可以清空对应Cache中的所有的缓存数据,这样的话,数据不是也可以保持最新吗?
确实这个也是一种解决方案,不过,它会使缓存的使用效率变的很低!AMapper和BMapper的任意的更新操作都会将共用的Cache清空,会频繁地清空Cache,导致Cache实际的命中率和使用率就变得很低了,所以这种策略实际情况下是不可取的。
最理想的解决方案就是:
对于某些表执行了更新(update、delete、insert)操作后,去清空跟这些指定的表有关联的查询语句所造成的缓存; 这样,就是以很细的粒度管理MyBatis内部的缓存,使得缓存的使用率和准确率都能大大地提升。
文章转载自:seven97_top
原文链接:《深入理解Mybatis原理》Mybatis中的缓存实现原理 - seven97_top - 博客园
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
《深入理解Mybatis原理》Mybatis中的缓存实现原理
一级缓存实现 什么是一级缓存? 为什么使用一级缓存? 每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句&…...
【Debug】django.db.utils.OperationalError: (1040, ‘Too many connections‘)
报错: django.db.utils.OperationalError: (1040, ‘Too many connections‘) 排查 可能是Mysql的连接数量超过了允许的最大连接数量; 查看Mysql允许最大连接数量: -- 查看允许连接的最大数量 SHOW VARIABLES LIKE %max_connections%;-- 查…...
常用教程备份
1.Ubuntu 系统软件安装教程 https://blog.csdn.net/weixin_51591021/article/details/134363237 2.Docker 教程 https://blog.csdn.net/weixin_51591021/article/details/134363849 3.Makefile 教程 https://blog.csdn.net/weixin_51591021/article/details/134363638 4.…...
什么是视频孪生智慧能源?视频孪生智慧能源的应用案例
视频孪生智慧能源是集三维地理信息系统、视频虚实融合、数字孪生、人工智能等多技术于一体的综合应用,旨在实现对能源系统的实时、动态、全方位监控和管理。 具体来说,视频孪生智慧能源通过以下方式实现其功能: 技术融合:…...
Kubernetes1.28 编译 kubeadm修改证书有效期到 100年.并更新k8s集群证书
文章目录 前言一、资源准备1. 下载对应源码2.安装编译工具3.安装并设置golang 二、修改证书有效期1.修改证书有效期2.修改 CA 证书有效期 三、编译kubeadm四、使用新kubeadm方式1.当部署新集群时,使用该kubeadm进行初始化2.替换现有集群kubeadm操作 前言 kubeadm 默认证书为一…...
时序数据库的订阅对比:TDengine vs InfluxDB 谁更强?
目录 1. 架构:内置 vs 依赖外部 TDengine: InfluxDB: 2. 灵活性:动态订阅 vs 静态订阅 TDengine: InfluxDB: 3. 消费机制、API 兼容性与易用性对比 4. 结语 在时序数据应用场景中,数据实时消费和处理能力成为衡量数据库性能和可用性的…...
OpenCV实现多尺度细节提升算法
1、算法原理 多尺度细节提升算法来源于论文*《DARK IMAGE ENHANCEMENT BASED ON PAIRWISE TARGET CONTRAST AND MULTI-SCALE DETAIL BOOSTING》*,算法主要是解决细节增强算法中噪声和细节的平衡问题。 常规的非锐化掩蔽(USM)算法在提升细节…...
按键精灵ios越狱脚本教程:多选框联动的ui界面
以下是一个简单的 iOS 代码示例,使用 Swift 语言来创建一个包含多选框(复选框)的 UI 界面,并实现联动效果。 import UIKitclass ViewController: UIViewController {let checkbox1 UIButton(type:.system)let checkbox2 UIButt…...
YOLOv10-1.1部分代码阅读笔记-patches.py
patches.py ultralytics\utils\patches.py 目录 patches.py 1.所需的库和模块 2.def imread(filename: str, flags: int cv2.IMREAD_COLOR): 3.def imwrite(filename: str, img: np.ndarray, paramsNone): 4.def imshow(winname: str, mat: np.ndarray): 5.def tor…...
撤回最近的 git commit
在 Git 中,如果你想撤回最近的 git commit,可以根据不同的需求选择不同的操作。以下是几种常见的撤回方式: 1. 撤回最后一次 commit,但保留修改(soft reset) 如果你想撤销 git commit,但保留修…...
基于DFT与IIR-FIR滤波器的音频分析与噪声处理
基于DFT与IIR-FIR滤波器的音频分析与噪声处理 【完整源码文档报告】 【需要可随时联系博主,常在线能秒回!】 系统功能与实现介绍 功能与实现 音频处理系统界面搭建:利用MATLAB的GUI工具,构建了音频分析界面,包括文件导入、录…...
MySQL主从部署(保姆版)
一、mysql 同步复制有关概述 一般数据库都是读取压力大于写数据压力,主从复制即为了实现数据库的负载均衡和读写分离。通过将Mysql的某一台主机的数据复制到其它主机(slaves)上,主服务器只负责写,而从服务器只负责读。…...
Golang笔记——协程同步
大家好,这里是Good Note,关注 公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Golang的协程同步的实现和应用场景。 文章目录 协程同步是什么?为什么需要协程同步?常见的协程同步机制互斥锁࿰…...
1.14学习
misc buuctf-大白 由提示可以知道这个应该是修改图片的宽高了,下载附件后得到了图片用随波逐流直接修改图片的宽高输出即可 buuctf-乌镇峰会种图 点击下载,出现了一个网页为图片将图片另存为,用随波逐流得到的信息解不了,再试…...
2025 年 JavaScript 入门教程
2025 年 JavaScript 入门教程 在当今数字化时代,JavaScript 作为一门广泛应用于 Web 开发的编程语言,其重要性不言而喻。无论是前端页面的交互实现,还是后端服务器的逻辑处理,JavaScript 都发挥着关键作用。本教程旨在帮助初学者…...
paddle——站在巨人肩膀上及背刺二三事
飞桨AI Studio - 人工智能学习与实训社区 飞桨PaddlePaddle-源于产业实践的开源深度学习平台 先抛结论,对于想要快速了解某一领域有哪些比较适合落地的算法的从业人员来说,是一个很好的参考系统。从中可以知道从哪些模型里选型、如何轻量化、如何加…...
nvim , neovim , Lua 语法, text object
说明 : 了解一下 nvim 中的基本的 文本的类型。 基本类型有几种, 1 word , sentence , paragragh 2 (), {}, ,"", 3 就是 html 中的 tag 标签。 然后就是选中的类型。 1 i : 待变 inner 2 a: 代表around , 基本的动作有 &…...
6.2 MySQL时间和日期函数
以前我们就用过now()函数来获得系统时间,用datediff()函数来计算日期相差的天数。我们在计算工龄的时候,让两个日期相减。那么其中的这个now函数返回的就是当前的系统日期和时间。 1. 获取系统时间函数 now()函数,返回的这个日期和时间的格…...
批量识别图片型PDF指定区域内容识别保存表格+PDF批量改名:技术难题与项目实战总结
相关项目实战: 一、引言 在当今数字化办公环境中,批量处理PDF文件中的表格数据并进行改名是一项常见但具有挑战性的任务。无论是从大量的财务报销凭证、学术研究报告还是项目文档中提取表格信息,都可能遇到各种各样的技术难题。 二、批量提…...
【MySQL】索引(一)
索引 一、磁盘1、物理结构2、示意图3、定位扇区4、读写操作的基本方式 二、页1、介绍2、示例3、作用与结构4、类型(1)数据页(2)其他 5、组织与管理6、性能优化7、示意图(B树) 三、索引1、作用2、注意事项 四…...
缩放 对内外参的影响
当你对图像进行同比例缩小时,图像的内参需要相应地变化,但外参通常保持不变。 相机内参 相机内参(内参矩阵)描述了相机的固有属性,包括焦距和主点(光轴与图像平面的交点)的坐标。 当你对图像…...
excel设置好的可选择列数据后,如何快速输入到单元格中?
当设置好列的【数据】-【数据有效性】-【序列】后,在单元格中输入可选择数据的开头,就会提示出对应的可选择数据,然后,按一下键盘上的【↓】键,再按回车,即可快速输入到单元格中。...
设计一个流程来生成测试模型安全性的问题以及验证模型是否安全
要使用 Ollama 运行 llama3.3:70b 模型,并设计一个流程来生成测试模型安全性的问题以及验证模型是否安全,可以按照以下步骤进行设计和实现。整个过程包括环境配置、设计安全测试提示词、执行测试以及分析结果。以下是详细的步骤和指导: 1. 环…...
vue3学习日记5 - 项目起步
最近发现职场前端用的框架大多为vue,所以最近也跟着黑马程序员vue3的课程进行学习,以下是我的学习记录 视频网址: Day2-11.项目起步-静态资源引入和ErrorLen安装_哔哩哔哩_bilibili 学习日记: vue3学习日记1 - 环境搭建-CSDN博…...
ESP32,uart安装驱动uart_driver_install函数剖析,以及intr_alloc_flags 参数的意义
在 uart_driver_install 函数中,参数 RX_BUF_SIZE * 2 指定了接收缓冲区(RX buffer)的大小。这个参数对于 UART 驱动程序来说非常重要,因为它决定了可以存储多少接收到的数据,直到应用程序读取它们为止。下面是对该函数…...
android源码编译后,为什么emulator一直黑屏或者停止android界面
一、前言 最近编译了android12的源码,但是编译完成之后,emulator命令一直卡在android界面上,不会进入系统。经过我多方面的研究,终于找到解决方案。记录下来,希望对遇到这个问题的朋友有所帮助。 二、解决方案 网上…...
ubuntu22.04降级安装CUDA11.3
环境:主机x64的ubuntu22.04,原有CUDA12.1,但是现在需要CUDA11.3,本篇文章介绍步骤。 一、下载CUDA11.3的run文件 下载网址:https://developer.nvidia.com/cuda-11-3-1-download-archive?target_osLinux&target_…...
hive知识体系
hive知识体系 hive知识体系 链接: 1Hive概览 链接: 2Hive表类型 链接: 3Hive数据抽样 链接: 4Hive计算引擎 链接: 5Hive存储与压缩 链接: 6Hive Sql 大全 链接: 6Hive Sql 大全-Hive 函数 链接: 6Hive Sql 大全-窗口函数 链接: 7Hive执行计划 链接: 8Hive SQL底层执行原理 链接…...
【ASP.NET学习】Web Forms创建Web应用
文章目录 什么是 Web Forms?ASP.NET Web Forms - HTML 页面用 ASP.NET 编写的 Hello RUNOOB.COM它是如何工作的?经典 ASP ASP.NET Web Forms - 服务器控件经典 ASP 的局限性ASP.NET - 服务器控件ASP.NET - HTML 服务器控件ASP.NET - Web 服务器控件ASP.N…...
【Qt】QThread总结
目录 成员函数创建方式方式一方式二方式三注意 example总结参考文章 成员函数 创建方式 方式一 QThread 静态成员create auto thd QThread::create([]{});方式二 继承QThread类,重写run run函数它作为线程的入口,也就是线程从run()开始执行&#…...
常见的安全测试漏洞详解
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 一、SQL注入攻击 SQL 注入攻击主要是由于程序员在开发过程中没有对客户端所传输到服务器端的参数进行严格的安全检查,同时 SQL 语句的执行引用了该参…...
代理模式和适配器模式有什么区别
代理模式(Proxy Pattern)和适配器模式(Adapter Pattern)都是结构型设计模式,它们有不同的应用场景和目标,虽然在某些方面看起来相似,但它们的意图和实现方式有显著的区别。 1. 代理模式&#x…...
机器学习头歌(第三部分-强化学习)
一、强化学习及其关键元素 二、强化学习的分类 三、任务与奖赏 import numpy as np# 迷宫定义 maze np.array([[0, 0, 0, 0, 0],[0, -1, -1, 0, 0],[0, 0, 0, -1, 0],[-1, -1, 0, -1, 0],[0, 0, 0, -1, 1] ])# 定义强化学习的参数 gamma 0.8 # 折扣因子 alpha 0.5 # 学习率…...
【Hive】新增字段(column)后,旧分区无法更新数据问题
TOC 【一】问题描述 Hive修改数据表结构的需求,比如:增加一个新字段。 如果使用如下语句新增列,可以成功添加列col1。但如果数据表tb已经有旧的分区(例如:dt20190101),则该旧分区中的col1将为…...
智能化的城市管理解决方案,智慧城管执法系统源码,微服务架构、Java编程语言、Spring Boot框架、Vue.js前端技术
智慧城管执法系统是一种高度信息化、智能化的城市管理解决方案,它利用现代信息技术,如微服务架构、Java编程语言、Spring Boot框架、Vue.js前端技术、Element UI组件库、UniApp跨平台开发工具以及MySQL数据库等,构建了一个综合性的执法管理平…...
【区间DP】【hard】力扣1312. 让字符串成为回文串的最少插入次数
加粗样式给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。 请你返回让 s 成为回文串的 最少操作次数 。 「回文串」是正读和反读都相同的字符串。 示例 1: 输入:s “zzazz” 输出:0 解释:字…...
android刷机
android ota和img包下载地址: https://developers.google.com/android/images?hlzh-cn android启动过程 线刷 格式:ota格式 模式:recovery 优点:方便、简单,刷机方法通用,不会破坏手机底层数据࿰…...
web-前端小实验8
实现以上图片中的内容 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…...
C++实现设计模式---单例模式 (Singleton)
单例模式 (Singleton) 概念 单例模式 确保一个类在整个程序生命周期中只有一个实例,并提供一个全局访问点。 它是一种创建型设计模式,广泛用于需要共享资源的场景。 使用场景 配置管理器:程序中需要一个全局的配置对象。日志系统ÿ…...
【大数据】机器学习-----线性模型
一、线性模型基本形式 线性模型旨在通过线性组合输入特征来预测输出。其一般形式为: 其中: x ( x 1 , x 2 , ⋯ , x d ) \mathbf{x}(x_1,x_2,\cdots,x_d) x(x1,x2,⋯,xd) 是输入特征向量,包含 d d d 个特征。 w ( w 1 , w 2 , ⋯ ,…...
C#类型转换
C#是静态类型的语言,变量一旦声明就无法重新声明或者存储其他类型的数据,除非进行类型转换。本章的主要任务就是学习类型转换的知识。类型转换有显式的,也有隐式的。所谓显式,就是我们必须明确地告知编译器,我们要把变…...
OpenCV相机标定与3D重建(55)通用解决 PnP 问题函数solvePnPGeneric()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 根据3D-2D点对应关系找到物体的姿态。 cv::solvePnPGeneric 是 OpenCV 中一个更为通用的函数,用于解决 PnP 问题。它能够返回多个可能…...
NVIDIA CUDA Linux 官方安装指南
本文翻译自:https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#post-installation-actions NVIDIA CUDALinux安装指南 CUDA工具包的Linux安装说明。 文章目录 1.导言1.1.系统要求1.2.操作系统支持政策1.3.主机编译器支持政策1.3.1.支持的C方言…...
C++中的STL
STL(标准模板库)在广义上分为:容器,算法,迭代器 容器和算法之间通过迭代器进行无缝衔接 STL大体上分为六大组件:分别为容器,算法,迭代器,仿函数,适配器,空间…...
前端进程和线程及介绍
前端开发中经常涉及到进程和线程的概念,特别是在浏览器中。理解这两个概念对于理解浏览器的工作机制和前端性能优化非常重要。以下是详细介绍: 1. 什么是进程和线程? 进程: 是操作系统分配资源的基本单位。一个程序启动后…...
本地用docker装mysql
目录 拉取镜像查看镜像 启动容器查看运行中的容器连接到 MySQL 容器其他一些操作 装WorkBench链接mysql——————————————允许远程登录MySql 拉取镜像 docker pull mysql查看镜像 docker image lsREPOSITORY TAG IMAGE ID CREATED SIZE mysq…...
设计模式 行为型 责任链模式(Chain of Responsibility Pattern)与 常见技术框架应用 解析
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许将请求沿着处理者链进行发送。每个处理者对象都有机会处理该请求,直到某个处理者决定处理该请求为止。这种模式的主要目的是避免请求的发送者和接收者之间…...
Apache Spark中与数据分区相关的配置和运行参数
Apache Spark中与数据分区相关的配置和运行参数涉及多个方面,包括动态分区设置、分区数设置、Executor与并行度配置等。合理配置这些参数可以显著提高Spark作业的执行效率和资源利用率。在实际应用中,建议根据业务需求和计算集群的特性进行相应的调整和测…...
“深入浅出”系列之设计模式篇:(0)什么是设计模式
设计模式六大原则 1. 单一职责原则:一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化。 核心思想:控制类的粒度大小,将对象解耦,提高其内聚性。 2. 开闭原则:对扩展开放…...
【Git版本控制器--1】Git的基本操作--本地仓库
目录 初识git 本地仓库 认识工作区、暂存区、版本库 add操作与commit操作 master文件与commit id 修改文件 版本回退 撤销修改 删除文件 初识git Git 是一个分布式版本控制系统,主要用于跟踪文件的更改,特别是在软件开发中。 为什么要版本…...