【InnoDB数据存储结构】第2章节:InnoDB行格式

目录结构

之前整篇文章太长,阅读体验不好,将其拆分为几个子篇章。

本篇章讲解 InnoDB 行格式。

InnoDB 行格式

InnoDB 一行记录是如何存储的?

这个问题是本文的重点,也是面试中经常问到的问题,所以就引出了下文的 InnoDB 行格式内容。

InnoDB 指定行格式语法

先看下指定行格式的简单语法

#创建表指定行格式
create table table_name(列信息) row_format = 行格式名称#修改表行格式
alter table table_name row_format = 行格式名称

Compact 行格式

Compact 行数据存储结构

在 MySQL5.1 版本中,默认设置为 Compact 行格式。一条完整的记录其实可以被分为记录的额外信息和记录的真实数据两大部分。

**举例:**采用 Compact 行格式创建一张表 page_demo

create table page_demo (c1 int,c2 int,c3 varchar(10000),primary key(c1)
) CAHRSET=ascii ROW_FORMAT=Compact
  • 字符集:ascii
  • 行格式:Compact

表中的每一行记录的行格式如下所示:

这些记录头信息中的各个属性如下(主要 6 个属性):

其中有两个预留位置没有使用,我们简化之后的行格式如下所示:

向库中插入 4 条数据:

insert into page_demo 
values
(1, 100, 'song'),
(2, 200, 'tong'),
(3, 300, 'zhan'),
(4, 400, 'lisi');

这 4 条记录的行格式如下所示:

上图各方块属性:

  • 蓝色方块为记录头信息
  • 绿色方块为 数据信息,这里为了展示方便,写的是 10 进制 ,实际上底层存储的是 2 进制

变长字段长度列表

创建一张表 record_test_table

create table record_test_table(col1 varchar(8),col2 varchar(8) not null,col3 vhar(8),col4 varchar(8)
) charset=ascii row_format=Compact

向表里面插入两个数据:

insert into record_test_table(col1, col2, col3, col4)
values
('zhangsan', 'lisi', 'wangwu', 'songhk'),
('tong', 'chen', NULL, NULL);

MySQL 支持一些变成的数据类型,比如 varchar(M)、varbinary(M)、text、blob 等类型,这些数据类型修饰的列被称为 变成字段。边长字段中存储多少个字节的数据是不固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存储起来。

在 Compact 行格式中,把所有变长字段的真实数据占用的字节长度存放在记录的开头部位,从而形成一个变长字段长度列表。

注意:

这里存储的变长字段的长度的顺序和表字段创建时的真实顺序是翻过来的,比如:两个 varchar 字段在表中的顺序是 a(10),b(15)。那么在变长字段长度列表中的顺序是 15,10,翻过来存储的。

根据上面插入的两条真实数据,分析一下各个变长字段真实数据占用的字节长度:

NULL 值列表

Compact 行格式会把可以为 NULL 值的列统一管理起来,存在一个标记为 NULL 值列表中。

如果表中没有可以为 NULL 值的列,那这个 NULL 值列表也就不存在。

为什么要定义 NULL 值列表?

之所以要存储 NULL值,是因为数据都是需要对齐的。如果没有标注出 NULL 值的位置,就有可能在查询数据的时候出现混乱 的情况。如果 使用一个特殊符号代替 NULL 值放到对应的位置,虽然可以达到效果,但是大量为 NULL 值的列会严重 浪费空间,所以直接在 行数据的头部开辟出一块空间 专门用来存储该行数据有哪些是非空数据,哪些是空数据, 格式如下:

  • 二进制位为 1:代表列值为 NULL
  • 二进制为为 0:代表列值不为 NULL

这样我们回答一个问题,MySQL 中的 NULL 值是怎么存储的?

答:NULL 值是由 NULL 列表记录的,用二进制逆序表示每一行记录中的每一列是否为 NULL 值,0 代表不为 NULL,1 代表为 NULL 值。

假设有一张表有 4 个字段,col1、col2、col3、col4

插入一条记录:‘a’, NULL, NULL, ‘dd’

那 NULL 值列表用二进制表示为:0 1 1 0,转化为 10 进制就是 06。

记录头信息(5 字节)

delete_mask(删除标记)

这个属性标记着当前记录是否被删除,占用 1 个 bit:

  • 值为 0:代表记录没有被删除
  • 值为 1:代表记录被删除了

被删除的记录为什么还在页中存储?

这些被删除的记录之所以不立即从磁盘的页中移除,是因为移除他们之后,紧跟着他们的记录需要 重新排列,特别是对 聚簇索引的叶子节点,假设移除的是主键值为 1的记录, 那整个聚簇索引的叶子节点会因为这一条记录的删除全部重新排序,导致性能消耗。所以只是将这些删除的记录做一个删除标记和正常记录做个区分,实际上这些被删除的记录会组成一个 垃圾链表,它们所占用的空间被称为 可重用空间,之后再插入的数据,可能会把这些被删除记录占用的空间直接 覆盖掉(复用)

min_rec_mask(最小记录标记)

B+Tree 的每层非叶子节点中的最小记录都会添加该标记,并且 min_rec_mask的值为 1。

我们自己插入的数据记录的 min_rec_mask的值为 0,所以它们都不是 B+Tree 的非叶子节点中的最小记录(这句话自己理解就行,不要纠结)。

record_type(记录类型)

这个属性代表当前记录的类型,一共有 4 种类型的记录:

  • 0:表示普通记录
  • 1:表示 B+Tree 非叶子节点记录
  • 2:表示最小记录
  • 3:表示最大记录

从图中可以看出,我们自己插入的记录的 record_type的值为 0,最大最小记录的 record_type的值分别为 23

非叶子节点记录 record_type的值为 1的情况(索引的数据结构一文中讲述的内容):

heap_no(记录位置)

这个属性代表代表当前记录在当前页中的下标位置。

下标为 0、1 的两条记录分别为最大和最小记录,在上文【Infimum + Supremum(最大记录和最小记录)】中已经提到了,因为这两个记录不是我们插入的,所以有时候也称为 伪记录虚拟记录

n_owned(每组记录数)

页目录(有多个组)中每个组中最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned字段的值。

next_record(下一条记录的地址偏移量,非指针)

记录头中该属性非常重要,它表示从 当前记录的真实数据下一条记录的真实数据 之间的 地址偏移量

比如:第一条记录中的 next_record值为 32,意味着从第一条记录的真实数据的地址处向后找 32 个字节,便是下一条记录的真实数据。

**注意:**下一记录并不是按照我们插入顺序的下一条记录,而是按照主键值顺序排列的下一条记录。

InnoDB 底层规定 Infimum 记录(最小记录)的下一条记录就是当前页中主键值最小的记录,而当前页中主键值最大的记录指向的下一条记录就是 Supremum 记录(最大记录)

下图用箭头指向代替地址偏移量,来表示 next_record

演示:删除一条记录的操作

根据上图所示,假设删除上图第 2 条记录:

# 删除主键值为2的记录
delete from page_demo where c1 = 2;

删除之后,整个链表也会跟着变化,第一条记录的 next_record就会直接指向第 3 条记录,但是第 2 条记录并没有被真实删除,只是将 delete_mask值变成了 1。下图所示:

变化内容如下:

  • 第 2 条记录的 delete_mask变为 1

  • 第 2 条记录的 next_record变为 0,代表不再指向真实数据了

  • 最大记录的 n_owned的值从 5=> 4,因为当前组少了一条记录

    • 原本当期页算上最大最小记录,总共 6 条记录,分为两个组,最小记录为一个组
    • 四条真实记录和最大记录为一组,所以最大记录中的n_owned的值为 5
    • 现在第二组中删除了一条记录,所以n_owned的值从 5=> 4

演示:增加一条记录的操作

上述主键值为 2 的记录被删除后(变成了垃圾链表),但是存储空间并没有被收回,如果再次把这条记录插入表中,会发生什么?

insert into page_demo values(2, 200, 'tong');

如下图所示:

变化内容如下:

  • 新插入的数据,因为指定了主键值为 2,所以按照聚簇索引结构这条记录会按照顺序插入原来第 2 条记录的位置
  • 因为原来被删除的第 2 条记录并没有被真实删除,仍然占有空间,所以这次新插入的数据会复用原有的空间
  • 第 2 条记录的 delete_mask的值变为 0
  • 第 2 条记录的 next_record的值变为 32
  • 第 1 条记录的 next_record指向第 2 条记录,第 2 条记录的next_record指向第 3 条记录
  • 最大记录的 n_owned的值从 4 => 5

记录的真实数据

记录的真实数据,除了我们自定义的列的数据以外,还会有三个隐藏列:

实际上这几个列的真实名称是:

  • db_row_id
  • db_trx_id
  • db_roll_ptr

其中 row_id 字段的含义,如果一个表没有手动定义主键,则会选取一个 Unique 键(值唯一的列)作为主键,如果连 Unique 键都没有定义的话,则会为表默认添加一个名为 row_id 的隐藏列作为主键。所以 row_id 是在没有手动定义主键以及不存在 Unique 键的情况下才会存在。

transaction_id 和 roll_pointer 涉及到事务,后面学到再讲解。

举例:创建一张表 mytest

create table mytest(col1 varchar(10),col2 varchar(10),col3 char(10),col4 varchar(10)
)engine=innodb charset=latin1 row_format=compact

插入三条数据:

insert into mytest values
('a', 'bb', 'bb', 'ccc'),
('d', 'ee', 'ee', 'fff'),
('d', NULL, NULL, 'fff');

找到存储表文件 mytest.ibd 的位置,用 notepad++打开,

刚打开可能会乱码,可以安装一个解析插件(自行解决),解析为十进制的数据格式。

格式化之后,二进制文件如下,只需要看真实数据存储的二进制即可:

我们对照下插入的三行记录:

('a', 'bb', 'bb', 'ccc'),
('d', 'ee', 'ee', 'fff'),
('d', NULL, NULL, 'fff');

解析上面的二进制文件,因为 col3 列是定长,不计入变长字段列表,下面解析第一行记录:

  • 【变长字段区域】:03 02 01 对照 col3 列 ccc 长度为 03,col2 列 bb 长度为 02,col1 列 a 长度为 01
  • 【NULL 值列表区域】:00 代表都是非空的字段,实际上是按照字段的逆序组成的二进制 0 0 0 0 ,转化为十进制就是 00
  • 【记录头信息】:00 00 10 00 2c 对照记录头信息(5 个字节),其中 2c对应 next_record,偏移 2c 个字节到下一条记录的位置
  • 【row_id】:00 00 00 2b 68 00 对照隐藏主键(6 字节),当没有手动指定主键,且没有 Unique 建时,InnoDB 会默认创建 row_id
  • 【transaction_id】:00 00 00 00 06 05 对照事务id(6 字节)
  • 【roll_pointer】:80 00 00 00 32 01 10 对照回滚指针(7 字节)
  • 【真实记录】:61 对照第一行记录 col1 的值 a
  • 【真实记录】:62 62 对照第一行记录 col2 的值 bb
  • 【真实记录】:62 62 20 20 20 20 20 20 20 20 对照第一行记录 col3 的值 bb,后面的 20 作为一个空值,因为 col3 字段是定长 char(10)10 个字节,而一个字符 b 只占 1 个字节,所以用 8 个 20 填充 8 个空字节位
  • 【真实记录】:63 63 63 对照第一行记录 col3 的值 ccc

根据上面的分析我们大致知道了,一行完整数据底层二进制文件的存储格式是怎样的。

第二行记录和第一行内容想通,根据行格式自行推断。

我们重点来看第三行记录是如何存储的?

  • 【变长字段列表】:03 01 对照字段 col4 和 col1,col3 和 col2 为 NULL 值不记录
  • 【NULL 值列表】:06 对照四个字段是否为 NULL 值的二进制 0 1 1 0,转化为十进制就是 06
  • 记录头信息】:00 00 20 ff 98 对照记录头信息(5 个字节),其中 98 是 next_record
  • 【row_id】:00 00 00 2b 68 02 对照 row_id(6 字节)
  • 【transaction_id】:00 00 00 00 06 07 对照事务 id(6 字节)
  • 【roll_pointer】:80 00 00 00 32 01 10 对照回滚指针(7 字节)
  • 【真实记录】:64 对照第三行记录的 col1 字段的值 d
  • 【真实记录】:66 66 66 对照第三行记录的 col4 字段的值 fff,因为 col2 和 col3 都是 NULL值所以没有记录

到这我们就分析完了,应该对底层二进制文件的存储有了一定的认知吧。

Dynamic 和 Compressed 行格式

字段的长度限制

在了解行溢出之前我们要先了解下一个字段的最大长度。

回顾一下,char 和 varchar 的区别

一个 varchar 类型的字段,最大容量为 65535 个字节。

我们创建一张表,验证一下是否真的可以指定为 65535 个字节?

首先我们查看一下 MySQL8.0.26 默认字符集

说明默认字符集采用 utf8mb4

再查看一下 MySQL5.7.34 默认字符集

说明默认字符集采用 utf8

这里我们统一采用 8.0.26 版本去实践验证。

首先我们明确一点,不同字符集字符和字节的对等关系:

  • **utf8 字符集:**1 个字符等于 3 个字节
  • **utf8mb4 字符集:**1 个字符等于 4 个字节
  • **ascii 字符集:**1 个字符等于 1个字节

第一步我们采用默认字符集创建一张表 varchar_size_demo,行格式统一采用 Compact

  • **utf8mb4 字符集:**1 个字符等于 4 个字节
create table varchar_size_demo  (c varchar(65535)
) row_format=COMPACT;

报错提示,字段长度最大不能超过 16383,因为 8.0.26 版本默认字符集为utf8mb4,也就是一个字符等于 4 个字节,但是16383 * 4 = 6553265532 还差了 3 个字节到 65535,按理论我们应该用 65535 除以 4 等于 16383.75,但是字段长度不能带小数,那我们字舍五入将字段长度改为 16384再试下:

显示还是不能超过 16383,那我们将字段长度改为 16383,再次尝试:

创建成功!!!

思考一下,那 3 个字节跑哪去了?

16383 * 4 = 65532

65535 - 65532 = 3

原因是:每一行记录的头信息中都会默认有 变长字段长度列表(2 字节)NULL 值列表 (1 字节),所以每一行记录都会默认空出 3 个字节,用户存储变长字段和 NULL 值的标识。

上述我们采用的是 8.0.26 默认的字符集 utf8mb4,下面我们验证一下指定字符集采用 utf8。

  • **utf8 字符集:**1 个字符等于 3 个字节

根据上述所知要预留 3 个字节,65535 - 3 = 6553265532 / 3 = 21844

也就是说字符集 utf8 字段的最大长度限制为 21844

那我们假设长度为 21845,创建表 varchar_size_demo1

-- utf8字符集,1个字符等于3个字节
create table varchar_size_demo1 (c varchar(21845)
)charset=utf8;

创建报错,显示字段过长。

那我们指定字段长度为 21884再次创建:

-- utf8字符集,1个字符等于3个字节
CREATE TABLE varchar_size_demo1 (c VARCHAR(21844)
)CHARSET=utf8;

创建成功,那就说明我们上述的逻辑是对的。

再指定字符集为 ASCII创建表 varchar_size_demo2

  • **ascii 字符集:**1 个字符等于 1个字节

预留 3 个自己,那字段长度最大为 65532,如果指定长度为 65533看下效果:

-- ascii字符集,1个字符等于1个字节
create table varchar_size_demo2 (c varchar(65533)
)charset=ascii;

创建失败,将字段长度改为 65532再次创建:

-- ascii字符集,1个字符等于1个字节
create table varchar_size_demo2 (c varchar(65532)
)charset=ascii;

OK 创建成功,撒花!!!

行溢出

根据上文所说的单个字段的最大长度根据不同的字符集,会有不同的限制,8.0.26 默认采用 utf8mb4字符集

  • **utf8mb4 字符集:**1 个字符等于 4 个字节

varchar 类型最大为 65535 个字节,预留 3 个字节,一个 varchar 字段最大的容量为 65533 字节,而 InnoDB 的一个数据页的大小为 16KB,16 * 1024 = 16384个字节,一个 varchar 的容量远远大于一个数据页的大小,这样就可能出现一个页存不下一行记录,这种现象成为 行溢出

在 Compact 和 Redundant 行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据(768 个前缀字节),把剩余的数据分散存储在其他的页中,这叫作 分页存储

然后记录的真实数据处用 20 个字节存储指向这些分散页的地址(这 20 个字节中还包括存储了分散在各个页中的真实数据占用的字节数),从而可以找打剩余数据所在的页,这称为页的扩展,如下图所示:

Dynamic 和 Compressed 行格式

在 MySQL8.0 中,默认的行格式为 Dynamic,Dynamic 和 Compressed 这两种行格式和 Compact 行格式类似,只不过在处理行溢出数据时方式不同,区别如下:

  • Compact 和 Redundant 两种行格式会在记录的真实数据处存储一部分数据(768 个前缀字节)。
  • Dynamic 和 Compressed 两种行格式对于存放在 Blob 中的数据采用了完全的行溢出存储方式。如下图所示,如果一行记录数据溢出了,在数据页中只存储 20 个字节的指针地址(存储真实数据的溢出页的地址),实际的数据都存储在 Off Page(溢出页)中。

Compressed 和 Dynamic 是什么区别呢?

Compressed 是在 Dynamic 的基础上优化了一层,存储在其中的行数据会以 zlib 算法进行压缩存储,因此对于 Blob、Text、Varchar 这类大长度类型的数据能够进行非常有效的存储。

Redundant 行格式

Redundant 是 MySQL5.0 版本之前 InnoDB 的行记录存储格式,MySQL 5.0 支持 Redundant 是为了兼容之前版本的页格式。

比如直接修改表的行格式为 Redundant:

alter table record_test_table row_rormat=Redundant;

Redundant 行格式存储格式如下所示:

对比 Compact 行格式主要有两大处不同:

  • Compact 是 变长字段长度 列表,Redundant 是 字段长度偏移 列表
  • Compact 有 NULL 值列表,Redundant 没有 NULL 值列表

字段长度偏移列表

为什么说 Redundant 行格式会有冗余说法?

因为 Redundant 行格式的字段长度便宜列表会将该行记录中所有列(包括隐藏列)的长度信息都按照逆序存储起来。

偏移 两字,意味着 Redundant 行格式计算列值的长度的方式不想 Compact 行格式那么直观,它是采用两个相邻数值的差值来计算各个列值的长度。

比如第一行记录的字段长度偏移列表(逆序)是:

  • 2B 25 1F 1B 13 0C 06

因为它是按照逆序排列的,所以按照顺序排列就是:

  • 06 0C 13 1B 1F 25 2B

可以看出有三个隐藏列和四个字段列。

按照两个相邻数值的差值来计算各个字段列值的长度的如下表所示:

列名十六进制字节数十进制字节数
row_id0x066
transaction_id0x0C - 0x066
roll_pointer0x13 - 0x0C7
col10x1B - 0x138
col20x1F - 0x1B4
col30x25 - 0x1F6
col40x2B - 0x256

记录头信息(record header)

不同于 Compact 行格式,Redundant 行格式中的记录头信息固定占用 6 个字节(48 位),每位的含义如下:

与 Compact 行格式的记录头信息对比来看,有两处不同:

  • Redundant 行格式多了 n_field1byte_offs_flag这两个属性
  • Redundant 行格式没有 record_type这个属性

其中两个属性的含义:

  • n_field代表一行中列的数量,占用 10 位,所以 MySQL5.0 之前的版本最多只能包含 1023 个列。

  • 1byte_offs_flags该属性定义了字段长度偏移列表占用 1 个字节,还是 2 个字节。

    • 当值为 1 时,表示占用 1 个字节;
    • 当值为 2 时,表示占用 2 个字节。

小结

到这我们就把 MySQL 的行格式了解的差不多了,当然更底层的知识点我们也用不到,也不会去用它,了解到这个层面其实在工作中也已经足够用了。

本文内容总结借鉴于康师傅的 MySQL 视频课:https://www.bilibili.com/video/BV1iq4y1u7vj


在这里插入图片描述

一起学编程,让生活更随和!

如果你觉得是个同道中人,欢迎关注博主gzh:【随和的皮蛋桑】。

专注于Java基础、进阶、面试以及计算机基础知识分享🐳。偶尔认知思考、日常水文🐌。

在这里插入图片描述


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

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

相关文章

【flink番外篇】9、Flink Table API 支持的操作示例(14)- 时态表的join(java版本)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点,并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分,比如术语、架构、编程模型、编程指南、基本的…

Unity 基于UDP实现本地时间与网络时间校验 防客户端修改日期作弊

新建一个Unity GameObject 挂上NTPComponent脚本 时间校验 源码 using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEngine.Networking; using System.Text; using System.Net.Sockets; using System.Net; using Sys…

c# OpenCvSharp Cv2.Threshold()和Cv2.AdaptiveThreshold参数说明

一、 Cv2.Threshold()二值化的函数参数说明 Cv2.Threshold()是一个用于图像二值化的函数。具体来说,它会将图像中的每一个像素的灰度值与一个阈值进行比较,大于该阈值的像素会被赋值为最大灰度值(即 255),小于该阈值的像素会被赋值为最小灰度…

Apollo感知模块 :传感器| 目标监测| 障碍物识别 | 模型管理

🎬 鸽芷咕:个人主页 🔥 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! ⛳️ 粉丝福利活动 ✅参与方式:通过连接报名观看课程,即可免费获取精美周边 ⛳️活动链接&#xf…

用c语言以升序顺序显示15个单词,从键盘上输入15个单词,将其以升序顺序显示出来。(排序方法不限,单词不得雷同,而且单词须是正确的单词)

用c语言以升序顺序显示15个单词,从键盘上输入15个单词,将其以升序顺序显示出来。(排序方法不限,单词不得雷同,而且单词须是正确的单词) 以下是一个使用C语言实现的示例程序,可以从键盘上输入15个单词,并将它…

简易机器学习笔记(八)关于经典的图像分类问题-常见经典神经网络LeNet

前言 图像分类是根据图像的语义信息对不同类别图像进行区分,是计算机视觉的核心,是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础。图像分类在许多领域都有着广泛的应用,如:安防领域的人脸识别和…

视频号频繁显眼!是资本的运作?还是互联网新风口到来?

视频号这个平台出现了,特别是在最近存在感越来越强,而且已经有些人开始在视频号当中购物了,这也就意味着,视频号电商出现了,腾讯也开始搞电商了。 很多人可能对视频号做电商这个事情呢,抱有一定的迟疑态度&…

【算法】数论---约数

约数里面的一个重要性质&#xff1a;一个数的约数都是成对存在的(以sqrt(x)为分界线) 一、求一个数的所有约数---试除法 int x; cin>>x; int yue[10000]{0},idx0; for(int i1;i<x/i;i) {if(x%i0){yue[idx]i;cout<<i<<" ";} }for(int iidx-1;i&…

深度学习:大规模模型分布式训练框架DeepSpeed

深度学习&#xff1a;大规模模型分布式训练框架DeepSpeed DeepSpeed简介DeepSpeed核心特点DeepSpeed如何工作&#xff1f;DeepSpeed如何使用&#xff1f;参考文献 DeepSpeed简介 随着机器学习模型变得越来越复杂和庞大&#xff0c;训练这些模型所需的计算资源也在不断增加。特别…

九州金榜|家庭教育小妙招如何培养孩子学习习惯

做小学老师的时候&#xff0c;很多家长都问过我同一个问题&#xff0c;孩子成绩差&#xff0c;如何提高孩子的成绩&#xff1f; 好像成绩是我们的家长判断孩子是否优秀的唯一标准&#xff0c;一切都是围绕着成绩说话&#xff0c;考好了表扬、鼓励&#xff0c;考不好就会被批评…

【UE5.1】给森林添加天气效果

在上一篇博客&#xff08;【UE5.1】程序化生成Nanite植被&#xff09;基础上给森林添加天气交互效果&#xff0c;角色和雪地、水坑的交互效果。 目录 效果 步骤 一、准备工作 二、添加超动态天空 2.1 修改时间 2.2 昼夜交替 三、添加超动态天气 3.1 改变天气 3.2 …

uniCloud 云数据库(新建表、增、删、改、查)

新建表结构描述文件 todo 为自定义的表名 表结构描述文件的默认后缀为 .schema.json 设置表的操作权限 uniCloud-aliyun/database/todo.schema.json 默认的操作权限都是 false "permission": {"read": false,"create": false,"update&quo…

html中的form表单以及相关控件input、文本域、下拉select等等的详细解释 ,点赞加关注持续更新~

文章目录 表单创建表单forminput 标签input标签的value属性设置input标签格式单选框多选框上传文件下拉菜单文本域设置文本域格式label 标签按钮 表单 作用&#xff1a;收集用户信息。 使用场景&#xff1a; 登录页面注册页面搜索区域 创建表单form <form action".…

DataGear 4.7.0 发布,数据可视化分析平台

DataGear专业版 1.0.0 正式发布&#xff0c;欢迎试用&#xff01; http://datagear.tech/pro/ DataGear 4.7.0 发布&#xff0c;严重漏洞和BUG修复&#xff0c;具体更新内容如下&#xff1a; 新增&#xff1a;HTTP数据集新增【编码请求地址】支持&#xff0c;可用于解决请求…

希亦、觉飞、小吉三款婴儿洗衣机大比拼!全方位对比测评

由于年龄幼小的婴儿的皮肤都非常的幼嫩&#xff0c;因此婴儿衣物材质的类型大部分都是采用为纯棉&#xff0c;并且婴儿的衣物不能够与大人的衣物一起进行混洗&#xff0c;容易把细菌感染到宝宝的衣物上&#xff0c;因此很多家庭为了保证宝宝衣服的有效清洁&#xff0c;避免交叉…

TXT文本删除第一行文本变成空要如何解决呢

首先大家一起来看下这个TXT文本里面有多行内容&#xff0c;想把开头第一行批量删除不要掉。 1..如果是一两个本可以手动删除也很方便哦&#xff0c;如果文本量比较大如几十几、几百个文本大家一直都选用《首助编辑高手》工具去批量操作哦。批量操作可以大大提高工作效率。接来看…

AI实景无人直播创业项目:开启自动直播新时代,一部手机即可实现财富增长

在当今社会&#xff0c;直播已经成为了人们日常生活中不可或缺的一部分。无论是商家推广产品、明星互动粉丝还是普通人分享生活&#xff0c;直播已经渗透到了各行各业。然而&#xff0c;传统直播方式存在着一些不足之处&#xff0c;如需现场主持人操作、高昂的费用等。近年来&a…

Minitab 各版本安装指南

Minitab下载链接 https://pan.baidu.com/s/1PLqocknkoRGGI9lbV3e45A?pwd0531 1.鼠标右击【Minitab 21(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;选择【解压到 Minitab 21(64bit)】。 2.打开解压后的文件夹&#xff0c;鼠标右击【setu…

MacOS - 苹果电脑程序还能正常启动,但图标消失不见了~

问题描述 网上有一些解决方案说是 killall Finder 命令&#xff0c;重置 Docker 等等&#xff0c;但是发现还是不行&#xff0c;于是必杀技…… 解决方案 方案一、删除该 App&#xff0c;重装即可方案二、如果懒得重装&#xff0c;可以在 Finder 中找到对应的应用程序&#xf…

如何把照片多余的地方擦除?一键消除图片上的瑕疵,简单又轻松,太方便了

在数字繁荣的时代&#xff0c;图片处理已然成为我们生活乐章中不可或缺的一部分&#xff0c;就如画师手中的画笔般灵动&#xff0c;摄影师镜头下的世界般多彩。然而&#xff0c;在捕捉或获取这些美丽的图片时&#xff0c;可能会不小心闯入一些不速之客&#xff0c;给画面带来瑕…