RR隔离级别下还有幻读吗

news2024/9/20 14:41:10

文章目录

    • 1 背景
    • 2 验证
      • 2.1 验证准备
        • 2.1.1 环境信息
        • 2.1.2 数据准备
      • 2.2 当前读场景验证
        • 2.2.1 前言
        • 2.2.2 场景验证
        • 2.2.3 小结
      • 2.3.快照读场景验证
        • 2.3.1 前言
        • 2.3.2 场景验证
        • 2.3.3 小结
    • 3 分析
    • 4 总结

1 背景

InnoDB默认的事务隔离级别是REPEATABLE-READ,它为了解决该隔离级别并发情况下的幻读问题,使用了LBCC(基于锁的并发控制)和MVCC(多版本的并发控制)两种方案。其中LBCC解决的是当前读情况下的幻读问题,MVCC解决的是快照读情况下的幻读问题,那既然如此,该隔离级别下是否仍然还存在幻读的问题呢?幻读问题到底有没有完全解决呢?基于这样的疑问,下面我们来进行验证下吧。

2 验证

2.1 验证准备

2.1.1 环境信息
MySQL版本:5.6.36
存储引擎:InnoDB
隔离级别:REPEATABLE-READ
2.1.2 数据准备

为了进行验证,在测试库建立了一张测试使用的用户信息表,并且插入了3条初始数据。

CREATE TABLE `user_info` (
	`id` BIGINT ( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键id',
	`name` VARCHAR ( 32 ) NOT NULL DEFAULT '' COMMENT '姓名',
	`gender` VARCHAR ( 32 ) NOT NULL DEFAULT '' COMMENT '性别',
	`email` VARCHAR ( 32 ) NOT NULL DEFAULT '' COMMENT '邮箱',
PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '用户信息表';

INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (1, 'Curry', '男', 'curry@163.com');
INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (2, 'Wade', '男', 'wade@163.com');
INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (3, 'James', '男', 'james@163.com');
commit;

2.2 当前读场景验证

首先我们先来看看当前读的场景下会不会出现幻读的问题。

2.2.1 前言
  • 什么是当前读

当前读(Locking Read)也称为锁定读,读取的是数据当前的最新版本,而且读取到这个数据之后会对这个数据加锁,防止别的事务进行更改,即通过next-key锁(唯一索引next-key锁会退化为记录锁)来解决当前读中的脏读,幻读,不可重复读问题,也就是LBCC的方式。在进行写操作的时候也需要进行“当前读”,读取数据记录的最新版本。当前读包含以下SQL类型:select … lock in share mode 、select … for update、update 、delete 、insert。

  • 什么是临键锁

我们将数据库中存储的每一行数据称为记录。如上图中1、5、9、12分别代表id为当前数的记录。对于键值在条件范围内但不存在的记录,叫做间隙(GAP)。则上图中的(-∞,1)、(1,5)…(12,+∞)为数据库中存在的间隙。而(-∞,1]、(1,5]…(12,+∞)我们称之为临键,即左开右闭的集合。当我们对上面的记录和间隙共同加锁时,添加的便是临键锁。

2.2.2 场景验证

触发当前读的方式有很多种,这里仅使用select lock in share mode这种方式来进行当前读幻读问题验证。

  • 场景:
  1. 开启事务1
  2. 在事务1中通过select lock in share mode进行当前读查询用户信息
  3. 开启事务2
  4. 在事务2中插入一条新数据
  5. 提交事务2
  6. 在事务1中再次查询用户信息
  7. 提交事务1
mysql> START TRANSACTION; -- 1.开启事务1
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_info lock in share mode; -- 2.读锁方式查询用户信息
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
+----+-------+--------+---------------+
3 rows in set (0.00 sec)
mysql> START TRANSACTION; -- 3.开启事务2
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (4, 'White', '男', 'white@163.com'); -- 4.在事务2中插入一条新数据

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction  -- 因锁等待插入未成功,最终等待超时,事务回滚终止





  • 场景验证结果

可以看到在事务1中开始事务执行了当前读后,事务2在进行插入新数据时进入了锁等待,最后发生了锁等待超时,导致事务终止回滚。插入数据因锁的原因是不会成功的,因此事务1第二次查询时也不会查询到新记录,所以此场景下不会产生幻读的问题。

2.2.3 小结

由场景验证结果可以看到,由于临键锁的存在,会阻塞其他事务对加锁间隙的数据插入,所以当前读场景下通过LBCC是可以完全解决幻读的问题。

2.3.快照读场景验证

那接下来我们再看看快照读场景下是怎么样的。

2.3.1 前言
  • 什么是快照读

由于当前读是通过LBCC基于锁的方式来进行并发控制,是悲观锁的实现,同时也会因为锁的原因,造成锁冲突的概率变大,也会导致性能的下降,因此基于提高并发性能的考虑,引入了快照读,快照读顾名思义即读取的是数据的快照版本,快照读的实现是基于MVCC多版本并发控制,它在很多情况下,避免了加锁操作,降低了性能开销。

2.3.2 场景验证
  • 场景一
  1. 开启事务1
  2. 在事务1中查询用户信息
  3. 开启事务2
  4. 在事务2中插入一条新数据
  5. 提交事务2
  6. 在事务1中再次查询用户信息
  7. 提交事务1
mysql> START TRANSACTION; -- 1.开启事务1
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_info; -- 2.在事务1中查询用户信息
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
+----+-------+--------+---------------+
3 rows in set (0.00 sec)
mysql> select * from user_info; 6.在事务1中再次查询用户信息
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
+----+-------+--------+---------------+
3 rows in set (0.00 sec)
mysql> commit; -- 7.提交事务1
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION; -- 3.开启事务2
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (4, 'White', '男', 'white@163.com'); -- 4.在事务2中插入一条新数据
Query OK, 1 row affected (0.00 sec)

mysql> commit; -- 5.提交事务2
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user_info;
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
|  4 | White | 男     | white@163.com |
+----+-------+--------+---------------+
4 rows in set (0.00 sec)



  • 场景验证结果

从场景一来看RR级别下是可以避免幻读的问题,在意料之中。
那如果我们在事务1中两次查询之间进行了当前读更新操作呢,那会不会出现幻读的问题呢,那接下来我们来看一看场景二。

  • 场景二
  1. 开启事务1
  2. 在事务1中查询用户信息
  3. 开启事务2
  4. 在事务2中插入一条新数据
  5. 提交事务2
  6. 在事务1中将ID为1的数据的用户姓名修改为Iversen
  7. 在事务1中再次查询用户信息
  8. 提交事务1
mysql> START TRANSACTION; -- 1. 开启事务1
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_info; -- 2. 在事务1中查询用户信息
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
+----+-------+--------+---------------+
3 rows in set (0.00 sec)
mysql> update user_info set name = 'Iversen' where id = 1; -- 在事务1中将ID为1的数据的用户姓名修改为Iversen
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from user_info; -- 7. 在事务1中再次查询用户信息
+----+---------+--------+---------------+
| id | name    | gender | email         |
+----+---------+--------+---------------+
|  1 | Iversen | 男     | curry@163.com |
|  2 | Wade    | 男     | wade@163.com  |
|  3 | James   | 男     | james@163.com |
+----+---------+--------+---------------+
3 rows in set (0.00 sec)
mysql> commit; -- 8. 提交事务1
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION; -- 3.开启事务2
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (4, 'White', '男', 'white@163.com'); -- 4.在事务2中插入一条新数据
Query OK, 1 row affected (0.00 sec)

mysql> commit; -- 5.提交事务2
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user_info;
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
|  4 | White | 男     | white@163.com |
+----+-------+--------+---------------+
4 rows in set (0.00 sec)






  • 场景验证结果

从场景二来看RR级别下仍然是可以避免幻读的问题,那是不是就可以确定RR级别下已经完全解决了幻读的问题呢。那我们再换一种更新方式来看看吧。

  • 场景三
  1. 开启事务1
  2. 在事务1中查询用户信息
  3. 开启事务2
  4. 在事务2中插入一条新数据
  5. 提交事务2
  6. 在事务1中将所有用户的邮箱信息的后缀更换为@gmail.com
  7. 在事务1中再次查询用户信息
  8. 提交事务1
mysql> START TRANSACTION; -- 1. 开启事务1
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_info; -- 2. 在事务1中查询用户信息
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
+----+-------+--------+---------------+
3 rows in set (0.00 sec)
mysql> update user_info set email = REPLACE(email, '@163.com', '@gmail.com'); -- 6. 在事务1中将所有用户的邮箱信息的后缀更换为@gmail.com
Query OK, 4 rows affected (0.00 sec)
Rows matched: 4  Changed: 4  Warnings: 0
mysql> select * from user_info;  -- 7. 在事务1中再次查询用户信息
+----+-------+--------+-----------------+
| id | name  | gender | email           |
+----+-------+--------+-------------- --+
|  1 | Curry | 男     | curry@gmail.com |
|  2 | Wade  | 男     | wade@gmail.com  |
|  3 | James | 男     | james@gmail.com |
|  4 | White | 男     | white@gmail.com |
+----+-------+--------+-----------------+
4 rows in set (0.00 sec)
mysql> commit;  -- 8. 提交事务1
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION; -- 3.开启事务2
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO `user_info` (`id`, `name`, `gender`, `email`) VALUES (4, 'White', '男', 'white@163.com'); -- 4.在事务2中插入一条新数据
Query OK, 1 row affected (0.00 sec)

mysql> commit; -- 5.提交事务2
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user_info;
+----+-------+--------+---------------+
| id | name  | gender | email         |
+----+-------+--------+---------------+
|  1 | Curry | 男     | curry@163.com |
|  2 | Wade  | 男     | wade@163.com  |
|  3 | James | 男     | james@163.com |
|  4 | White | 男     | white@163.com |
+----+-------+--------+---------------+
4 rows in set (0.00 sec)







  • 场景验证结果

事务1在进行更新之后再次查询读取到了事务2新插入到数据,出现了幻读。

2.3.3 小结

看来RR级别的确没有完全解决幻读问题,那为什么还会存在幻读的问题呢,为什么更新的方式不同,会出现不同的结果,什么情况下还会出现幻读问题呢。带着这样的疑问,我们来探索下~

3 分析

从验证结果来看,当前读是可以完全避免幻读的问题,而对于快照读如果在两次读取之间进行了当前读,在某些情况下是会触发幻读的问题。那么下面我们可以从当前读的实现(MVCC)的角度来分析幻读问题的产生原因。

我们应该知道MVCC实现原理主要是依赖记录中的3个隐式字段,undo日志,Read View来实现的,好,那么我们基于产生幻读的场景结合MVCC的实现原理来一步步进行分析。

1.产生幻读的场景

  1. 初始三条测试数据
  2. 开启事务1
  3. 在事务1中查询用户信息
  4. 开启事务2
  5. 在事务2中插入一条新数据
  6. 提交事务2
  7. 在事务1中将所有用户的邮箱信息更换为@gmail.com
  8. 在事务1中再次查询用户信息
  9. 提交事务1

1.1 执行步骤0:初始三条测试数据

在初始化三条数据后三条初始数据分别会有三个隐式字段值,

DB_TRX_ID(事务id),DB_ROLL_PTR(回滚指针),DB_ROW_ID(隐式主键)。

如下:因为是新插入的数据,回滚指针字段的值均为NULL。

+----+-------+--------+--------------+-----------+-----------+---------+
| id | name  | gender | email        | DB_TRX_ID |DB_ROLL_PTR|DB_ROW_ID|
+----+-------+--------+--------------+-----------+-----------+---------+
|  1 | Curry | 男     | curry@163.com|    2334   |    NULL   |    1    |
|  2 | Wade  | 男     | wade@163.com |    2334   |    NULL   |    2    |
|  3 | James | 男     | james@163.com|    2334   |    NULL   |    3    |
+----+-------+--------+--------------+-----------+-----------+---------+

1.2 执行步骤1:开启事务1

在开启事务1后会为事务1分配一个唯一的事务id

mysql> SELECT trx_id,trx_state,trx_started  FROM INFORMATION_SCHEMA.INNODB_TRX;
+-----------------+-----------+---------------------+
| trx_id          | trx_state | trx_started         |
+-----------------+-----------+---------------------+
| 2335            | RUNNING   | 2024-07-28 21:31:52 |
+-----------------+-----------+---------------------+
1 row in set (0.00 sec)

1.3 执行步骤2:在事务1中查询用户信息

因为是开启事务后的首次查询,所以此时会生成一张Read Veaw读视图,此时trx_list,up_limit_id,low_limit_id的值分别为:

trx_list:因为是测试验证,无其他并发事务参与,所以活跃事务列表中只有当前的事务id[2335];

up_limit_id:活跃事务列表中最小的事务id,即当前事务id:2335;

low_limit_id:下一个未开始的事务id,即当前事务id+1为:2336;

此时查询数据会使用当前生成的Read View并依据可见性算法来进行查询,因为数据库中数据的事务id均小于up_limit_id所以对当前事务均是可见的,所以三条初始数据会全部被查询出来。

注: 可见性算法

  1. 首先比较 DB_TRX_ID < up_limit_id , 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
  2. 接下来判断 DB_TRX_ID >= low_limit_id , 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
  3. 判断 DB_TRX_ID 是否在活跃事务之中,trx_list.contains (DB_TRX_ID),如果在,则代表Read View 生成时刻,这个事务仍处于活跃中,还没有commit,如果DB_TRX_ID=creator_trx_id,则说明是当前事务自己产生的数据,是可见的,如果不等于,则为其他事务修改的数据,当前事务也是看不见的;如果不在活跃事务之中,则说明,你这个事务在Read View生成之前就已经commit了,修改的结果,当前事务是能够看见的。

1.4 执行步骤3:开启事务2

在开启事务2后会为事务2分配一个唯一的事务id。

事务id的分配是递增的,因此事务2的事务id一定是大于事务1。

mysql> SELECT trx_id,trx_state,trx_started  FROM INFORMATION_SCHEMA.INNODB_TRX;
+-----------------+-----------+---------------------+
| trx_id          | trx_state | trx_started         |
+-----------------+-----------+---------------------+
| 2336            | RUNNING   | 2024-07-28 21:35:52 |
+-----------------+-----------+---------------------+
1 row in set (0.00 sec)

1.5 执行步骤4:在事务2中插入一条新数据

此时会产生一条新插入数据的insert undolog日志

1.6 执行步骤5:提交事务2

由于事务提交插入的数据会实际生效,insert undolog日志会被删除,此时表的数据情况如下:

+----+-------+--------+--------------+-----------+-----------+---------+
| id | name  | gender | email        | DB_TRX_ID |DB_ROLL_PTR|DB_ROW_ID|
+----+-------+--------+--------------+-----------+-----------+---------+
|  1 | Curry | 男     | curry@163.com|    2334   |    NULL   |    1    |
|  2 | Wade  | 男     | wade@163.com |    2334   |    NULL   |    2    |
|  3 | James | 男     | james@163.com|    2334   |    NULL   |    3    |
|  4 | White | 男     | white@163.com|    2336   |    NULL   |    4    |
+----+-------+--------+--------------+-----------+-----------+---------+

1.7 执行步骤6:在事务1中将所有用户的邮箱信息的后缀更换为@gmail.com

因为是更新操作,所以是当前读会将所有的符合条件的数据都读取出来,进行更新。
更新后的数据表中的数据如下:

+----+-------+--------+----------------+-----------+-----------+---------+
| id | name  | gender | email         | DB_TRX_ID |DB_ROLL_PTR|DB_ROW_ID|
+----+-------+--------+----------------+-----------+-----------+---------+
|  1 | Curry | 男     |curry@gmail.com |    2335   |  0x123825 |    1    |
|  2 | Wade  | 男     |wade@gmail.com  |    2335   |  0x153125 |    2    |
|  3 | James | 男     |james@gmail.com |    2335   |  0x115725 |    3    |
|  4 | White | 男     |white@gmail.com |    2335   |  0x163225 |    4    |
+----+-------+--------+----------------+-----------+-----------+---------+

undolog情况如下:

1.8 执行步骤7:在事务1中再次查询用户信息

  • 当前是RR的隔离级别,所以此时使用的Read View读视图仍然是首次查询生成的读视图。

  • 依据Read View的可见性算法分析,分别对四条数据的undolog版本链从尾部至头部逐一进行可见性判断是否可见进行追溯,会看到四条数据的尾部版本就可对当前事务可见。所以四条数据是会在此次查询中全部被查询得到。

由此可以推断产生幻读的原因啦,因为事务1中的更新操作,对事务2中的新插入的数据也进行了更新,更新后新数据的undolog日志中会追加此次更新的回滚日志,并指向新插入数据的undolog记录,此时根据MVCC的可见性算法,事务2新插入的数据此时对于事务1也变成了可见的,因此产生了幻读的问题。

  • 那同样是更新场景二为什么没有产生幻读的问题呢?

在场景二中,更新语句更新的是事务1第一次查询可见的数据,而对事务2中新插入的数据没有进行任何操作,新插入数据的版本链中是不存在当前事务产生的版本数据的,因此新插入的数据对与事务1仍然不可见,所以没有产生幻读问题。

4 总结

  1. 当前读可以通过锁机制完全避免幻读问题,快照读如果中间对其他事务已提交的插入或更新的数据进行了更新,则会出现幻读的问题。
  2. 如何进行避免呢?
  • 采用串行化的隔离级别(不建议);
  • 开发时注意考虑这种产生幻读的场景,尽量通过调整代码逻辑规避幻读问题的发生(建议);
  • 若不能通过调整代码逻辑规避,可以考虑采用当前读的方式避免(建议);

关于作者 孔德志 采货侠JAVA开发工程师

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

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

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

相关文章

数学 小朋友按如图所示的规则练习数数,1大拇指,2食指,3中指,4无名指,5小指,6无名指,…,一直数到2024的时候,是哪一个手指?

1、先上一张图&#xff1a; 如图&#xff0c;一个小朋友按如图所示的规则练习数数&#xff0c;1大拇指&#xff0c;2食指&#xff0c;3中指&#xff0c;4无名指&#xff0c;5小指&#xff0c;6无名指&#xff0c;…&#xff0c;一直数到2024时&#xff0c;对应的指头是&#xf…

UE的Gameplay框架(三) —— Level和World

这篇文章关注于Level和World&#xff0c;对其在Gameplay框架中的作用及使用做简单讲解。 文章目录 Level和World的作用关卡流送关卡流送体积蓝图加载 参考资料 Level和World的作用 在UE里&#xff0c;Level 可以看作是 Actors 的容器&#xff0c;其有一个LevelScriptActor用于处…

【Netty】实战:基于WebSocket的聊天室

​ 本文将使用Netty快速实现一个聊天室应用&#xff0c;该应用基于WebSocket协议&#xff0c;用户可以在浏览器内聊天。 实现过程很简单&#xff0c;就几步。 一、处理Http请求 package cn.md.netty.websocket.groupchat;import io.netty.channel.*; import io.netty.handle…

yolov8目标检测pyside6可视化图形界面+检测源码ui文件——用于计数统计

项目结构 YOLOv8模型加载&#xff1a;加载预训练的YOLOv8模型。PySide6 GUI&#xff1a;设计图形用户界面&#xff0c;用于显示检测结果和控制选项。摄像头/视频输入&#xff1a;从摄像头或视频文件读取图像帧。目标检测&#xff1a;使用YOLOv8模型对输入图像进行实时目标检测…

Explorer++:轻量级高效文件管理器!!

项目简介 Explorer 是一款专为Windows操作系统设计的轻量级且高效的文件管理器。作为Windows资源管理器的强大替代方案&#xff0c;它提供了丰富的特性和优化的用户体验&#xff0c;使得文件管理和组织变得更加便捷高效。无论是专业用户还是普通用户&#xff0c;都能从中受益&a…

Leetcode面试经典150题-92.反转链表II

解法都在代码里&#xff0c;不懂就留言或者私信 比反转链表I略微难一点点 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, Li…

0903作业+思维导图

一、作业 1》多态的实现 1、代码 #include <iostream>using namespace std; //父类 class Person { public:string name;int age; public:Person(){}Person(string n,int a):name(n),age(a){}~Person(){}//纯虚函数virtual void show() 0; }; //子类1 class Stu:publ…

游戏开发者必看:Perforce龙智即将携手亮相2024 Unreal Fest上海站,打造游戏开发版本控制新生态

2024年9月5- 6日&#xff08;周四-周五&#xff09;&#xff0c;Unreal Fest Shanghai 2024将在上海宝华万豪酒店隆重举行&#xff01;作为游戏行业备受瞩目的盛会之一&#xff0c;Unreal Fest每年都会吸引来自世界各地的技术专家和行业领导者齐聚一堂&#xff0c;分享最新的技…

『功能项目』销毁怪物蛋的Shaders消融特效【17】

本章项目成果展示 我们打开上一篇16主角的信息显示的项目&#xff0c; 本章要做的事情是在怪物消亡时生成一个销毁特效 首先创建一个Unlit Shader 重命名为Dissolve 双击进入脚本后编写脚本&#xff1a; Shader "Unlit/Dissolve"{Properties{//物体基础材质纹理[Hea…

Apache Kafka UI :一款功能丰富且美观的 Kafka 开源管理平台!!【送源码】

Apache Kafka UI 是一个免费的开源 Web UI&#xff0c;用于监控和管理 Apache Kafka 集群&#xff0c;可方便地查看 Kafka Brokers、Topics、消息、Consumer 等情况&#xff0c;支持多集群管理、性能监控、访问控制等功能。 1 特征 多集群管理&#xff1a; 在一个地方监控和管理…

软考高级网络规划设计师含金量高吗?

网络规划设计师含金量很高&#xff01;这个证书是计算机技术与软件领域的高级专业证书。 拿到这个证书的人&#xff0c;那在网络系统建设方面可是全能选手&#xff0c;从需求分析到规划设计&#xff0c;再到部署实施、评测运维&#xff0c;统统都能搞定。 他们得对网络技术应…

Flutter 小技巧之 Row/Column 即将支持 Flex.spacing

事实上这是一个相当久远的话题&#xff0c;如果对于前因后果不管兴趣&#xff0c;直接看最后就行。 这个需求最早提及应该是 2018 年初在 #16957 被人提起&#xff0c;因为在 Flutter 上 Wrap 有 runSpacing 和 spacing 用于配置垂直和水平间距&#xff0c;而为什么 Colum 和 …

单细胞组学大模型(3)--- scGPT,有非常详细的学习文档和应用说明,且有多种训练数据权重!

–https://doi.org/10.1038/s41592-024-02201-0 代码来源&#xff1a;https://github.com/bowang-lab/scGPT 学习参考&#xff1a;https://scgpt.readthedocs.io/en/latest/introduction.html scGPT: Towards Building a Foundation Model for Single-Cell Multi-omics Usin…

2024.9.3

#include <iostream> #include <cstring> using namespace std;class Stack { private:int len;int count 0;int *stack; public:Stack():len(10) //无参构造{stack new int[len];stack[len] {0};}Stack(int len):len(len) //有参构造{stac…

一文搞懂微服务架构之限流

前置知识 限流是通过限制住流量大小来保护系统&#xff0c;能够解决异常突发流量打崩系统的问题。例如常见的某个攻击者在攻击你维护的系统&#xff0c;那么限流就是极大程度上保护住你的系统。 算法 限流算法也可以像负载均衡算法那样&#xff0c;划分成静态算法和动态算法…

【软件测试专栏】测试分类篇

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;软件测试专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 测试分类篇 关键词&#xff1a;测试方法的概念、测试类型、先后顺…

花生壳二级域名的绑定测试

1、花生壳客户端的登录 左下角显示的就是你的外部IP。 2、新建映射 点击新建映射&#xff0c;就会进入一个管理页面&#xff0c;如下图&#xff1a; 3、可以通过域名访问网站了 就可以二级域名直接访问&#xff0c;在192.168.1.11:8080 上建立的Tomcat网站了&#xff0c;非常…

uniapp写的一个年月日时分秒时间选择功能

代码: <template><view><picker mode"multiSelector" :value"multiIndex" :range"multiRange" change"onMultiChange"><view class"picker">当前选择&#xff1a;{{ formattedDateTime }}</vie…

各业务领域相关方案

电商 电商系统的简单架构 电商系统的简单架构_电商交易平台 系统架构-CSDN博客 订单系统 美团团购订单系统优化记 vivo 全球商城&#xff1a;订单中心架构设计与实践 库存系统 电商库存系统的防超卖和高并发扣减方案 vivo全球商城&#xff1a;库存系统架构设计与实践 资金…

开篇_____何谓安卓机型“工程固件” 与其他固件的区别 作用

此系列博文将分析安卓系列机型与一些车机 wifi板子等工程固件的一些常识。从早期安卓1.0起始到目前的安卓15&#xff0c;一些厂家发布新机型的常规流程都是从工程机到量产的过程。在其中就需要调试各种参数以便后续的量产参数可以固定到最佳&#xff0c;工程固件由此诞生。 后…