推广 热搜: 行业  机械  设备    系统  教师    参数  经纪  蒸汽 

Hive 核心原理(hive-3.1.2)

   日期:2024-11-10     移动:http://sjzytwl.xhstdz.com/mobile/quote/711.html
  • 由Facebook开源用于解决海量结构化日志的数据统计
  • 基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射成一张表,并且提供类SQL的查询功能
  • Hive仅仅是一个工具,本身不存储数据只提供一种管理方式,同时也不涉及分布式概念,就是个软件而已
  • Hive本质就是MapReduce,将类SQL(HQL)转换成MapReduce程序

1.1.1 HQL转换MR流程

在这里插入图片描述

Hive 核心原理(hive-3.1.2)

解释

  1. Hive处理的数据存储在HDFS
  2. Hive分析数据底层默认实现是MapReduce[可以修改为spark]
 
  1. 执行的程序运行在yarn上
  2. Hive相当于Hadoop的一个客户端
  3. Hive不是分布式

1.2.1 优点

  1. 操作接口采用类SQL语法,提供快速开发的能力(简单、易上手
  2. 避免去写MR,减少开发人员学习成本
  3. Hive的延迟比较高(因为MR延迟高,因此Hive常用于数据分析
  4. Hive优势在于处理大数据(数据量少真不如MySQL等
  5. Hive支持用户自定义函数,可以根据自己的去求实现自己的函数

1.2.2 缺点

  1. Hive的HQL表达能力有限(MR决定的
    1. 迭代算法无法表达
    2. 不适用于数据挖掘
  2. Hive的效率比较低
    1. Hive自动生成的MR作业,通常情况不够智能
    2. Hive调优难(只能对资源,SQL层面调优,无法深入作业底层逻辑

在这里插入图片描述

  1. 用户接口:Client

    CLI(hive shell)、JDBC/ODBC(java访问hive)、WEBUI(浏览器访问hive

  2. 元数据:metastore

    包括表名、表所属的数据库、表的拥有者、列/分区字段、表的类型、表数据所在的目录等(自带个derby数据库,推荐配置到MySQL

  3. 底层存储:HDFS

    使用HDFS进行存储,使用MapReduce计算

  4. 驱动器:Driver

    1. 解析器(SQL Parser:将SQL字符串转换成抽象语法树AST,并对语法树进行语法分析,如:SQL语法、表/字符是否存在
    2. 编译期(Physical Plan:将AST编译生成逻辑执行计划
    3. 优化器(Query Optimizer:对逻辑执行计划进行优化
    4. 执行器(Execution:把逻辑执行计算转换成运行的物理计划,即MR/Spark 在这里插入图片描述

Hive通过给用户提供的一系列交互接口,接受到用户编写的SQL,使用自己的Driver结合metaStore,将SQL指令翻译成MapReduce提交到Hadoop中执行,将执行结果输出到用户交互接口。

Hive除了提供类似SQL语法外和传统数据库没有任何相似之处,Hive是站在数据仓库出发点而设计的。

1.4.1 数据存储位置

Hive是建立在Hadoop之上,所有的Hive数据都是存储在HDFS上;传统数据库将数据保存在本地文件系统中;因此Hive能够处理更大更多的数据

1.4.2 数据更新

Hive是针对数据仓库应用设计,因此数据一次写入多次读出,即Hive中不建议对数据进行改写操作,所有数据都是在加载的时候确定好;对于数据库通常需要进行频繁的增删查改

1.4.3 索引

Hive在加载数据过程不会对数据进行任何处理,因为数据量庞大建立索引并不划算,因此Hive访问数据中满足特定值需要暴力扫描真个数据,因此访问延迟高。由于MapReduce的引入,Hive可以并行访问数据,即便没有索引也可用于大数据量的访问;传统数据库通常针对一个或多个列建立索引,因此在访问数据是延迟低效率高,即Hive不适合实时数据分析

1.4.4 执行

Hive 的执行引擎为MR/Spark,传统数据库都有自己的执行引擎

1.4.5 可拓展性

由于Hadoop的高拓展性,因此Hive也具备很强的拓展性;传统数据库的拓展会受到一定的限制

1.4.6 数据规模

Hive可以利用MapReduce进行大规模数据的并行计算;传统数据库支持的数据规模较小

将元数据配置到MySQL中需要初始化,初始化命令(其余步骤可自行百度

 
  1. 启动hive

     
  2. 查看数据库

     

    hive自带一个default数据库,默认也是进这个数据库

  3. 切换数据库

     
  4. 创建表

     

    和MySQL语法基本一致,只是Hive的数据类型和Java类似

  5. 查看表结构

     
  6. 插入数据(不要用,不要用,不要用

     

    谁用谁知道

  7. 查询数据

     
  8. 退出hive

     
  9. 执行hdfs shell

     
  10. 执行linux shell

     
 

插入一条数据84秒,显然不现实…为此Hive插入数据将采用最暴力最直接的方式,只需要将数据文件放到hdfs制定的路径即可。但也不是什么数据都可以,需要在创建表时指定分隔符。

 

准备数据

 

2.3.1 hive 插入数据一

加载本地数据到hive

 

评价:方便、推荐使用

2.3.2 hive 插入数据二

hive管理的数据是放在hdfs,可以再配置文件指定存储路径,可以进入指定路径查看,也可以通过看到该表的存储路径 在这里插入图片描述 在这里插入图片描述

  • hive在hdfs存储的根路径是在/…/warehouse/下
  • 一个数据库对应一个文件夹,命名方式为数据库名.db(默认数据库除外
  • 每张表也对应一个文件夹,命名方式为表名
  • 数据文件直接放在表对应的文件夹下,因此通过load方式其实底层调用的是
  • 默认数据库下的表直接放在warehouse下命名方式不变

基于上述规律可以有第二种插入方式,直接通过hadoop的shell将文件put到指定的hdfs路径下即可

 
 

总结:也可以用,但是必须知道表在hdfs上的路径,所以这种方式路就走窄了呀

看过hive数据库、表、数据在hdfs上的存储结构后,尝试再次加载一次数据,这次通过load加载hdfs上的数据

 

load方式加载hdfs文件不需要加local(很显然,这时候再次查看hdfs信息,此时原数据将会被删除(其实就是hadoop fs -mv在这里插入图片描述

对于多次加载相同数据文件情况,hive会将数据文件重命名后上传到hdfs指定路径,重命名格式:原文件名_copy_n.txt;和windows下同名文件处理类似。

2.4.1 基本数据类型

Hive数据类型Java数据类型长度tinyintbyte1bytesmalintshort2byteintint4bytebigintlong8bytebooleanbooleantrue/falsefloatfloat单精度doubledouble双精度stringString字符串timestampbigary

常用的基本数据类型有int、bigint、double、string且不区分大小写;boolean一般使用0/1代替以减少存储量;string使用最多,一般都是处理日志,理论上可以存储2G数据(一行)。

2.4.2 集合数据类型

数据类型描述语法实例struct结构体,复杂无关系数据struct<k1:v,k2:v>map字典,键值对元组集合map<k,v>array数组,同一类型集合array<v>

struct和map区别在于map只能存储一组一组的k-v对,且一个map中的k不能相同,struct可以存储很对组相同key不同value的数据结构,即map中每组数据的key都不相同,struct中每组数据对应位置的key都是一样的;集合数据类型允许任意层次的嵌套。

2.4.3 类型转换

Hive支持类似java的数据类型转换

隐式转换

  • tinyint -> smalint -> int -> bigint -> float -> double
  • string类型只有是数字才可以转换
  • boolean不能转换成任意类型

强制类型转换

 

好吧,看不出什么效果

2.4.4 测试集合数据类型

需要存储如下格式数据(json

 

将一条数据转换成一行数据,去除没必要的数据,在一条数据中,字段之间用’,‘隔开,集合元素之间用’_‘隔开,map的kv用’:'隔开,因此可以转换成如下格式

 

针对上述数据创建如下表

 

解释

  • 设置字段分割符
  • 设置集合元素分割符
  • 设置map键值对分隔符
  • 设置行分隔符,默认

导入数据测试

 

3.1.1 创建数据库

1.方式一

创建一个数据库,默认存储在hdfs中/user/hive/warehouse/*.db

 

若数据库已经存在会报,推荐使用方法二

2.方式二

避免创建的数据库已经存在的错误,使用写法

 
3.方式三

指定数据库在hdfs的存储位置

 

3.1.2 查询数据库

1.显示数据库

显示数据库

 

过滤显示查询的数据库

 
2.查看数据库详情

显示数据库信息

 

显示数据库详细信息

 

为自定义属性,可作为注释使用

3.1.3 修改数据库

已经创建的数据库其信息都是不可以修改的,包括数据库名和数据库所在的目录位置等,这里修改数据库指的是修改数据库的的键值对

 

3.1.4 删除数据库

1.删除空数据库
 
2.强制删除数据库

对于非空数据库,上述命令无法删除

 

因此可以使用进行强制删除

 

3.2.1 创建表

标准语句

 

3.2.2 修改表

1.表的重命名
 
2.添加列
 
3.修改列

包括修改列名,列属性

 

替换列

 

3.2.3 删除表

 

3.2.4 内/外部表

1.内部表

又称管理表(MANAGED_TABLE,因为对应的表叫外部表(EXTERNAL_TABLE)所以喜欢叫它外部表,创建表默认是内部表,删除表时元数据和hdfs内数据均会被删除

2.外部表

与内部表对立,删除外部表时仅删除元数据hdfs内的数据不会被删除,再次创建同名表数据会"恢复",即hive并非认为其完全拥有此表,创建外部表命令如下

 
3.内外部表转换

内部表 -> 外部表

 

外部表 -> 内部表

 
4.应用场景

在实际生产环境中,涉及到共享数据一定要使用外部表,防止误操作导致的数据丢失,对于个人使用,数据分析的中间表可以使用内部表,方便管理。

3.2.5 分区表

假设Hive的数仓存储了一年的数据,现在需要查找出某一天的数据,当使用where语句时,Hive会对该表(文件夹)下所有数据进行全表扫描,从而导致查询效率低;因此引入分区表概念来优化查询(有谓词下推的意思

1.创建分区表
 

在创建表语法后加

2.导入数据

需要注意的是当创建的是分区表时加载数据需要添加上分区信息否则会保存(因为hive不知道要把数据put到哪个文件夹

 

可以发现分区字段会自动加到表的数据中(原始数据文件不会被添加,hive自己做的处理,在读取数据的时候自动添加分区字段数据,添加多个分区后

 

在这里插入图片描述

因此分区的本质就是分文件夹,文件夹以分区名命名,不同分区的数据被放在不同文件夹下,这样的好处是当执行where查询时,hive会直接去指定的分区文件夹下扫描数据,效率将大幅提高。

3.操作分区

添加分区(本质就是创建文件夹

 

删除分区(本质就是删除文件夹

 

创建二级分区

 

理论、操作都一样,多级分区上限没有限制(适可而止),结合上面知识,向分区表插入数据也可以直接put文件到指定路径

查看分区

 

4.1.1 向表中转载数据(load

1.标准语法
 
argsexplainload data加载数据local加载本地文件,不加默认加载hdfsinpath path加载数据文件的路径overwrite覆盖已有数据into table追加数据table_name具体的表名partition加载数据到指定分区
2.操作案例

见2.3 Hive常规操作

4.1.2 insert

insert插入分区表,真不推荐使用这个

 

基本插入模式,根据查询的结果插入数据

 
 

4.1.3 as select

 

4.1.4 export

只能搭配import使用,见下面的inport用法

4.2.1 insert

 
 

导出的数据会把导出路径下的所有文件进行覆盖,一定要写一个不存在的路径。但cat文件发现数据不友好,因此需要格式化导出数据

 
 

4.2.2 hive shell

不常用hive -e ‘sql’ > file 利用linux的重定向

4.2.3 export/import

先导出后导入

 

对于数据量小,学习一些操作命令可以将hive的运行模式设置成本地模式。对于小数据集可以明显的缩短时间,通过如下配置

 

关闭本地模式

 

和mysql语法一致,基本语法

 

5.2.1 全表和特定字段查询

1.全表查询
 
2.特定字段查询
 

总结

  1. HQL语法不区分大小写
  2. HQL可以写一行也可以写多行
  3. 关键字不能被缩写或换行
  4. 字句一般分行写
  5. 多使用缩进提高代码可读性

5.2.2 列起别名

基本语法,as可以省略

 

5.2.3 算术运算符

运算符描述A+BA和B相加A-BA减去BA*BA和B相乘A/BA除以BA%BA对B取余A&BA和B按位取与A|BA和B按位取或A^BA和B按位取异或~AA按位取反

5.2.4 limit 语句

基本语法

 

5.2.5 where 语句

和mysql语法一致,基本语法

 
1.比较运算符
操作符支持的数据类型描述A=B基本数据类型如果A等于B则返回TRUE,反之返回FALSEA!=B基本数据类型如果A不等于B,则返回TRUE,反之返回FALSEA<B基本数据类型如果A小于B,则返回TRUE,反之返回FALSEA<=B基本数据类型如果A小于等于B,则返回TRUE,反之返回FALSEA>B基本数据类型如果A大于B,则返回TRUE,反之返回FALSEA>=B基本数据类型如果A大于等于B,则返回TRUE,反之返回FALSEA [NOT] BETWEEN B AND C基本数据类型如果A的值大于等于B而且小于或等于C,则结果为TRUE,反之为FALSE。如果使用NOT关键字则可达到相反的效果。A IS NULL所有数据类型如果A等于NULL,则返回TRUE,反之返回FALSEA IS NOT NULL所有数据类型如果A不等于NULL,则返回TRUE,反之返回FALSEIN(数值1, 数值2)所有数据类型等于数值1、数值2,返回TRUEA [NOT] LIKE BSTRING 类型B是一个SQL下的简单正则表达式,如果A与其匹配的话,则返回TRUE;反之返回FALSE。B的表达式说明如下:‘x%‘表示A必须以字母’x’开头,’%x’表示A必须以字母’x’结尾,而’%x%‘表示A包含有字母’x’,可以位于开头,结尾或者字符串中间。如果使用NOT关键字则可达到相反的效果。
2.逻辑运算符
操作符含义AND逻辑并OR逻辑或NOT逻辑否

和mysql语法一致

5.4.1 group by

group by语句通常会和聚合函数(count、max、min、avg、sum)一起使用,按照一个或者多个列队结果进行分组,然后对每个组执行聚合操作。同样select的字段必须出现在group by后或者聚合函数里

 

5.4.2 having

用法和mysql一样

having和where区别

  1. where对表中的列发挥作用,having对查询结果的列发挥作用
  2. where后面不能接聚合函数,having后面可以接聚合函数
  3. having只能用在group by后面

5.5.1 等值连接

Hive只支持等值连接,不支持非等值连接,其用法可mysql语法一致

5.5.2 总结

内连接、左连接、右连接、满连接、多表连接、笛卡尔积都和mysql语法一致

hive的排序将会和mysql有很大的区别,为了更好的展现hive的排序,需要了解hive配置

 

设置reduce个数,默认-1会根据sql、数据量等动态规划reduce个数,通过设置大于0的数来规定reduce个数

5.6.1 全局排序

就是MapReduce的全局排序,在hive中体现为order by对应一个reduce,因为站在MapReduce角度全局排序必须输出一个文件因此必须只有一个reduce。

1.使用
 

同样默认升序(asc,可以按降序排序(desc

2.细节

当使用order by排序是会启动一个reduce,那么当手动设置reduce个数最终会启动几个reduce呢

 

提交一个job,启动一个reduce,发现无论设置多少个reduce个数全局排序就只会启动一个redcue。

3.补充

支持别名排序

 

支持多个字段排序

 

5.6.2 局部排序

对应MapReduce多个reduce,每个reduce局部有序,不一定保证全局有序,hive中通过sort by实现。

设置reduce个数为三

 

具体实现

 

结果如下 在这里插入图片描述

可以大致看出是分三个区(三个reduce即对应三个分区,当然可以将结果输出到文件中可以发现也是三个文件

 

但问题是在MapReduce中分区默认为HashPartitioner根据key的hash和reduce个数取余,那么hive是怎么实现分区呢?按照哪(几)个字段实现分区?从官方文档可以找到答案,按照上述没有指定分区的hql来说hive通过随机值的形式分区。 在这里插入图片描述

为什么?当我们没有指定字段分区时,若hive还按照字段进行分区万一导致数据倾斜问题该找谁呢?所以hive通过随机加载的形式尽可能减少数据倾斜的发生。

5.6.3 分区排序

distribute by:类似MapReduce的partition,通过指定某个字段进行分区(HashPartitioner)结合sort by从而达到分区内有序

按照部门id分区,分区内按照薪资升序排序

 

在这里插入图片描述

5.6.4 cluster by

当distribute by和sort by字段一致时可以使用cluster by代替,因此distribute by兼顾分区和排序两个功能,但是cluster by排序只支持降序排序,不能指定desc或asc一句话

 

那么有人就问了对一个字段分区又排序的意义可在?当我们数据有很多是,分区数少但该字段类型有很多种,因此就会有很多字段进入同一个分区,在对同一个分区按照该字段排序。

5.6.5 总结

语法总结group by对字段进行分区,将相同字段再进行分区,后面会接聚合操作distribute by仅对字段进行分区order by全局排序,只会起一个reducesort by局部排序,若设置reduce个数为1则和order by一样

请区别于分区表(我觉得这两张表的名字起得不太友好,学完发现分区表仅是数据分开存储,分桶表则对应MR的分区,将数据文件分开存储;注意分区表针对的是数据的存储路径,将一个个文件分文件夹存储,分桶表则是对一个个文件进行分区,一个分区对应一个文件即对一份数据文件进行了拆分。

分区提供一个数据隔离和优化查询的便利方式,但不是所有的数据集都能形成比较好的分区;分桶则是将数据集拆分成更容易管理的若干部分的另一种手段。

5.7.1 创建分桶表

 

分桶表使用clustered by(注意和分区排序区分开)且字段要是表中的字段(分区字段不能是表中字段)且分4个桶

查看表结构

 

在这里插入图片描述

加载数据到分桶表中,加载之前要确保这样hive会根据分桶表中的分桶数自动创建对应数量的reduce数 在这里插入图片描述 大致可以看出按照id的hash值取余 在这里插入图片描述

看了很多的文章发现创建分桶表步骤很多,首先开启分桶开关之类的,但发现在hive 3.1.2版本这个配置已经不存在了

 

通过阅读官方文档发现hive 2.x直接load数据即可,不需要通过创建中间表,再通过中间表走MR插入到分桶表的形式来加载数据了 在这里插入图片描述

5.7.2 分桶表应用

对于分桶表主要用于抽样查询中,在一个庞大的数据集中往往需要一个代表性查询结果而不是全部查询结果,因此hive通过抽样语句来实现抽样查询

 

col:为分桶字段

x:表示从哪个桶开始抽取

y:必须是分桶数的倍数或者因子,决定抽样的比例。如假设分桶数为4,当y=2时,抽取(4/2)=2个桶的数据;当y=4时,抽取(4/4)=1个桶的数据;当y=8时,抽取(4/8)=1/2个桶的数据

假设分桶数z:z/y>1,则最终取x,x+z/y+z/y*2…

 

即从第一个桶开始抽,抽取两个桶的数据,也就是抽取1,3两个桶中的数据

因此tablesample要求,因为抽取的最后一个为

 

若x > y会报

hive的函数和mysql一样分为系统内置函数和用户自定义函数,只是自定义函数和mysql将会有巨大差别

查看系统内置函数

 

查看内置函数用法

 

查看内置函数详细用法

 

下面列举常用的内置函数,主要介绍和mysql不同的部分(带*的)[一共216个]

functionexplanationround四舍五入ceil向上取整floor向下取整rand取0-1随机数lower转小写upper转大写length返回字符串长度concat字符串拼接concat_ws指定分隔符拼接collect_set合并字段*substr求子串trim前后去空格split字符串分割to_date字符串转日期year、month…从日期中提取年月日from_unixtime时间戳转日期*unix_timestamp日期转时间戳*case…when…条件函数if判断函数count求个数(聚合)sum求总和(聚合)min求最小值(聚合)max求最大值(聚合)avg求平均值(聚合)explode膨胀函数*lateral view拓展explode*over开窗函数

6.1.1 collect_set

通常搭配group by使用,将每个组的数据收集(collect)起来封装成一个集合(set)。 在这里插入图片描述

具体用法

 

按学院分组,打印出每个学员信息和每个学院都有哪些学生,使用函数将同学院学生封装集合

6.1.2 日期相关

日期转时间戳 unix_timestamp

 

时间戳转日期 from_unixtime

 

6.1.3 膨胀函数

1.explode

将一行数据转换成列,在hive中只能用于array和map数据类型

用于array数据类型

 

用于map数据类型

 

但是explode函数有很大的缺陷

  1. 不能关联原表的其他字段
  2. 不能分组、排序
  3. 不能进行UDTF(用户自定义表生成函数)嵌套
2.lateral view

lateral view是hive中提供给UDTF的结合,它可以解决UDTF不能添加额外select的问题,其原理是lateral view类似mysql视图,将UDTF(接收一行输入,输出多行,explode就是UDTF)结果保存为一个视图(虚拟表)并和输入行进行join来达连接其他字段的select的目的。

标准语法

 

udtf(expression):使用的UDTF函数。如explode()

tableAlias:表示虚拟表表名

columnAlias:给虚拟表取的字段名,多个字段用,隔间

解决explode()带来的不足

 

hive中的自定义函数根据输入输出行数分为三种

  • 用户定义函数(user-defined function)UDF
  • 用户定义聚集函数(user-defined aggregate function)UDAF
  • 用户定义表生成函数(user-defined table-generating)UDTF
函数类型描述UDF一行输入一行输出,如字符串类函数UDAF多行输入一行输出,如聚合函数UDTF一行输入多行输出,如膨胀函数

6.2.1 UDF

1.编程步骤
  1. 继承
  2. 实现evaluate函数,该函数支持重载
  3. hive中添加jar包
  4. 创建函数
  5. 使用函数
  6. 删除函数

注意:UDF必须有返回值,可以返回null,但不能不返回

2.具体实现

实现length()函数功能

1)添加依赖

 

2)创建类继承UDF

 

3)实现evaluate函数

 

4)hive中添加jar包

将程序达成jar包,放入hive/lib下,这个包下jar在hive启动时会自动加载,当然也可以手动加载jar包

 

5)创建函数

hive中自定义函数创建时可以分为临时函数和永久函数,对于临时函数仅在当前session,当前数据库起作用。

标准语法

 
 

6)使用函数

根据逻辑传入一个字符串返回一个int

 
 

7)删除函数

 

6.2.2 GenericUDF

UDF类已经过时,hive推荐使用GenericUDF,该类支持更多的数据类型且效率更高

 
  • 初始化,可以进行参数校验,对象实例化等等
  • 业务逻辑
  • 显示函数的帮助信息

那么里面些什么呢?打开一个的实现类

 

这个函数应该是韩国人写的,我们可以看他的返回的是啥

 

大致明白通过里的静态变量返回该函数需要返回的值,推测他的函数应该返回int类型,因此我们也可以直接返回

 

这个evaluate显然是优于UDF的,可是让我们函数接受任意多任意类型的值。

 
 

这里有坑,避免java的强制类型转换,返回值建议返回hadoop的数据类型

6.2.2 UDAF

UDAF区别于UDF是UDAF需要接受任意多个值后再进行计算后返回,因此UDAF的结构将比UDA复杂

1.编程步骤
  1. 函数类继承、计算类实现接口
  2. 实现接口的、、、、
    1. 初始化
    2. 接收传入参数,进行内部迭代
    3. 返回后的数据
    4. 接收返回结果,进行操作
    5. 返回最终的结果
2.具体实现

6.2.3 UDTF

实现split函数功能

1.编程步骤
  1. 创建一个类继承
  2. 实现、、方法

发现仅需要我们重写、两个方法,实际操作发现会报错,为什么?从源码可以看出

 

源码直接抛出异常,因此我们必须重写

2.代码实现

同理查看别人怎么实现的 在这里插入图片描述

纵观它的初始化逻辑很简单,为方法形参数据的长度,保存函数返回值字段名,保存函数返回值类型,因此我们可以写出自己的初始化,同时实现自己的逻辑

 

使用

 
本文地址:http://sjzytwl.xhstdz.com/quote/711.html    物流园资讯网 http://sjzytwl.xhstdz.com/ , 查看更多

特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


0相关评论
相关最新动态
推荐最新动态
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号