select for update是行锁还是表锁,还真得看情况

news2024/11/15 15:52:37

背景

看到许多写select for update是行锁还是表锁的文章,但每篇文章的结论好像都不太一样。同时,是行锁还是表锁的问题直接影响着系统的性能,所以特意为大家调研一番,也就有了本篇文章,一共为大家汇总验证了20个场景下的结论。

对于软件或框架来说,特别是在有大版本更新的情况下,脱离了具体版本的结论往往是无意义的。针对这个问题,网络上之所以有多个版本的答案,最主要的原因就是脱离MySQL的版本以及事务隔离级别。

本文就基于两个MySQL版本(5.7.x、8.0.x)、两种常见事务隔离级别(读已提交、可重复读)来逐一验证。总共有四大类情况,20个小场景。最后,再给大家汇总一个结论性的验证结果。大家可以收藏,已备用到时查阅对照。

通过阅读本文,你不仅能能够学到相关的结论,同时也提供了一套科学的实验方法论,个人觉得后者对大家来说更为重要。

环境准备

在验证之前,我们先准备好具体的环境和数据。

建表语句:

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_no` varchar(16) DEFAULT NULL COMMENT '用户编号',
  `user_name` varchar(16) DEFAULT NULL COMMENT '用户名',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `address` varchar(128) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_user_no` (`user_no`),
  KEY `idx_user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

初始化数据:

insert into user values(null,'0001','user01',18,'北京');
insert into user values(null,'0002','user02',19,'上海');
insert into user values(null,'0003','user03',20,'广州');
insert into user values(null,'0004','user04',21,'深圳');
insert into user values(null,'0005','user05',22,'杭州');

数据库版本:

版本一:
>select @@version;
5.7.22

版本二:
>select @@version;
8.0.18

查询数据事务隔离级别:

>select @@transaction_isolation;
REPEATABLE-READ

MySQL innodb支持的四种事务隔离级别:

  • READ_UNCOMMITTED:读未提交;
  • READ_COMMITTED:读已提交,后文简称为RC;
  • REPEATABLE_READ:可重复读,MySQL默认的事务隔离级别。后文简称为RR;
  • SERIALIZABLE:串行读;

设置全局隔离级别:

set global transaction isolation level REPEATABLE READ;
set global transaction isolation level READ COMMITTED;

设置会话隔离级别:

set session transaction isolation level REPEATABLE READ;
set session transaction isolation level READ COMMITTED;

关闭自动提交:

> set @@autocommit=0;  //设置自动提交关闭

在执行完锁语句之后,可执行commit命令进行事务提交。

commit;

准备完以上数据,便可以开始每一个场景的验证了。每个场景都起了一个编号,比如:V5.x-RR-主键,表示在MySQL 5.7.x,事务隔离级别为RR(可重复读),条件字段为主键的场景下进行的实验。

场景1.1:V5.x-RR-主键

操作:使用主键ID作为条件查询,然后新开启一个事务去更新数据。

分析思路:一,如果更新数据被阻塞,则说明加锁成功;二,如果更新其他数据成功,则说明是行锁,如果更新其他数据失败则说明是表锁。三,部分场景会测试插入操作;后续所有操作基本雷同。

执行悲观锁查询:

select * from user where id = 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

在此场景下,来看一下数据库加的什么锁。

当第二条语句被阻塞时,执行查看锁信息语句:

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

注意,必须是正在执行第二条语句,且第二条语句处于阻塞状态下,上述语句才能查询到数据。

查询结果如下:

锁信息

第二条记录为for update锁表语句,第一条记录为单纯的update语句。可以看出,此场景下,lock_mode为X,lock_type为RECORD,lock_data为1。

lock_mode为X(排他锁):即写锁,允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

lock_type为RECORD,说是是行级锁,lock_data表示锁定了1条记录。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为主键时,select for update为行级锁。

当我们执行完一个场景之后,我们需要执行commit命令将当前事物提交。

场景1.2:V5.x-RR-唯一索引

执行悲观锁操作:

select * from user where user_no = '0001' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息,同场景一的主键一致。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为唯一索引时,select for update为行级锁。

场景1.3:V5.x-RR-普通索引

执行悲观锁操作:

select * from user where user_name = 'user01' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息:

锁信息

此时,锁类型不仅仅是X排他锁,同时还添加了GAP(间隙锁),也就是说针对数据添加了排他间隙锁。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

此时再进行一笔插入操作:

insert into user values(null,'0006','user05',23,'重庆');

执行成功。

由于存在了间隙锁,再执行一笔user_name与查询条件相同的插入操作:

insert into user values(null,'0008','user01',24,'成都');

执行阻塞,说明此时有排他间隙锁的存在。

结论:当查询条件为普通索引时,select for update为行级锁,同时会有排他间隙锁存在,当插入数据满足锁语句查询条件(相等、范围等)时,会发生阻塞。

场景1.4:V5.x-RR-无索引

执行悲观锁操作:

select * from user where address = '北京' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行被阻塞

此时查询锁表信息展示如下:

锁信息

这里比较奇怪是lock_type,很明显,上述锁操作已经锁住了整张表,但lock_type依旧为RECORD。出处暂时有些费解。

结论:当查询条件无索引时,select for update为表级锁。

场景1.5:V5.x-RR-索引-范围查询

执行悲观锁操作:

select * from user where id > 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

执行成功,说明并没有锁定id为1的记录。

执行插入操作:

insert into user values(null,'0007','user07',24,'武汉');

插入操作被阻塞。这是因为插入的数据生成的id满足大于1的条件,会被阻塞。

所信息如下:

锁信息

此时,lock_type虽然是RECORD,但是lock_data显示supremum pseudo-record ,这就是InnoDB为了解决幻读问题的临键锁(Next-key Lock),这里间隙锁和临键锁可以看做是一样的。

需要注意的是:supremum pseudo-record有可能是间隙锁,需要结合死锁日志里的heap no判断。heap no 1是间隙锁。

结论:当查询条件有索引且查询条件为范围时,select for update会采用间隙锁或临键锁,对指定范围内的数据进行加锁。当然,当查询条件无索引时,与场景1.4一致,为表锁。

场景2.1:V8.x-RR-主键

执行悲观锁查询:

select * from user where id = 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查看数据库对应的锁:

SELECT * FROM performance_schema.data_locks;

注意,在MySQL 8中,采用了performance_schema替代了MySQL5中基于INFORMATION_SCHEMA的锁查询方式。

锁信息

上述查询结果中,有两条记录。lock_type字段展示锁范围,lock_mode字段展示了锁的类型。可以看到,该SQL语句先是在表范围上加了一把IX(意向排他锁,表锁)。然后,在记录(Record)范围上添加了一把X(排他锁),一把REC_NOT_GAP(行锁),综合起来就是对这条记录添加了行级排他锁,其他事务不能够再对其添加任何锁了。

这里,既然在表的层面上添加了IX(意向排他锁),为什么不是表锁呢?这是因为意向排他锁的作用仅仅表名意向的锁,当其他事务要对全表的数据进行加锁时,那么就不需要判断每一条数据是否被加锁了。

事务在给一行记录加排他锁前,必须先取得该表的IX锁,意向排他锁之间相互兼容,可以并行,不会产生冲突。意向排他锁存在的意义是为了更高效的获取表锁,主要目的是显示事务正在锁定某行或者试图锁定某行。

继续实验,执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为主键时,select for update为行级锁。

场景2.2:V8.x-RR-唯一索引

执行悲观锁操作:

select * from user where user_no = '0001' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息:

锁信息

此时,可以看到三把锁,一把表级别的IX锁,一把基于唯一索引的行级排他锁,一把基于主键的行级排他锁。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为唯一索引时,select for update为行级锁。

场景2.3:V8.x-RR-普通索引

执行悲观锁操作:

select * from user where user_name = 'user01' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息:

锁信息

此时,可以看到四把锁,一把表级别的IX锁,一把基于普通索引的X排他锁,一把基于主键的行级排他锁,一把基于普通索引的X,GAP排他间隙锁。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功,说明更新操作没有影响。

既然有排他间隙锁,此时需再测试一笔插入操作:

insert into user values(null,'0006','user05',23,'重庆');

执行成功。

再执行一笔插入操作:

insert into user values(null,'0007','user01',24,'武汉');

注意这里插入的记录user_name与锁查询条件相同,发现操作被阻塞。

通过两笔插入操作可以看出,排他间隙锁会阻塞符合查询条件(user_name=‘user01’)的数据的插入。

结论:当查询条件为普通索引时,select for update为行级锁,同时会多一把排他间隙锁,如果插入数据满足锁语句的查询条件(等于、范围条件等),则无法插入。

场景2.4:V8.x-RR-无索引

执行悲观锁操作:

select * from user where address = '北京' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息:

锁信息

此时,数据库一共加了8把锁,一把表级别的IX意向排他锁,6把基于主键的针对数据记录(总共6条)的X锁,一把针对记录的supremum pseudo-record锁。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行被阻塞

结论:当查询条件无索引时,select for update为表级锁。

场景2.5:V8.x-RR-索引-范围查询

执行悲观锁操作:

select * from user where id > 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

执行成功,说明并没有锁定id为1的记录。

执行插入操作:

insert into user values(null,'0007','user07',24,'武汉');

插入操作被阻塞。这是因为插入的数据生成的id满足大于1的条件,会被阻塞。

查询锁信息如下:

锁信息

此时,锁信息对比场景2.4,少了一条不满足条件记录(id=1)的锁,其他符合条件的数据均被锁。

结论:当查询条件有索引且查询条件为范围时,select for update会采用间隙锁或临键锁,对指定范围内的数据进行加锁。

完成了上面针对RR事务隔离级别的验证,下面将数据库事务隔离级别切换为RC。

set global transaction isolation level READ COMMITTED;

注意,此处可能需要重启数据库,如果通过命令配置无效,可通过数据库配置文件进行配置,重启。

另外,也可以通过在所有命令窗口执行session级别的设置,也可以达到效果,设置完成之后注意需要进行验证。

场景3.1:V5.x-RC-主键

执行悲观锁查询:

select * from user where id = 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

锁信息与RR事务相同。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为主键时,select for update为行级锁。

场景3.2:V5.x-RC-唯一索引

执行悲观锁操作:

select * from user where user_no = '0001' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息,与RR一致。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为唯一索引时,select for update为行级锁。

场景3.3:V5.x-RC-普通索引

执行悲观锁操作:

select * from user where user_name = 'user01' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息如下:

锁信息

再把RR场景下的锁信息贴出来:

锁信息

可以看出,RC事务隔离级别时比RR事务隔离级别时少了一个GAP(间隙锁)。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

此时再进行一笔插入操作:

insert into user values(null,'0009','user01',24,'郑州');

执行成功。

再验证下间隙锁是否真的不存在,执行一笔user_name与查询条件相同的插入操作:

insert into user values(null,'0008','user01',24,'成都');

执行成功,说明此时间隙锁的不存在了。

结论:当查询条件为普通索引时,select for update为行级锁,无间隙锁。

场景3.4:V5.x-RC-无索引

执行悲观锁操作:

select * from user where address = '北京' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

锁信息如下:

锁信息

显示基于主键的排他锁,这块挺出乎意料的,并没有进行表锁。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

再执行一笔插入操作,插入数据与查询条件address一致:

insert into user values(null,'0011','user01',24,'北京');

执行成功。

结论:当查询条件无索引时,select for update为行级锁,也就说,在RC事务隔离级别下,即便无索引,也是只锁记录,与通常的直知觉不同。

原因:会出现上述情况的原因是,本来如果锁条件上没有索引,MySQL会走聚簇(主键)索引进行全表扫描过滤,每条记录都会添加上X锁。但为了效率,MySQL会对扫描过程中不满足条件的记录进行解锁操作。

场景3.5:V5.x-RC-索引-范围查询

执行悲观锁操作:

select * from user where id > 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

执行成功,说明并没有锁定id为1的记录。

执行更新操作:

update user set age = age +1 where id = 2;

操作被阻塞。这是因为操作的数据的id满足大于1的条件,会被阻塞。

所信息如下:

锁信息

结论:当查询条件有索引且查询条件为范围时,select for update对指定范围内的数据进行加锁。

场景4.1:V8.x-RC-主键

执行悲观锁查询:

select * from user where id = 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

锁信息同RR。

继续实验,执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为主键时,select for update为行级锁。

场景4.2:V8.x-RC-唯一索引

执行悲观锁操作:

select * from user where user_no = '0001' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

锁信息同RR。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件为唯一索引时,select for update为行级锁。

场景4.3:V8.x-RC-普通索引

执行悲观锁操作:

select * from user where user_name = 'user01' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息:

锁信息

对照一下RR场景下的锁信息:

锁信息

可以看出RC场景下笔RR场景下少了一条行级间隙锁。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功,说明更新操作没有影响。

验证一下是否有排他间隙锁,此时需再测试一笔插入操作:

insert into user values(null,'0010','user05',23,'重庆');

执行成功。

再执行一笔插入操作:

insert into user values(null,'0007','user01',24,'武汉');

注意这里插入的记录user_name与锁查询条件相同,执行成功,说明真的不存在X,GAP(排他间隙锁)。

结论:当查询条件为普通索引时,select for update为行级锁。

场景4.4:V8.x-RC-无索引

执行悲观锁操作:

select * from user where address = '北京' for update;

执行更新操作:

update user set age = age +1 where id = 1;

此处更新操作被阻塞,说明数据锁定成功。

查询锁信息:

锁信息

对照一下RR场景:

锁信息

对于RR场景,RC场景下,只有一条排他行锁(X,REC_NOT_GAP)。

执行更新其他记录操作:

update user set age = age +1 where id = 2;

执行成功。

结论:当查询条件无索引时,select for update为行级锁。这里的原因与场景3.4一致。

场景4.5:V8.x-RC-索引-范围查询

执行悲观锁操作:

select * from user where id > 1 for update;

执行更新操作:

update user set age = age +1 where id = 1;

执行成功,说明并没有锁定id为1的记录。

执行插入操作:

insert into user values(null,'0012','user12',24,'--');

执行成功。

查询锁信息如下:

锁信息

对照RR场景下的锁信息:

锁信息

此时,RC场景下,少了临键锁,排他锁也变为了行级排他锁。

结论:当查询条件有索引且查询条件为范围时,select for update会对指定范围内的数据进行加锁,只会阻塞符合条件的记录,不影响插入操作。

场景及结论

完成了上面的实验之后,我们通过一个表格来总结一下所有的场景和结论。

版本主键唯一索引普通索引无索引范围查询
MySQL 5.7.x - RRX:行锁X,行锁X,GAP:行锁,间隙锁,条件范围内会阻塞表锁指定范围加锁,insert阻塞
MySQL 8.0.x - RRX,REC_NOT_GAP:行级排他锁X,REC_NOT_GAP:行级排他锁X;X,REC_NOT_GAP;X,GAP:行锁+排他间隙锁,阻塞范围内insert;表锁,每条记录一个X锁指定范围加锁,insert阻塞
MySQL 5.7.x - RCX:行锁X,行锁X,行锁,无间隙锁;行锁指定范围加锁,更新、insert阻塞
MySQL 8.0.x - RCX,REC_NOT_GAP:行级排他锁X,REC_NOT_GAP:行级排他锁X,REC_NOT_GAP:行锁,无间隙锁;X,REC_NOT_GAP:行锁指定范围加锁,不阻塞insert

从上面表中我们可以总结出以下结论(基于RR、RC两种事务隔离级别):

  • 无论哪个版本的MySQL,查询条件为主键、唯一索引、普通索引的情况下,为行锁;
  • 查询条件为普通索引时,事务隔离级别为RR时,MySQL还会添加一个间隙锁,条件内的插入、更新会被阻塞;
  • 事务隔离级别为RR时,查询条件无索引,为表锁;
  • 事务隔离级别为RC时,查询条件无索引,为行锁;
  • 查询条件为范围时,有索引的情况下,除MySQL 8.0.x RC场景下不阻塞插入操作,其他场景均阻塞指定范围更新、插入操作;

通过上面的结论,我们可以看出,并不是简单的说“有索引就是行锁,无索引就是表锁”,因为在事务隔离级别为RC时,无索引,同样表现(被优化)为行锁。

至于,根据范围条件(大于、小于、不等于、between、like等)查询、查询无结果等情况,大家可根据上述实验方法进行自行验证。

本文为大家提供了实验方法,并针对常见的场景给出了结论,希望能够帮到你,也希望大家能够点赞、转发、收藏,以备不时之需。

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

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

相关文章

MES系统选型攻略,优秀MES系统应具备哪些性质

在众多MES系统中,企业怎样才能找到最适合自己的产品?那么,一套高质量的MES系统,究竟有什么特点?随着全球经济一体化的发展,中美两国之间的贸易战争日趋白热化,中国作为一个生产大国,…

行为型模式 - 迭代器模式iterator

模式的定义与特点 迭代器模式(iterator Pattern),为的提是可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个…

ffmpeg源码编译vs2013版本

完整版安装ffmpeg 一、安装choco 1.Set-ExecutionPolicy AllSigned 2.Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.N…

链队基本操作(笔记版)

本节主要针对链栈的基本操作进行解析。 # coding:___utf-8___ # author:Guoxuan Sun time:2023/1/12 #链栈的基本操作 #链栈的创建与顺序栈的区别就是每个结点都有一个指针域 #同时链栈也是有两个指针front和rear #链栈中的front指针指向的结点是第一个结点,不是空…

关于MySQL中的存储引擎

存储引擎:(了解内容) 1、什么是存储引擎,有什么用? 存储引擎是mysql中特有的一个术语,其他数据库中没有。 存储引擎就是一个表存储/组织数据的方式。不同的存储引擎,表存储数据的方式不同。 目前…

基于Androidstudio的宠物交友app

需求信息: 客户端: 1:登录注册:用户可以通过自己的信息进行账号的注册 2:宠物信息:列表显示发布的宠物想,可以通过条件对宠物信息进行筛选,以及沟通意向点亮 3:宠物圈&am…

java调用python文件的几种方式

java调用python的契机来自于一个项目需要用到算法,但是算法工程师们写的python,于是就有了java后端调用python脚本的需求,中间遇到了许多问题,特此记录整理了一次。1、java调用python的方式有哪几种1.1 方法一:jpython…

选择排序.

一、简单选择排序 void select_sort(int a[], int len){ //len为数组长度for (int i 0; i < len-1; i){//n个数需要比较n-1趟int min i;//记录最小值的位置for (int j i1; j < len-1; j){if (a[j] < a[min]) min j;//更新最小值的位置} if (min ! i) swap(a[i], …

vue3 项目篇商场 之 初始化项目

目录vue3 项目篇商场 之 初始化项目1&#xff1a;安装 rem 适配src 同级目录下创建 postcss.config.jsmain.ts2 :使用字体图标加字体图标 &#xff08; Symbol 这个选项&#xff09;public / index.html使用效果3 sass4&#xff1a;vant3引入 按需引入 ( 非 vite )4-1 babel.co…

2022年,开源社最亮的星

开源社成立于 2014 年&#xff0c;是由志愿贡献于开源事业的个人成员&#xff0c;依 “贡献、共识、共治” 原则所组成&#xff0c;始终维持厂商中立、公益、非营利的特点&#xff0c;是最早以 “开源治理、国际接轨、社区发展、开源项目” 为使命的开源社区联合体。本次年度评…

常用的专业数据恢复软件有哪些?恢复数据就看这10个!

互联网时代&#xff0c;我们都习惯使用电脑来进行办公&#xff0c;电脑里面都保存着我们很多数据。数据的日积月累&#xff0c;会导致电脑的运行速度减缓&#xff0c;为此我们都会定期对电脑进行清理。 如果在清理过程中&#xff0c;不小心误删或者格式化了数据&#xff0c;有…

漏洞挖掘之信息收集

简介 对单一指定目标网站进行黑盒测试&#xff0c;最重要的就是信息收集&#xff0c;因为网站管理员肯定会在用户经常访问的主网站进行经常维护&#xff0c;而那些子域名&#xff0c;没有什么人访问的&#xff0c;可能就会忘记维护&#xff0c;挖洞的突破点大都在于子域名上&am…

Android/Linux 子系统Graphics图形栈入门普法介绍

Android/Linux 子系统Graphics图形栈入门普法介绍 写在最前面 由于工作原因&#xff0c;最近在公司做了一个关于Android/Linux 子系统Graphics图形栈入门相关知识的培训介绍&#xff0c;个人感觉对于想要了解入门这块的朋友还是有一定帮助的。由于博客不能直接放入ppt&#xff…

Spring AOP源码:代理的创建过程

前言 上篇文章讲解了AOP解析工作&#xff0c;将配置文件解析并封装成beanDefinition&#xff0c;由于配置文件中有5个通知方法&#xff0c;before、after、around、after-returning、after-throwing&#xff0c;这里会将其解析成5个advisor通知类。 <?xml version"1…

opencv——图像阈值设定及常见的滤波操作

1、图像阈值ret,dstcv2.threshold(src,thresh,maxval,type)src:输入图&#xff0c;只能是单通道图&#xff0c;也就是灰度图。thresh:阈值。maxval:当像素超过了阈值&#xff0c;所赋予的值。type:二值化操作的类型&#xff0c;包括binary&#xff0c;binary_iny,trunc,tozero,…

Python编程 闭包

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;b网络豆的主页​​​​​​ 目录 前言 一.闭包 1.什么是闭包 前景引入&#xff1a; 2.闭包的定义需要满足以下…

Redis序列化、乱码问题

Redis序列化问题 每当初学者学习Redis&#xff0c;并且使用SpringBoot整合Redis的时候&#xff0c;总会看到别人使用这种东西—配置类&#xff0c;然后自己又看不懂&#xff0c;oh&#xff0c;fuck&#xff01;&#xff01; 这是为什么&#xff0c;为什么要有这个配置类&…

Pytorch优化器全总结(四)常用优化器性能对比 含代码

目录 写在前面 一、优化器介绍 1.SGDMomentum 2.Adagrad 3.Adadelta 4.RMSprop 5.Adam 6.Adamax 7.AdaW 8.L-BFGS 二、优化器对比 优化器系列文章列表 Pytorch优化器全总结&#xff08;一&#xff09;SGD、ASGD、Rprop、Adagrad Pytorch优化器全总结&#xff08;二…

设计模式学习(七):Factory Method工厂模式

目录 一、什么是Factory Method模式 二、Factory Method示例代码 2.1 类之间的关系 2.2 Product类 2.3 Factory类 2.4 IDCard类 2.5 IDCardFactory类 2.6 用于测试的Main类 2.7 运行结果 三、拓展思路的要点 3.1 框架与具体加工 3.2 使用模式与开发人员之间的沟通 …

(12)go-micro微服务JWT跨域认证

文章目录一 JWT介绍二 JWT优缺点三 JWT使用1. 导包和数据定义2.生成JWT3.解析JWT4.完整代码四 最后一 JWT介绍 JWT 英文名是 Json Web Token &#xff0c;是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范&#xff0c;经常用在跨域身份验证。 JWT 以 JS…