我只是想知道RDD
和DataFrame
之间有什么区别(Spark 2.0.0 DataFrame只是Dataset[Row]
的类型别名在Apache Spark?
你能把一个转换成另一个吗?
答案 0 :(得分:195)
通过Google搜索" DataFrame定义"来定义DataFrame
:
数据框是一个表,或者是二维数组结构 每列包含一个变量和每行的测量值 包含一个案例。
因此,DataFrame
由于其表格格式而具有其他元数据,这允许Spark对最终查询运行某些优化。
另一方面,RDD
只是 R 有条件的 D 属于 D ataset更多的是无法针对可以对其执行的操作进行优化的数据黑盒不受限制。
但是,您可以通过RDD
方法从DataFrame转到rdd
,您可以从RDD
转到DataFrame
(如果RDD是以表格格式)通过toDF
方法
一般,由于内置的查询优化,建议尽可能使用DataFrame
。
答案 1 :(得分:177)
首先,
DataFrame
是从SchemaRDD
演变而来的。
是..绝对有可能在Dataframe
和RDD
之间进行转换。
以下是一些示例代码段。
df.rdd
是RDD[Row]
以下是一些创建数据框的选项。
1)yourrddOffrow.toDF
转换为DataFrame
。
2)使用createDataFrame
的sql context
val df = spark.createDataFrame(rddOfRow, schema)
其中架构可以来自以下某些选项as described by nice SO post..
来自scala案例类和scala反射apiimport org.apache.spark.sql.catalyst.ScalaReflection val schema = ScalaReflection.schemaFor[YourScalacaseClass].dataType.asInstanceOf[StructType]
或使用
Encoders
Schema描述的import org.apache.spark.sql.Encoders val mySchema = Encoders.product[MyCaseClass].schema
也可以使用
StructType
创建StructField
val schema = new StructType() .add(StructField("id", StringType, true)) .add(StructField("col1", DoubleType, true)) .add(StructField("col2", DoubleType, true)) etc...
In fact there Are Now 3 Apache Spark APIs..
RDD
API:自
RDD
(弹性分布式数据集)API以来一直在Spark中 1.0发布。
RDD
API提供了许多转换方法,例如map
(),filter
()和reduce
()用于对数据执行计算。每 这些方法导致表示转换的新RDD
数据。但是,这些方法只是定义了操作 执行并且直到动作才执行转换 方法被调用。行动方法的例子是collect
()和saveAsObjectFile
()。
RDD示例:
rdd.filter(_.age > 21) // transformation
.map(_.last)// transformation
.saveAsObjectFile("under21.bin") // action
示例:按RDD属性过滤
rdd.filter(_.age > 21)
DataFrame
API Spark 1.3引入了一个新的
DataFrame
API作为项目的一部分 寻求改善性能的钨计划 Spark的可扩展性。DataFrame
API引入了a的概念 用于描述数据的模式,允许Spark管理模式和 仅以比使用更有效的方式在节点之间传递数据 Java序列化。
DataFrame
API与RDD
API完全不同,因为它 是一个用于构建Spark的Catalyst的关系查询计划的API 然后优化器可以执行。 API对于开发人员来说很自然 熟悉构建查询计划
示例SQL样式:
df.filter("age > 21");
限制: 由于代码是按名称引用数据属性,因此编译器无法捕获任何错误。如果属性名称不正确,则只有在创建查询计划时才会在运行时检测到错误。
DataFrame
API的另一个缺点是它非常以scala为中心,虽然它确实支持Java,但支持有限。
例如,从现有DataFrame
Java对象创建RDD
时,Spark的Catalyst优化器无法推断架构,并假设DataFrame中的任何对象都实现了scala.Product
接口。 Scala case class
解决了这个问题,因为它们实现了这个界面。
Dataset
API 在Spark 1.6中作为API预览发布的
Dataset
API旨在实现 提供两全其美的服务;熟悉的面向对象 编程风格和RDD
API的编译时类型安全性,但有 Catalyst查询优化器的性能优势。数据集 也使用与之相同的高效堆外存储机制DataFrame
API。在序列化数据时,
Dataset
API具有以下概念 编码器,它在JVM表示(对象)和。之间进行转换 Spark的内部二进制格式。 Spark有内置编码器 非常先进,因为它们生成与之交互的字节代码 堆外数据并提供对各个属性的按需访问 无需反序列化整个对象。 Spark还没有 提供用于实现自定义编码器的API,但这是计划的 为了将来的发布。此外,
Dataset
API旨在与之一致 Java和Scala。使用Java对象时,这很重要 它们完全符合bean标准。
示例Dataset
API SQL样式:
dataset.filter(_.age < 21);
进一步阅读... databricks article
答案 2 :(得分:118)
Apache Spark提供三种类型的API
以下是RDD,Dataframe和Dataset之间的API比较。
Spark提供的主要抽象是一个弹性分布式数据集(RDD),它是跨群集节点分区的元素的集合,可以并行操作。
分布式集合:
RDD使用MapReduce操作,该操作被广泛用于在群集上使用并行分布式算法处理和生成大型数据集。它允许用户使用一组高级操作符编写并行计算,而不必担心工作分配和容错。
不可变: RDD由分区的记录集合组成。分区是RDD中并行性的基本单元,每个分区是数据的一个逻辑分区,它是不可变的,并通过现有分区上的一些转换创建。可模糊性有助于实现计算的一致性。
容错: 在我们丢失一些RDD分区的情况下,我们可以在lineage中重放该分区上的转换以实现相同的计算,而不是跨多个节点进行数据复制。这个特性是RDD的最大好处,因为它节省了大量的努力进行数据管理和复制,从而实现更快的计算。
懒惰的评估:Spark中的所有转换都是惰性的,因为它们不会立即计算结果。相反,他们只记得应用于某些基础数据集的转换。仅当操作需要将结果返回到驱动程序时才会计算转换。
功能转换: RDD支持两种类型的操作:转换(从现有数据集创建新数据集)和操作(在数据集上运行计算后将值返回到驱动程序)。
数据处理格式:
它可以轻松高效地处理结构化数据和非结构化数据。
支持的编程语言:
RDD API可用于Java,Scala,Python和R。
没有内置优化引擎 使用结构化数据时,RDD无法利用Spark的高级优化器,包括催化剂优化器和Tungsten执行引擎。开发人员需要根据其属性优化每个RDD。
处理结构化数据 与Dataframe和数据集不同,RDD不会推断摄取数据的模式,并且需要用户指定它。
Spark在Spark 1.3版本中引入了Dataframes。 Dataframe克服了RDD所面临的主要挑战。
DataFrame是组织到命名列中的分布式数据集合。它在概念上等同于关系数据库或R / Python Dataframe中的表。与Dataframe一起,Spark还引入了催化剂优化器,它利用高级编程功能构建可扩展的查询优化器。
行对象的分布式集合: DataFrame是组织到命名列中的分布式数据集合。它在概念上等同于关系数据库中的表,但在更深层次的优化下。
数据处理: 处理结构化和非结构化数据格式(Avro,CSV,弹性搜索和Cassandra)和存储系统(HDFS,HIVE表,MySQL等)。它可以从所有这些不同的数据源读取和写入。
使用催化剂优化器优化 它为SQL查询和DataFrame API提供支持。 Dataframe使用催化剂树转换框架分为四个阶段,
1.Analyzing a logical plan to resolve references
2.Logical plan optimization
3.Physical planning
4.Code generation to compile parts of the query to Java bytecode.
Hive兼容性: 使用Spark SQL,您可以在现有的Hive仓库上运行未修改的Hive查询。它重用了Hive前端和MetaStore,使您可以完全兼容现有的Hive数据,查询和UDF。
<强>钨:强> Tungsten提供物理执行后端,用于显式管理内存并动态生成字节码以进行表达式评估。
支持的编程语言:
Dataframe API提供Java,Scala,Python和R。
示例:
case class Person(name : String , age : Int)
val dataframe = sqlContext.read.json("people.json")
dataframe.filter("salary > 10000").show
=> throws Exception : cannot resolve 'salary' given input age , name
当您使用多个转换和聚合步骤时,这一点尤其具有挑战性。
示例:
case class Person(name : String , age : Int)
val personRDD = sc.makeRDD(Seq(Person("A",10),Person("B",20)))
val personDF = sqlContext.createDataframe(personRDD)
personDF.rdd // returns RDD[Row] , does not returns RDD[Person]
Dataset API是DataFrames的扩展,它提供了一种类型安全的,面向对象的编程接口。它是一个强类型,不可变的对象集合,映射到关系模式。
在数据集的核心,API是一种称为编码器的新概念,它负责在JVM对象和表格表示之间进行转换。表格表示使用Spark内部Tungsten二进制格式存储,允许对序列化数据进行操作并提高内存利用率。 Spark 1.6支持自动生成各种类型的编码器,包括基本类型(例如String,Integer,Long),Scala案例类和Java Bean。
提供RDD和Dataframe的最佳功能: RDD(函数式编程,类型安全),DataFrame(关系模型,查询优化,钨执行,排序和改组)
<强>编码器:强> 通过使用编码器,可以轻松地将任何JVM对象转换为数据集,从而允许用户使用结构化和非结构化数据,而不像Dataframe。
支持的编程语言: 数据集API目前仅在Scala和Java中可用。 1.6版目前不支持Python和R. Python支持适用于2.0版本。
类型安全: 数据集API提供编译时安全性,这在Dataframe中是不可用的。在下面的示例中,我们可以看到Dataset如何使用编译lambda函数对域对象进行操作。
示例:
case class Person(name : String , age : Int)
val personRDD = sc.makeRDD(Seq(Person("A",10),Person("B",20)))
val personDF = sqlContext.createDataframe(personRDD)
val ds:Dataset[Person] = personDF.as[Person]
ds.filter(p => p.age > 25)
ds.filter(p => p.salary > 25)
// error : value salary is not a member of person
ds.rdd // returns RDD[Person]
示例:
ds.select(col("name").as[String], $"age".as[Int]).collect()
不支持Python和R:从1.6版开始,Datasets仅支持Scala和Java。 Python支持将引入Python。
与现有的RDD和Dataframe API相比,Datasets API带来了几个优势,具有更好的类型安全性和函数式编程。由于API中的类型转换要求的挑战,您仍然不需要所需的类型安全性并且会使您的代码变脆
答案 3 :(得分:59)
Spark提供的主要抽象是一个弹性分布式数据集(RDD),它是跨群集节点分区的元素的集合,可以并行操作。
分布式集合:
RDD使用MapReduce操作,该操作被广泛用于在群集上使用并行分布式算法处理和生成大型数据集。它允许用户使用一组高级操作符编写并行计算,而不必担心工作分配和容错。
不可变: RDD由分区的记录集合组成。分区是RDD中并行性的基本单元,每个分区是数据的一个逻辑分区,它是不可变的,并通过现有分区上的一些转换创建。可模糊性有助于实现计算的一致性。
容错: 在我们丢失一些RDD分区的情况下,我们可以在lineage中重放该分区上的转换以实现相同的计算,而不是跨多个节点进行数据复制。这个特性是RDD的最大好处,因为它节省了大量的努力进行数据管理和复制,从而实现更快的计算。
懒惰的评估:Spark中的所有转换都是惰性的,因为它们不会立即计算结果。相反,他们只记得应用于某些基础数据集的转换。仅当操作需要将结果返回到驱动程序时才会计算转换。
功能转换: RDD支持两种类型的操作:转换(从现有数据集创建新数据集)和操作(在数据集上运行计算后将值返回到驱动程序)。
数据处理格式:
它可以轻松高效地处理结构化数据和非结构化数据。
没有内置优化引擎 使用结构化数据时,RDD无法利用Spark的高级优化器,包括催化剂优化器和Tungsten执行引擎。开发人员需要根据其属性优化每个RDD。
处理结构化数据 与Dataframe和数据集不同,RDD不会推断摄取数据的模式,并且需要用户指定它。
Spark在Spark 1.3版本中引入了Dataframes。 Dataframe克服了RDD所面临的主要挑战。
DataFrame是组织到命名列中的分布式数据集合。它在概念上等同于关系数据库或R / Python Dataframe中的表。与Dataframe一起,Spark还引入了催化剂优化器,它利用高级编程功能构建可扩展的查询优化器。
行对象的分布式集合: DataFrame是组织到命名列中的分布式数据集合。它在概念上等同于关系数据库中的表,但在更深层次的优化下。
数据处理: 处理结构化和非结构化数据格式(Avro,CSV,弹性搜索和Cassandra)和存储系统(HDFS,HIVE表,MySQL等)。它可以从所有这些不同的数据源读取和写入。
使用催化剂优化器优化 它为SQL查询和DataFrame API提供支持。 Dataframe使用催化剂树转换框架分为四个阶段,
1.Analyzing a logical plan to resolve references
2.Logical plan optimization
3.Physical planning
4.Code generation to compile parts of the query to Java bytecode.
Hive兼容性: 使用Spark SQL,您可以在现有的Hive仓库上运行未修改的Hive查询。它重用了Hive前端和MetaStore,使您可以完全兼容现有的Hive数据,查询和UDF。
<强>钨:强> Tungsten提供物理执行后端,用于显式管理内存并动态生成字节码以进行表达式评估。
支持的编程语言:
Dataframe API提供Java,Scala,Python和R。
示例:
case class Person(name : String , age : Int)
val dataframe = sqlContect.read.json("people.json")
dataframe.filter("salary > 10000").show
=> throws Exception : cannot resolve 'salary' given input age , name
当您使用多个转换和聚合步骤时,这一点尤其具有挑战性。
示例:
case class Person(name : String , age : Int)
val personRDD = sc.makeRDD(Seq(Person("A",10),Person("B",20)))
val personDF = sqlContect.createDataframe(personRDD)
personDF.rdd // returns RDD[Row] , does not returns RDD[Person]
Dataset API是DataFrames的扩展,它提供了一种类型安全的,面向对象的编程接口。它是一个强类型,不可变的对象集合,映射到关系模式。
在数据集的核心,API是一种称为编码器的新概念,它负责在JVM对象和表格表示之间进行转换。表格表示使用Spark内部Tungsten二进制格式存储,允许对序列化数据进行操作并提高内存利用率。 Spark 1.6支持自动生成各种类型的编码器,包括基本类型(例如String,Integer,Long),Scala案例类和Java Bean。
提供RDD和Dataframe的最佳功能: RDD(函数式编程,类型安全),DataFrame(关系模型,查询优化,钨执行,排序和改组)
<强>编码器:强> 通过使用编码器,可以轻松地将任何JVM对象转换为数据集,从而允许用户使用结构化和非结构化数据,而不像Dataframe。
支持的编程语言: 数据集API目前仅在Scala和Java中可用。 1.6版目前不支持Python和R. Python支持适用于2.0版本。
类型安全: 数据集API提供编译时安全性,这在Dataframe中是不可用的。在下面的示例中,我们可以看到Dataset如何使用编译lambda函数对域对象进行操作。
示例:
case class Person(name : String , age : Int)
val personRDD = sc.makeRDD(Seq(Person("A",10),Person("B",20)))
val personDF = sqlContect.createDataframe(personRDD)
val ds:Dataset[Person] = personDF.as[Person]
ds.filter(p => p.age > 25)
ds.filter(p => p.salary > 25)
// error : value salary is not a member of person
ds.rdd // returns RDD[Person]
示例:
ds.select(col("name").as[String], $"age".as[Int]).collect()
不支持Python和R:从1.6版开始,Datasets仅支持Scala和Java。 Python支持将引入Python。
与现有的RDD和Dataframe API相比,Datasets API带来了几个优势,具有更好的类型安全性和函数式编程。由于API中的类型转换要求的挑战,您仍然不需要所需的类型安全性并且会使您的代码变脆
答案 4 :(得分:34)
RDD
是一个容错的容错集合,可以并行操作。
DataFrame
是一个组织成命名列的数据集。它是 概念上等同于关系数据库或数据中的表 R / Python中的框架,但引擎盖下的优化更为丰富。
Dataset
是一个分布式数据集合。数据集是Spark 1.6中添加的一个新界面,它提供了RDD的优势 (强类型,使用强大的lambda函数的能力)随着 Spark SQL优化执行引擎的好处。注意:
Scala / Java中的行数据集(
Dataset[Row]
)通常会将称为DataFrames 。
问:你能将一个转换为另一个,如RDD到DataFrame,反之亦然?
<强> 1。使用RDD
DataFrame
到.toDF()
val rowsRdd: RDD[Row] = sc.parallelize(
Seq(
Row("first", 2.0, 7.0),
Row("second", 3.5, 2.5),
Row("third", 7.0, 5.9)
)
)
val df = spark.createDataFrame(rowsRdd).toDF("id", "val1", "val2")
df.show()
+------+----+----+
| id|val1|val2|
+------+----+----+
| first| 2.0| 7.0|
|second| 3.5| 2.5|
| third| 7.0| 5.9|
+------+----+----+
更多方式:Convert an RDD object to Dataframe in Spark
<强> 2。使用DataFrame
方法的DataSet
/ RDD
到.rdd()
val rowsRdd: RDD[Row] = df.rdd() // DataFrame to RDD
答案 5 :(得分:24)
简单RDD
是核心组件,但DataFrame
是spark 1.30中引入的API。
名为RDD
的数据分区集合。这些RDD
必须遵循以下几个属性:
此处RDD
是结构化的或非结构化的。
DataFrame
是Scala,Java,Python和R中可用的API。它允许处理任何类型的结构化和半结构化数据。要定义DataFrame
,将分布式数据的集合组织到名为DataFrame
的命名列中。您可以轻松优化RDDs
中的DataFrame
。
您可以使用DataFrame
一次处理JSON数据,镶木地板数据,HiveQL数据。
val sampleRDD = sqlContext.jsonFile("hdfs://localhost:9000/jsondata.json")
val sample_DF = sampleRDD.toDF()
此处Sample_DF视为DataFrame
。 sampleRDD
是(原始数据),称为RDD
。
答案 6 :(得分:22)
因为DataFrame
是弱类型的,并且开发人员没有获得类型系统的好处。例如,假设您想从SQL读取内容并在其上运行一些聚合:
val people = sqlContext.read.parquet("...")
val department = sqlContext.read.parquet("...")
people.filter("age > 30")
.join(department, people("deptId") === department("id"))
.groupBy(department("name"), "gender")
.agg(avg(people("salary")), max(people("age")))
当您说people("deptId")
时,您还没有找到Int
或Long
,那么您将找回Column
个对象你需要操作。在具有类型系统(如Scala)的语言中,最终会失去所有类型安全性,从而增加了在编译时可以发现的事物的运行时错误数。
相反,输入DataSet[T]
。当你这样做时:
val people: People = val people = sqlContext.read.parquet("...").as[People]
您实际上正在找回People
对象,其中deptId
是实际的整数类型而不是列类型,因此利用了类型系统。
从Spark 2.0开始,DataFrame和DataSet API将统一,DataFrame
将成为DataSet[Row]
的类型别名。
答案 7 :(得分:8)
大多数答案都是正确的,只想在这里添加一个点
在Spark 2.0中,两个API(DataFrame + DataSet)将统一为一个API。
&#34;统一DataFrame和数据集:在Scala和Java中,DataFrame和Dataset已经统一,即DataFrame只是Row的Dataset的类型别名。在Python和R中,由于缺乏类型安全性,DataFrame是主要的编程接口。&#34;
数据集与RDD类似,但是,它们使用专门的编码器来序列化对象以便通过网络进行处理或传输,而不是使用Java序列化或Kryo。
Spark SQL支持两种不同的方法将现有RDD转换为数据集。第一种方法使用反射来推断包含特定类型对象的RDD的模式。这种基于反射的方法可以使代码更加简洁,并且在编写Spark应用程序时已经了解了模式,效果很好。
创建数据集的第二种方法是通过编程接口,允许您构建模式,然后将其应用于现有RDD。虽然此方法更详细,但它允许您在直到运行时才知道列及其类型时构造数据集。
在这里您可以找到RDD tof数据框对话答案
答案 8 :(得分:7)
DataFrame等同于RDBMS中的表,也可以通过类似于&#34; native&#34;的方式进行操作。 RDD中的分布式集合。与RDD不同,Dataframes跟踪架构并支持各种关系操作,从而实现更优化的执行。 每个DataFrame对象代表一个逻辑计划,但由于他们的&#34;懒惰&#34;在用户调用特定的&#34;输出操作&#34;。
之前,不执行任何执行答案 9 :(得分:4)
Dataframe是Row对象的RDD,每个对象代表一条记录。一个 Dataframe还知道其行的模式(即数据字段)。而Dataframes 看起来像常规RDD,在内部他们以更有效的方式存储数据,利用他们的架构。此外,它们还提供RDD上不可用的新操作,例如运行SQL查询的功能。可以从外部数据源,查询结果或常规RDD创建数据帧。
参考文献:Zaharia M.,et al。学习Spark(O'Reilly,2015)
答案 10 :(得分:4)
我希望它有所帮助!
答案 11 :(得分:0)
您可以将RDD用于结构化和非结构化,其中Dataframe / Dataset只能处理结构化和半结构化数据(它具有适当的模式)
答案 12 :(得分:0)
Spark RDD (resilient distributed dataset)
:
RDD是核心数据抽象API,自Spark的第一个发行版(Spark 1.0)起可用。它是用于处理分布式数据收集的较低级别的API。 RDD API公开了一些非常有用的方法,可用于对底层物理数据结构进行非常严格的控制。它是分布在不同计算机上的分区数据的不可变(只读)集合。 RDD支持在大型群集上进行内存中计算,从而以容错方式加快大数据处理速度。 为了实现容错,RDD使用DAG(有向无环图),它由一组顶点和边组成。 DAG中的顶点和边缘分别表示RDD和要在该RDD上应用的操作。在RDD上定义的转换是惰性的,仅在调用动作时执行
Spark DataFrame
:
Spark 1.3引入了两个新的数据抽象API – DataFrame和DataSet。 DataFrame API将数据组织到命名列中,例如关系数据库中的表。它使程序员能够在分布式数据集合上定义架构。 DataFrame中的每一行都是对象类型行。像SQL表一样,每一列在DataFrame中必须具有相同数量的行。简而言之,DataFrame是延迟评估的计划,该计划指定需要对数据的分布式集合执行的操作。 DataFrame也是一个不可变的集合。
Spark DataSet
:
作为对DataFrame API的扩展,Spark 1.3还引入了DataSet API,该API在Spark中提供了严格类型化和面向对象的编程接口。它是不可变的,类型安全的分布式数据集合。与DataFrame一样,DataSet API也使用Catalyst引擎来启用执行优化。 DataSet是DataFrame API的扩展。
Other Differences
-
答案 13 :(得分:0)
火花RDD –
RDD代表弹性分布式数据集。它是只读的 分区收集记录。 RDD是基本的数据结构 的火花。它允许程序员在内存上执行内存计算 大型群集以容错的方式。因此,可以加快任务的速度。
火花数据框 –
与RDD不同,数据被组织到命名列中。例如一张桌子 在关系数据库中。这是一个不变的分布式集合 数据。 Spark中的DataFrame允许开发人员将结构强加到 分布式数据集合,允许更高级别的抽象。
火花数据集 –
Apache Spark中的数据集是DataFrame API的扩展, 提供类型安全的,面向对象的编程接口。数据集 通过公开表达式来利用Spark的Catalyst优化器 和数据字段发送给查询计划者。
答案 14 :(得分:0)
一个。 RDD (Spark1.0) —> 数据框(Spark1.3) —> 数据集(Spark1.6)
B. RDD 让我们决定我们想要做什么,这限制了 Spark 可以对底层处理进行的优化。数据帧/数据集让我们决定我们想要做什么,让 Spark 决定如何进行计算。
c. RDD 作为内存中的 jvm 对象,RDD 涉及垃圾收集和 Java(或更好的 Kryo)序列化的开销,这在数据增长时会很昂贵。那就是降低性能。
Data frame 比 RDD 提供了巨大的性能提升,因为它具有 2 个强大的功能:
d。数据集(Project Tungsten 和 Catalyst Optimizer)如何在数据框架上得分是它的一个附加功能:编码器
答案 15 :(得分:-1)
DataFrame 是具有架构的RDD。您可以将其视为关系数据库表,因为每列都有一个名称和一个已知类型。 DataFrames 的强大之处在于,当您从结构化数据集(Json,Parquet ..)创建DataFrame时,Spark能够通过对整个(Json)进行传递来推断模式。 ,Parquet ..)正在加载的数据集。然后,在计算执行计划时,Spark可以使用模式并进行更好的计算优化。 请注意, DataFrame 在Spark v1.3.0之前称为SchemaRDD
答案 16 :(得分:-1)
所有不错的答案,并且使用每个API都有一定的取舍。 数据集是作为超级API构建的,可以解决很多问题,但是如果您了解自己的数据,并且如果处理算法经过优化可以在单次传递到大数据中做很多事情,那么RDD在很多情况下仍然是最好的选择。
使用数据集API进行聚合仍会消耗内存,并且随着时间的推移会变得更好。