Table of Contents
Untyped Dataset Operations (aka DataFrame Operations)非类型化数据集操作(即DataFrame操作)
Running SQL Queries Programmatically
Inferring the Schema Using Reflection
Programmatically Specifying the Schema
Untyped User-Defined Aggregate Functions
Hive metastore Parquet table conversion
Interacting with Different Versions of Hive Metastore
Other Configuration Options(其他可选项)
Distributed SQL Engine(分布式SQL引擎)
Running the Thrift JDBC/ODBC server
Upgrading From Spark SQL 2.0 to 2.1
Upgrading From Spark SQL 1.6 to 2.0
Upgrading From Spark SQL 1.5 to 1.6
Upgrading From Spark SQL 1.4 to 1.5
Upgrading from Spark SQL 1.3 to 1.4
Upgrading from Spark SQL 1.0-1.2 to 1.3
Compatibility with Apache Hive(兼容)
Unsupported Hive Functionality
Overview
Spark SQL 是一个用于结构化数据处理的 Spark 模块。与基本的 Spark RDD API 不同,Spark SQL 提供的接口为 Spark 提供了有关数据结构和正在执行的计算的更多信息。在内部,Spark SQL 使用这些额外的信息来执行额外的优化。有几种与 Spark SQL交互的方法,包括 SQL 和 Dataset API。当计算结果时,使用相同的执行引擎,而不依赖于使用哪种API/语言来表示计算。这种统一意味着开发人员可以很容易地在不同的 api 之间来回切换,而这些 api 提供了表达给定转换的最自然的方式。
本页上的所有示例都使用 Spark 发行版中包含的示例数据,可以在 Spark -shell、pyspark shell 或 sparkR shell 中运行。
SQL
Spark SQL 的一个用途是执行 SQL 查询。还可以使用 Spark SQL 从现有的 Hive 安装中读取数据。有关如何配置此功能的更多信息,请参阅 Hive 表部分。当在另一种编程语言中运行 SQL 时,结果将作为 DataSet/DataFrame 返回。您还可以使用命令行或JDBC/ODBC与SQL接口进行交互。
Datasets and DataFrames
数据集是分布式的数据集合。Dataset 是在 Spark 1.6中添加的一个新接口,它提供了 RDDs(强类型,能够使用强大的lambda函数)的优点和 Spark SQL 的优化执行引擎的优点。可以从 JVM 对象构造数据集,然后使用功能转换(映射、平面映射、筛选器等)进行操作。Dataset API 在 Scala 和 Java 中可用。Python 不支持数据集API。但是由于 Python 的动态特性,Dataset API的许多优点已经可用(例如,您可以通过名称自然地访问行中的字段row. columnname)。R的情况类似。
DataFrame 是组织到指定列中的数据集。它在概念上等同于关系数据库中的表或 R/Python 中的数据框架,但在底层有更丰富的优化。DataFrames 可以从大量的数据源构建,例如:结构化数据文件、Hive中的表、外部数据库或现有的RDDs。DataFrame API 可在Scala、Java、Python 和r中使用。在Scala和Java中,DataFrame 由 Rows 数据集表示。在Scala API中,DataFrame只是Dataset[Row]的一个类型别名。而在Java API中,用户需要使用Dataset<Row>来表示一个DataFrame。
在本文档中,我们经常将Scala/Java数据集的 Rows 称为DataFrames。
Getting Started
起点: SparkSession
Spark 中所有功能的入口点是 SparkSession 类。要创建一个基本的 SparkSession,只需使用 SparkSession.builder():
import org.apache.spark.sql.SparkSession
val spark = SparkSession
.builder()
.appName("Spark SQL basic example")
.config("spark.some.config.option", "some-value")
.getOrCreate()
// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._
Spark 2.0 中的 SparkSession 提供了对 Hive 特性的内置支持,包括使用 HiveQL 编写查询、访问 Hive udf 和从 Hive 表读取数据的能力。要使用这些特性,您不需要现有的 Hive 设置。
创建 DataFrames
使用 SparkSession,应用程序可以从现有的 RDD、Hive 表或 Spark 数据源创建数据流。
例如,以下代码基于 JSON 文件的内容创建了一个DataFrame:
val df = spark.read.json("examples/src/main/resources/people.json")
// Displays the content of the DataFrame to stdout
df.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
Untyped Dataset Operations (aka DataFrame Operations)非类型化数据集操作(即DataFrame操作)
DataFrames 为 Scala、Java、Python 和 R 中的结构化数据操作提供了一种特定于域的语言。
如前所述,在Spark 2.0中,DataFrames 只是 Scala 和 Java API 中的行数据集。这些操作也被称为“无类型转换”,与强类型Scala/Java 数据集的“类型化转换”形成对比。
在这里,我们包括一些基本的例子,结构化数据处理使用数据集:
// This import is needed to use the $-notation
import spark.implicits._
// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)
// Select only the "name" column
df.select("name").show()
// +-------+
// | name|
// +-------+
// |Michael|
// | Andy|
// | Justin|
// +-------+
// Select everybody, but increment the age by 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// | name|(age + 1)|
// +-------+---------+
// |Michael| null|
// | Andy| 31|
// | Justin| 20|
// +-------+---------+
// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+
// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// | 19| 1|
// |null| 1|
// | 30| 1|
// +----+-----+
完整API Documentation:http://spark.apache.org/docs/2.1.1/api/scala/index.html#org.apache.spark.sql.Dataset
除了简单的列引用和表达式外,数据集还有丰富的函数库,包括字符串操作、日期算术、公共数学操作等。完整的列表可以在DataFrame 函数引用中找到。
运行sql查询程序
SparkSession 上的 sql 函数允许应用程序以编程方式运行 sql 查询,并以 Dataframe 的形式返回结果。
// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
创建全局临时视图
Spark SQL 中的临时视图是会话范围的,如果创建它的会话终止,它就会消失。如果您希望拥有一个在所有会话之间共享的临时视图,并一直保持活动状态,直到 Spark 应用程序终止,那么您可以创建一个全局临时视图。全局临时视图绑定到系统保留的数据库 global_temp,我们必须使用限定名来引用它,例如,从global_temp.view1中选择*。
// Register the DataFrame as a global temporary view
df.createGlobalTempView("people")
// Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
// Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
创建 Datasets
但是,DataSet 与 RDDs 类似,它们使用专门的编码器对对象进行序列化,以便在网络上进行处理或传输,而不是使用 Java 序列化或 Kryo。虽然编码器和标准序列化都负责将对象转换为字节,但编码器是动态生成的代码,并使用允许 Spark 执行许多操作(如过滤、排序和散列)的格式,而无需将字节反序列化回对象。
// Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,
// you can use custom classes that implement the Product interface
case class Person(name: String, age: Long)
// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+
// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)
// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
RDD 转 DataSet
Spark SQL 支持两种不同的方法将现有的 RDDs 转换为 DataSet。第一个方法使用反射来推断包含特定对象类型的 RDD 的模式。这种基于反射的方法使代码更加简洁,并且当您在编写 Spark 应用程序时已经了解了模式时,它可以很好地工作。
创建数据集的第二种方法是通过编程接口,该接口允许您构造一个模式,然后将其应用于现有的 RDD。虽然此方法更详细,但它允许您在列及其类型直到运行时才知道时构造数据集。
使用反射推断模式
Spark SQL 的 Scala 接口支持将包含 case 类的 RDD 自动转换为 DataFrame。case 类定义表的模式。case 类的参数名使用反射读取,并成为列的名称。Case 类还可以嵌套或包含复杂类型,如 Seqs 或 Array。可以隐式地将此 RDD 转换为 DataFrame,然后将其注册为表。表可以在后续的 SQL 语句中使用。
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.Encoder
// For implicit conversions from RDDs to DataFrames
import spark.implicits._
// Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.sparkContext
.textFile("examples/src/main/resources/people.txt")
.map(_.split(","))
.map(attributes => Person(attributes(0), attributes(1).trim.toInt))
.toDF()
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people")
// SQL statements can be run by using the sql methods provided by Spark
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")
// The columns of a row in the result can be accessed by field index
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
// or by field name
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
// No pre-defined encoders for Dataset[Map[K,V]], define explicitly
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// Primitive types and case classes can be also defined as
// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()
// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))
以编程方式指定模式
如果不能提前定义 case 类(例如,记录的结构编码在一个字符串中,或者文本数据集将被解析,字段将针对不同的用户以不同的方式投影),则可以通过三个步骤以编程方式创建一个 DataFrame。
- 从原始的 RDD 创建一个 Row RDD。
- 创建由 StructType 表示的模式,该模式与步骤1中创建的 RDD 中的 Row 结构匹配。
- 通过 SparkSession 提供的 createDataFrame 方法将模式应用到 Row 的 RDD。
import org.apache.spark.sql.types._
// Create an RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")
// The schema is encoded in a string
val schemaString = "name age"
// Generate the schema based on the string of schema
val fields = schemaString.split(" ")
.map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)
// Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD
.map(_.split(","))
.map(attributes => Row(attributes(0), attributes(1).trim))
// Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)
// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")
// SQL can be run over a temporary view created using DataFrames
val results = spark.sql("SELECT name FROM people")
// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// | value|
// +-------------+
// |Name: Michael|
// | Name: Andy|
// | Name: Justin|
// +-------------+
聚合
内置的 DataFrames 函数提供常见的聚合,如 count()、countDistinct()、avg()、max()、min()等。虽然这些函数是为 DataFrames 设计的,但 Spark SQL 在 Scala 和 Java 中也有一些类型安全的版本,用于处理强类型数据集。此外,用户不仅可以使用预定义的聚合函数,还可以创建自己的聚合函数。
无类型用户自定义函数
用户必须扩展 UserDefinedAggregateFunction 抽象类来实现自定义的无类型聚合函数。例如,用户定义的平均值可能是:
import org.apache.spark.sql.expressions.MutableAggregationBuffer
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession
object MyAverage extends UserDefinedAggregateFunction {
// Data types of input arguments of this aggregate function
def inputSchema: StructType = StructType(StructField("inputColumn", LongType) :: Nil)
// Data types of values in the aggregation buffer
def bufferSchema: StructType = {
StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil)
}
// The data type of the returned value
def dataType: DataType = DoubleType
// Whether this function always returns the same output on the identical input
def deterministic: Boolean = true
// Initializes the given aggregation buffer. The buffer itself is a `Row` that in addition to
// standard methods like retrieving a value at an index (e.g., get(), getBoolean()), provides
// the opportunity to update its values. Note that arrays and maps inside the buffer are still
// immutable.
def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer(0) = 0L
buffer(1) = 0L
}
// Updates the given aggregation buffer `buffer` with new input data from `input`
def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
if (!input.isNullAt(0)) {
buffer(0) = buffer.getLong(0) + input.getLong(0)
buffer(1) = buffer.getLong(1) + 1
}
}
// Merges two aggregation buffers and stores the updated buffer values back to `buffer1`
def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
}
// Calculates the final result
def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1)
}
// Register the function to access it
spark.udf.register("myAverage", MyAverage)
val df = spark.read.json("examples/src/main/resources/employees.json")
df.createOrReplaceTempView("employees")
df.show()
// +-------+------+
// | name|salary|
// +-------+------+
// |Michael| 3000|
// | Andy| 4500|
// | Justin| 3500|
// | Berta| 4000|
// +-------+------+
val result = spark.sql("SELECT myAverage(salary) as average_salary FROM employees")
result.show()
// +--------------+
// |average_salary|
// +--------------+
// | 3750.0|
// +--------------+
Data Sources
Spark SQL 支持通过 DataFrame 接口在各种数据源上进行操作。可以使用关系转换对 DataFrame 进行操作,也可以用于创建临时视图。将 DataFrame 注册为临时视图允许您对其数据运行 SQL 查询。本节介绍使用 Spark 数据源加载和保存数据的一般方法,然后介绍可用于内置数据源的特定选项。
通用 Load/Save 函数
在最简单的形式中,默认数据源(除非 spark.sql.sources.default 另外配置了 parquet)将用于所有操作。
val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
手动自定
您还可以手动指定将与您希望传递给数据源的任何其他选项一起使用的数据源。数据源由它们的全限定名(即但对于内置的源代码,您也可以使用它们的短名称(json、parquet、jdbc、orc、libsvm、csv、text)。可以使用此语法将从任何数据源类型加载的数据流转换为其他类型。
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
直接在文件上运行SQL
您也可以使用 SQL 直接查询该文件,而不是使用 read API 将文件加载到 DataFrame 并对其进行查询。
val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
存储模式
Save 操作可以选择使用 SaveMode,它指定如果存在,如何处理现有数据。重要的是要认识到,这些保存模式不利用任何锁定,也不是原子性的。此外,在执行覆盖时,在写入新数据之前将删除数据。
Scala/Java | Any Language | Meaning |
---|---|---|
SaveMode.ErrorIfExists (default) | "error" (default) | 在将 Dataframe 保存到数据源时,如果数据已经存在,则会抛出异常。 |
SaveMode.Append | "append" | 在将 Dataframe 保存到数据源时,如果数据/表已经存在,则 Dataframe 的内容将被附加到现有数据中。 |
SaveMode.Overwrite | "overwrite" | 覆盖模式意味着在将 Dataframe 保存到数据源时,如果数据/表已经存在,则现有数据将被 DataFrame 的内容覆盖。 |
SaveMode.Ignore | "ignore" | 忽略模式意味着在将数据aframe保存到数据源时,如果数据已经存在,则 save 操作不保存 Dataframe的内容,也不更改现有数据。这类似于在 SQL 中 CREATE TABLE IF NOT EXISTS in SQL. |
保存在持久表
也可以使用 saveAsTable 命令将数据流保存为持久表到 Hive metastore。请注意,现有的 Hive 部署并不需要使用此功能。Spark 将为您创建一个默认的本地蜂巢转移(使用Derby)。与 createOrReplaceTempView 命令不同,saveAsTable 将物化 DataFrame 的内容,并在 Hive metastore 中创建一个指向数据的指针。即使在您的 Spark 程序重新启动之后,只要您保持到相同亚稳态的连接,持久表仍然存在。可以使用表的名称在 SparkSession 上调用表方法来创建持久表的 DataFrame。
默认情况下 saveAsTable 将创建一个“托管表”,这意味着数据的位置将由 metastore 控制。在删除表时,托管表的数据也会被自动删除。
目前,saveAsTable 不公开支持从 DataFrame 创建“外部表”的API。但是,在使用 saveAsTable 保存表时,可以通过向DataFrameWriter 提供一个 path 选项来实现此功能,其中 path 作为键,外部表的位置作为其值(一个字符串)。当删除外部表时,只删除其元数据。
从Spark 2.1开始,持久数据源表将每个分区的元数据存储在 Hive metastore中。这带来了几个好处:
- 由于metastore只能返回一个查询所需的分区,所以不再需要在第一个查询中发现表上的所有分区。
- Hive ddl,如ALTER TABLE PARTITION…使用 Datasource API 创建的表现在可以使用 SET LOCATION。
注意,在创建外部数据源表(带有 path 选项的表)时,默认情况下不会收集分区信息。要同步 metastore 中的分区信息,您可以调用 MSCK 修复表。
Parquet Files
Parquet 是一种柱状格式,得到许多其他数据处理系统的支持。Spark SQL支持读取和写入 parquet 文件,这些文件自动保存原始数据的模式。在编写 parquet 文件时,出于兼容性的考虑,所有列都会自动转换为可空。
spark.read.json 加载数据
使用上述例子中的数据:
// Encoders for most common types are automatically provided by importing spark.implicits._
import spark.implicits._
val peopleDF = spark.read.json("examples/src/main/resources/people.json")
// DataFrames can be saved as Parquet files, maintaining the schema information
peopleDF.write.parquet("people.parquet")
// Read in the parquet file created above
// Parquet files are self-describing so the schema is preserved
// The result of loading a Parquet file is also a DataFrame
val parquetFileDF = spark.read.parquet("people.parquet")
// Parquet files can also be used to create a temporary view and then used in SQL statements
parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
Partition 发现
表分区是在 Hive 等系统中使用的一种常见优化方法。在分区表中,数据通常存储在不同的目录中,分区列值编码在每个分区目录的路径中。拼花游戏数据源现在能够自动发现和推断分区信息。例如,我们可以使用以下目录结构将以前使用的所有人口数据存储到分区表中,并添加两个额外的列,性别和国家作为分区列:
path
└── to
└── table
├── gender=male
│ ├── ...
│ │
│ ├── country=US
│ │ └── data.parquet
│ ├── country=CN
│ │ └── data.parquet
│ └── ...
└── gender=female
├── ...
│
├── country=US
│ └── data.parquet
├── country=CN
│ └── data.parquet
└── ...
通过 path/to/table
到任意 SparkSession.read.parquet
或 SparkSession.read.load,
Spark SQL将自动从路径中提取分区信息。现在返回的 DataFrame 的模式变成:
root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)
注意,分区列的数据类型是自动推断的。目前支持数字数据类型和字符串类型。有时,用户可能不希望自动推断分区列的数据类型。对于这些用例,可以使用 spark.sql.sources.partitionColumnTypeInference.enabled 来配置自动类型推断。默认为 true。当类型推断被禁用时,分区列将使用 string 类型。
从 Spark 1.6.0开始,分区发现在默认情况下只找到给定路径下的分区。对于上面的例子,如果传递 path/to/table/gender=male
到任意 SparkSession.read.parquet
或 SparkSession.read.load
,,gender
将不被视为分区列。如果用户需要指定分区发现应该开始的基本路径,他们可以在数据源选项中设置 basePath。例如,当path/to/table/gender=male 是数据的路径,用户将 basePath 设置为 path/to/table/ 时,gender 将是一个分区列。
Schema Merging
与 ProtocolBuffer、Avro 和 Thrift 一样,Parquet 也支持模式演化。用户可以从一个简单的模式开始,然后根据需要逐步向模式中添加更多的列。这样,用户可能会得到多个模式不同但相互兼容的 parquet 文件。Parquet 数据源现在能够自动检测这种情况并合并所有这些文件的模式。
由于模式合并是一个相对昂贵的操作,并且在大多数情况下不是必需的,所以我们从1.5.0开始默认关闭它。你可以使它成为可能
- 读取 parquet 文件时将数据源选项 mergeSchema 设置为 true(如下面的示例所示),或者
- 设置全局 SQL 选项spark.sql.parquet.mergeSchema 为 true。
// This is used to implicitly convert an RDD to a DataFrame.
import spark.implicits._
// Create a simple DataFrame, store into a partition directory
val squaresDF = spark.sparkContext.makeRDD(1 to 5).map(i => (i, i * i)).toDF("value", "square")
squaresDF.write.parquet("data/test_table/key=1")
// Create another DataFrame in a new partition directory,
// adding a new column and dropping an existing column
val cubesDF = spark.sparkContext.makeRDD(6 to 10).map(i => (i, i * i * i)).toDF("value", "cube")
cubesDF.write.parquet("data/test_table/key=2")
// Read the partitioned table
val mergedDF = spark.read.option("mergeSchema", "true").parquet("data/test_table")
mergedDF.printSchema()
// The final schema consists of all 3 columns in the Parquet files together
// with the partitioning column appeared in the partition directory paths
// root
// |-- value: int (nullable = true)
// |-- square: int (nullable = true)
// |-- cube: int (nullable = true)
// |-- key: int (nullable = true)
Hive metastore Parquet table 转换
当读取和写入 Hive metastore parquet 表时,Spark SQL 将尝试使用自己的 parquet 支持,而不是 Hive SerDe,以获得更好的性能。此行为由 spark.sql.hive.convertMetastoreParquet
控制配置,并在默认情况下打开。
Hive/Parquet Schema Reconciliation
从表模式处理的角度来看,Hive和Parquet有两个关键的区别。
- Hive 不区分大小写,而 Parquet 区分
- Hive 认为所有的列都可以为空,而 parquet 中的可空性是很重要的
因此,在将一个 Hive metastore Parquet table 转换成一个 Spark SQL Parquet table 时,我们必须协调 Hive metastore schema 和 Parquet schema。和解规则如下:
-
无论是否为空,两个模式中具有相同名称的字段必须具有相同的数据类型。调整后的字段应该具有拼花球的数据类型,以保证可为空。
-
协调模式包含在Hive metastore模式中定义的字段。
- 只出现在 parquet 模式中的任何字段都将在协调模式中删除。
- 任何只出现在 Hive metastore 模式中的字段都会被添加到调和模式中作为可空字段。
Metadata Refreshing
Spark SQL缓存 parquet 元数据以获得更好的性能。当 Hive metastore parquet 表转换启用,这些元数据转换表也缓存。如果这些表是由 Hive 或其他外部工具更新的,则需要手动刷新它们以确保一致的元数据。
// spark is an existing SparkSession
spark.catalog.refreshTable("my_table")
Configuration
可以使用 SparkSession 上的 setConf 方法或使用 SQL 运行 SET key=value 命令来配置 parquet。
Property Name | Default | Meaning |
---|---|---|
spark.sql.parquet.binaryAsString | false | 其他一些生成 Parquet 的系统,特别是 Impala、Hive 和较早版本的 Spark SQL,在编写 Parquet 模式时并不区分二进制数据和字符串。此标志告诉Spark SQL将二进制数据解释为字符串,以提供与这些系统的兼容性。 |
spark.sql.parquet.int96AsTimestamp | true | 一些 parquet 生产系统,特别是 Impala 和 Hive,将时间戳存储到I NT96中。此标志告诉 Spark SQL 将INT96数据解释为一个时间戳,以提供与这些系统的兼容性。 |
spark.sql.parquet.cacheMetadata | true | 打开 parquet 模式元数据的缓存。可以加速静态数据的查询。 |
spark.sql.parquet.compression.codec | snappy | 设置压缩编解码器使用时写入 parquet 文件。可接受的值包括:未压缩、snappy、gzip、lzo。 |
spark.sql.parquet.filterPushdown | true | 启用 Parquet 过滤器下推优化时,设置为真。 |
spark.sql.hive.convertMetastoreParquet | true | 当设置为false时,Spark SQL 将使用 Hive SerDe 而不是内置的支持。 |
spark.sql.parquet.mergeSchema | false | 当为真时,Parquet 数据源将从所有数据文件收集的模式合并在一起,否则将从摘要文件或随机数据文件(如果没有可用的摘要文件)中选择模式。 |
spark.sql.optimizer.metadataOnly | true | 如果为真,则启用仅使用元数据的查询优化,该优化使用表的元数据来生成分区列,而不是表扫描。当扫描的所有列都是分区列并且查询具有满足不同语义的聚合操作符时,它适用。 |
JSON Datasets
Spark SQL 可以自动推断 JSON 数据集的模式,并将其加载为 DataSet[Row]。可以使用 SparkSession.read.json() 对字符串的 RDD 或 JSON 文件进行转换。
注意,作为 json 文件提供的文件不是典型的 json 文件。每一行必须包含一个独立的、自包含的有效JSON对象。有关更多信息,请参见JSON行文本格式,也称为新行分隔的 JSON。因此,一个常规的多行 JSON 文件通常会失败。
// A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)
// The inferred schema can be visualized using the printSchema() method
peopleDF.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)
// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")
// SQL statements can be run by using the sql methods provided by spark
val teenagerNamesDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// | name|
// +------+
// |Justin|
// +------+
// Alternatively, a DataFrame can be created for a JSON dataset represented by
// an RDD[String] storing one JSON object per string
val otherPeopleRDD = spark.sparkContext.makeRDD(
"""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleRDD)
otherPeople.show()
// +---------------+----+
// | address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+
Hive Tables
Spark SQL 还支持读取和写入存储在 Apache Hive 中的数据。然而,由于 Hive 有大量的依赖项,这些依赖项不包括在默认的Spark 分布中。如果可以在类路径中找到 Hive 依赖项,Spark 将自动加载它们。请注意,这些 Hive 依赖项也必须出现在所有的工作节点上,因为它们需要访问 Hive 序列化和反序列化库(SerDes)来访问存储在 Hive 中的数据。
Hive 的配置是通过放置你的 Hive 位置来完成的。conf/中的xml、core-site.xml(用于安全配置)和HDFS -site.xml(用于HDFS配置)文件。
当使用 Hive 时,必须使用 Hive 支持实例化 SparkSession,包括对一个持久的 Hive metastore 的连接,对 Hive serdes 的支持,以及 Hive 用户定义的函数。没有现有 Hive 部署的用户仍然可以启用 Hive 支持。当未配置 hive-site.xml 时。上下文会在当前目录中自动创建 metastore_db,并创建一个由 spark.sql.warehouse 配置的目录。默认为 Spark 应用程序启动的当前目录中的目录 Spark -warehouse。注意从Spark 2.0.0开始,hive-site.xml 中的 hive.metastore.warehouse.dir
属性被弃用。相反,使用spark.sql.warehouse。指定数据仓库中数据库的默认位置。
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession
case class Record(key: Int, value: String)
// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = "spark-warehouse"
val spark = SparkSession
.builder()
.appName("Spark Hive Example")
.config("spark.sql.warehouse.dir", warehouseLocation)
.enableHiveSupport()
.getOrCreate()
import spark.implicits._
import spark.sql
sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")
// Queries are expressed in HiveQL
sql("SELECT * FROM src").show()
// +---+-------+
// |key| value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...
// Aggregation queries are also supported.
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// | 500 |
// +--------+
// The results of SQL queries are themselves DataFrames and support all normal functions.
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")
// The items in DaraFrames are of type Row, which allows you to access each column by ordinal.
val stringsDS = sqlDF.map {
case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
// +--------------------+
// | value|
// +--------------------+
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// ...
// You can also use DataFrames to create temporary views within a SparkSession.
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")
// Queries can then join DataFrame data with data stored in Hive.
sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()
// +---+------+---+------+
// |key| value|key| value|
// +---+------+---+------+
// | 2| val_2| 2| val_2|
// | 4| val_4| 4| val_4|
// | 5| val_5| 5| val_5|
// ...
Different Versions of Hive Metastore 的交互
Spark SQL 的 Hive 支持的最重要的部分之一是与 Hive metastore 的交互,这使得 Spark SQL 能够访问 Hive 表的元数据。从Spark 1.4.0 开始,使用下面描述的配置,可以使用 Spark SQL 的单个二进制构建来查询不同版本的 Hive metastore。注意,与用于与 metastore 对话的 Hive 版本无关,内部 Spark SQL 将针对 Hive 1.2.1 进行编译,并将这些类用于内部执行(serdes、udf、UDAFs等)。
以下选项可用于配置用于检索元数据的 Hive 版本:
Property Name | Default | Meaning |
---|---|---|
spark.sql.hive.metastore.version | 1.2.1 | Hive 转移的版本。可用选项从0.12.0到1.2.1。 |
spark.sql.hive.metastore.jars | builtin | 应该用来实例化 hivemetoreclient 的 jar 的位置。这个属性可以是三个选项之一:
|
spark.sql.hive.metastore.sharedPrefixes | com.mysql.jdbc, | 一个逗号分隔的类前缀列表,应该使用在 Spark SQL 和特定版本的 Hive 之间共享的类加载器来加载。应该共享的类的一个例子是需要与 metastore 对话的 JDBC 驱动程序。其他需要共享的类是那些与已经共享的类交互的类。例如,log4j使用的自定义附加器。 |
spark.sql.hive.metastore.barrierPrefixes | (empty) | 一个逗号分隔的类前缀列表,应该为 Spark SQL 通信的每个Hive版本显式地重新加载这些类前缀。例如,在前缀中声明的 Hive udf 通常是共享的(例如org.apache.spark.*)。 |
JDBC To Other Databases
Spark SQL 还包括一个数据源,可以使用 JDBC 从其他数据库读取数据。这个功能应该比使用 JdbcRDD 更好。这是因为结果是以 Dataframe 的形式返回的,可以很容易地在 Spark SQL 中处理它们,或者将它们与其他数据源连接起来。从 Java 或 Python 中使用 JDBC 数据源也更容易,因为它不需要用户提供 ClassTag。(注意,这与 Spark SQL JDBC 服务器不同,后者允许其他应用程序使用 Spark SQL 运行查询)。
要开始使用它,您需要在 spark 类路径中包含特定数据库的 JDBC 驱动程序。例如,要从 Spark Shell 连接到 postgres,需要运行以下命令:
bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar
可以使用数据源 API 将远程数据库中的表加载为 DataFrame 或 Spark SQL 临时视图。用户可以在数据源选项中指定 JDBC 连接属性。用户和密码通常作为登录到数据源的连接属性提供。除了连接属性外,Spark 还支持以下不区分大小写的选项:
Property Name | Meaning |
---|---|
url | 要连接的JDBC URL。特定于源的连接属性可以在URL中指定。 e.g., jdbc:postgresql://localhost/test?user=fred&password=secret |
dbtable | 应该读取的 JDBC 表。注意,可以使用 SQL 查询的 FROM 子句中有效的任何内容。例如,您也可以使用圆括号中的子查询来代替完整的表。 |
driver | 用于连接到此URL的JDBC驱动程序的类名。 |
partitionColumn, lowerBound, upperBound, numPartitions | 如果指定了其中任何一个,则必须全部指定这些选项。它们描述了如何在从多个 worker 并行读取数据时对表进行分区。partitionColumn 必须是表中的数字列。注意,下界和上界只是用来决定分区步长,而不是用来过滤表中的行。因此表中的所有行都将被分区并返回。此选项仅适用于读。 |
fetchsize | JDBC 获取大小,它决定了每次往返需要获取多少行。这有助于 JDBC 驱动程序的性能,因为 JDBC 驱动程序的默认获取大小比较小(例如,Oracle有10行)。此选项仅适用于读。 |
batchsize | JDBC 批处理大小,它决定每个往返要插入多少行。这有助于提高JDBC驱动程序的性能。此选项仅适用于写入。默认为1000。 |
isolationLevel | 事务隔离级别,它应用于当前连接。它可以是 NONE、READ_COMMITTED、READ_UNCOMMITTED、REPEATABLE_READ 或 SERIALIZABLE 中的一个,对应于 JDBC 的连接对象定义的标准事务隔离级别,默认情况下为 READ_UNCOMMITTED。此选项仅适用于写入。请参阅 java.sql.Connection 中的文档。 |
truncate | 这是一个与 JDBC 写入器相关的选项。当 SaveMode。启用覆盖,此选项将导致 Spark 截断现有表,而不是删除并重新创建它。这可能更有效,并且可以防止删除表元数据(例如索引)。但是,在某些情况下,例如当新数据具有不同的模式时,它将不起作用。它默认为 false。此选项仅适用于写入。 |
createTableOptions | 这是一个与 JDBC 写入器相关的选项。如果指定了,这个选项允许在创建表时设置特定于数据库的表和分区选项(例如,CREATE table t (name string) ENGINE=InnoDB.)。此选项仅适用于写入。 |
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods
// Loading data from a JDBC source
val jdbcDF = spark.read
.format("jdbc")
.option("url", "jdbc:postgresql:dbserver")
.option("dbtable", "schema.tablename")
.option("user", "username")
.option("password", "password")
.load()
val connectionProperties = new Properties()
connectionProperties.put("user", "username")
connectionProperties.put("password", "password")
val jdbcDF2 = spark.read
.jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
// Saving data to a JDBC source
jdbcDF.write
.format("jdbc")
.option("url", "jdbc:postgresql:dbserver")
.option("dbtable", "schema.tablename")
.option("user", "username")
.option("password", "password")
.save()
jdbcDF2.write
.jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)
Troubleshooting(排错)
- JDBC 驱动程序类必须对客户端会话和所有执行器上的原始类装入器可见。这是因为 Java 的 DriverManager 类做了一个安全检查,当你打开一个连接时,它会忽略所有原始类装入器看不到的驱动。一种方便的方法是修改所有工作节点上的compute_classpath.sh 以包含您的驱动程序 jar。
- 有些数据库,如H2,将所有名称转换为大写。在 Spark SQL 中,需要使用大写来引用这些名称。
Performance Tuning(性能调优)
对于某些工作负载,可以通过在内存中缓存数据或打开一些实验性选项来提高性能。
Caching Data In Memory(内存缓存)
通过调用 Spark .catalog. cachetable(“tableName”)或 datafram .cache(), Spark SQL 可以使用内存中的列格式缓存表。然后,Spark SQL 将只扫描所需的列,并自动调整压缩,以最小化内存使用和 GC 压力。您可以调用spark.catalog.uncacheTable(“tableName”)来从内存中删除表。
可以使用 SparkSession 上的 setConf 方法或使用 SQL 运行 SET key=value命令来配置内存缓存。
Property Name | Default | Meaning |
---|---|---|
spark.sql.inMemoryColumnarStorage.compressed | true | 当设置为true Spark SQL时,将根据数据的统计信息为每一列自动选择一个压缩编解码器。 |
spark.sql.inMemoryColumnarStorage.batchSize | 10000 | 控制用于列缓存的批的大小。更大的批处理大小可以提高内存利用率和压缩,但是在缓存数据时存在OOMs风险。 |
Other Configuration Options(其他可选项)
以下选项也可用于调优查询执行的性能。在将来的版本中,随着更多的优化自动执行,这些选项可能会被弃用。
Property Name | Default | Meaning |
---|---|---|
spark.sql.files.maxPartitionBytes | 134217728 (128 MB) | 读取文件时要装入单个分区的最大字节数。 |
spark.sql.files.openCostInBytes | 4194304 (4 MB) | 打开一个文件的估计成本,通过同时扫描的字节数来衡量。这是在将多个文件放入一个分区时使用的。最好是高估,那么带有小文件的分区将比带有大文件的分区(首先计划)更快。 |
spark.sql.broadcastTimeout | 300 | 广播连接中广播等待时间的超时(秒) |
spark.sql.autoBroadcastJoinThreshold | 10485760 (10 MB) | 配置将在执行联接时广播到所有工作节点的表的最大大小(以字节为单位)。通过将此值设置为-1,可以禁用广播。请注意,目前仅支持 Hive Metastore 表的统计数据,其中命令 ANALYZE TABLE <tableName> COMPUTE statistics noscan 已经运行。 |
spark.sql.shuffle.partitions | 200 | 在为连接或聚合重组数据时配置要使用的分区数量。 |
Distributed SQL Engine(分布式SQL引擎)
Spark SQL 还可以使用它的 JDBC/ODBC 或命令行接口充当分布式查询引擎。在这种模式下,终端用户或应用程序可以直接与Spark SQL 交互来运行 SQL 查询,而不需要编写任何代码。
Running the Thrift JDBC/ODBC server
这里实现的 Thrift JDBC/ODBC 服务器对应于 Hive 1.2.1中的 HiveServer2,您可以使用 Spark 或 Hive 1.2.1 附带的 beeline 脚本来测试 JDBC 服务器。
要启动 JDBC/ODBC 服务器,请在 Spark 目录中运行以下命令:
./sbin/start-thriftserver.sh
这个脚本接受所有 bin/spark-submit 命令行选项,加上一个 --hiveconf 选项来指定 Hive 属性。您可以运行 ./sbin/start-thriftserver.sh --help 所有可用选项的完整列表的帮助。默认情况下,服务器监听 localhost:10000。你可以通过任何一个环境变量覆盖这种行为,即:
export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \
--master <master-uri> \
...
或系统属性:
./sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=<listening-port> \
--hiveconf hive.server2.thrift.bind.host=<listening-host> \
--master <master-uri>
...
现在你可以使用 beeline 来测试 JDBC/ODBC 服务器了:
./bin/beeline
直接连接到JDBC/ODBC服务器:
beeline> !connect jdbc:hive2://localhost:10000
Beeline 会询问你的用户名和密码。在非安全模式下,只需在计算机上输入用户名和空白密码。对于安全模式,请遵循 beeline 文档中的说明。
Hive 的配置是通过放置你的 Hive 位置来完成的。conf/中的 hive-site.xml、core-site.xml和hdfs-site.xml文件。
你也可以使用 Hive 附带的 beeline 脚本。
Thrift JDBC 服务器也支持通过 HTTP 传输发送 Thrift RPC消息。使用以下设置来启用 HTTP 模式作为系统属性或 hive-site.xml文件中的conf/:
hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number to listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice
要进行测试,使用beeline以http模式连接到JDBC/ODBC服务器:
beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>
Running the Spark SQL CLI
Spark SQL CLI 是一个方便的工具,可以在本地模式下运行 Hive metastore 服务,并从命令行执行查询输入。注意,Spark SQL CLI 不能与 Thrift JDBC 服务器通信。
要启动Spark SQL CLI,请在Spark目录中运行以下命令:
./bin/spark-sql
Hive 的配置是通过放置你的 Hive 位置来完成的。conf/中的 hive-site.xml、core-site.xml和hdfs-site.xml文件。您可以运行./bin/spark-sql --help 获取所有可用选项的完整列表。
Migration Guide(迁移指南)
Spark SQL 2.0 to 2.1
- Datasource 表现在将分区元数据存储在 Hive metastore 中。这意味着 Hive ddl,如 ALTER TABLE PARTITION…使用Datasource API 创建的表现在可以使用 SET LOCATION。
- 可以通过 MSCK 修复表命令将遗留数据源表迁移到这种格式。建议迁移遗留表,以利用 Hive DDL 支持和改进的计划性能。
- 要确定表是否已经迁移,look for the
PartitionProvider: Catalog
attribute when issuingDESCRIBE FORMATTED
on the table.
- 对插入覆盖表的更改…分区…数据源表的行为。
- 在以前的 Spark 版本中,即使给定了分区规范,INSERT 也会覆盖整个数据源表。现在只覆盖符合规范的分区。
- 注意,这仍然不同于 Hive 表的行为,即只覆盖与新插入数据重叠的分区。
Spark SQL 1.6 to 2.0
-
SparkSession 现在是 Spark 的新入口点,它取代了旧的 SQLContext 和 HiveContext。请注意,保留旧的 SQLContext 和HiveContext 是为了向后兼容。一个新的目录接口可以从 SparkSession 访问——现有的数据库和表访问的API,如 listTables, createExternalTable, dropTempView, cacheTable 被移动到这里。
-
DataSet API 和 DataFrame API是统一的。在 Scala 中,DataFrame 成为 Dataset[Row] 的类型别名,而Java API用户必须使用Dataset<Row>替换DataFrame。类型化转换(例如,map、filter和groupByKey)和非类型化转换(例如,select和groupBy)都可以在数据集类中使用。由于Python和R中的编译时类型安全不是语言特性,所以Dataset的概念不适用于这些语言的api。相反,DataFrame仍然是主要的编程抽象,这类似于这些语言中的单节点数据帧概念。
- Dataset and DataFrame API unionAll已经被弃用并被union取代
- Dataset and DataFrame API 已经不推荐使用explosion,或者使用functions. burst () with select或flatMap
-
Dataset and DataFrame API registerTempTable 已被弃用,并被 createOrReplaceTempView 取代
- Changes to
CREATE TABLE ... LOCATION
behavior for Hive tables.- 从Spark 2.0,创建表…位置相当于创建外部表…定位,以防止意外丢失用户提供的位置中的现有数据。这意味着,在具有用户指定位置的 Spark SQL 中创建的 Hive 表始终是一个 Hive 外部表。删除外部表不会删除数据。不允许用户为Hive管理的表指定位置。请注意,这与Hive行为不同。
- 因此,这些表上的 DROP 表语句不会删除数据。
Spark SQL 1.5 to 1.6
- 从Spark 1.6开始,在默认情况下 Thrift 服务器以多会话模式运行。这意味着每个 JDBC/ODBC 连接都拥有自己的 SQL 配置和临时函数注册表的副本。但是缓存的表仍然是共享的。如果您希望在旧的单会话模式下运行 Thrift 服务器,请设置选项 spark.sql.hive.thriftServer.singleSession 为true。您可以将此选项添加到 spark-defaults.conf,或将其传递给 start-thriftserver.sh via—conf:
./sbin/start-thriftserver.sh \
--conf spark.sql.hive.thriftServer.singleSession=true \
...
-
Since 1.6.1, sparkR 中的 withColumn 方法支持向 DataFrame 的同名现有列添加新列或替换现有列。
-
From Spark 1.6, 将长类型强制转换为 TimestampType 预期的是秒而不是微秒。此更改是为了匹配 Hive 1.2的行为,以便从数值类型向 TimestampType 进行更一致的类型转换。详见SPARK-11724。
Spark SQL 1.4 to 1.5
- 默认情况下,使用手动管理内存(Tungsten)优化执行现在是启用的,同时为表达式求值生成代码。这些功能都可以通过设置
spark.sql.tungsten.enabled 为 false 来
禁用。 - 默认情况下不再启用 parquet 模式合并。It can be re-enabled by setting
spark.sql.parquet.mergeSchema
totrue
. - python中字符串到列的解析现在支持使用点(.)来限定列或访问嵌套的值。For example
df['table.column.nestedField']
. 但是,这意味着如果列名包含任何点,现在必须使用反号转义它们 (e.g.,table.`column.with.dots`.nested
). - 内存中的列存储分区修剪在默认情况下是打开的。It can be disabled by setting
spark.sql.inMemoryColumnarStorage.partitionPruning
tofalse
. - 不再支持无限精度的十进制列,取而代之的是Spark SQL强制最大精度为38。当从BigDecimal对象推断模式时,现在使用的精度是(38,18)。如果在DDL中没有指定精度,则默认保留Decimal(10,0)。
- 时间戳现在以1us的精度存储,而不是1ns
- 在sql方言中,浮点数现在被解析为十进制。HiveQL 解析保持不变。
- SQL/DataFrame 函数的规范名称现在是小写的 (e.g., sum vs SUM).
- JSON 数据源不会自动加载其他应用程序创建的新文件(即没有通过 Spark SQL 插入数据集的文件)。对于 JSON 持久表(即表的元数据存储在 Hive Metastore中),用户可以使用 REFRESH table SQL 命令或 HiveContext 的 refreshTable 方法将这些新文件包含到表中。对于表示 JSON 数据集的 DataFrame,用户需要重新创建 DataFrame,而新的 DataFrame 将包含新文件。
- DataFrame.withColumn 方法支持添加新列或替换同名的现有列。
Spark SQL 1.3 to 1.4
DataFrame数据读取/写入接口
根据用户的反馈,我们创建了一个新的、更灵活的API,用于读取数据(SQLContext.read)和写入数据(datafrpm .write),并废弃了旧的API (e.g., SQLContext.parquetFile
, SQLContext.jsonFile
).
See the API docs for SQLContext.read
( Scala, Java, Python ) and DataFrame.write
( Scala, Java, Python ) more information.
DataFrame.groupBy 保留分组列
根据用户反馈,我们更改了 DataFrame.groupBy().agg()
的默认行为,以保留结果 DataFreme 中的分组列。要在1.3中保持这种行为,请设置 spark.sql.retainGroupColumns
to false
.
// In 1.3.x, in order for the grouping column "department" to show up,
// it must be included explicitly as part of the agg function call.
df.groupBy("department").agg($"department", max("age"), sum("expense"))
// In 1.4+, grouping column "department" is included automatically.
df.groupBy("department").agg(max("age"), sum("expense"))
// Revert to 1.3 behavior (not retaining grouping column) by:
sqlContext.setConf("spark.sql.retainGroupColumns", "false")
Behavior change on DataFrame.withColumn
在1.4之前,datafame . withcolumn()只支持添加一个列。在结果 DataFrame 中,该列将始终作为具有指定名称的新列添加,即使可能存在任何具有相同名称的现有列。从1.4开始,datafame . withcolumn()支持添加一个不同于所有现有列的名称的列,或者替换相同名称的现有列。
请注意,此更改仅针对 Scala API,不针对 PySpark 和 SparkR。
Spark SQL 1.0-1.2 to 1.3
在Spark 1.3中,我们从 Spark SQL 中删除了“Alpha”标签,并对可用的api进行了清理。从Spark 1.3开始,Spark SQL将提供与1中的其他版本的二进制兼容性。X系列。这种兼容性保证排除了显式标记为不稳定的api (i.e., DeveloperAPI or Experimental).
Rename of SchemaRDD to DataFrame
升级到 Spark SQL 1.3时,用户会注意到的最大变化是 SchemaRDD 被重命名为 DataFrame。这主要是因为 DataFrames 不再直接从 RDD 继承,而是通过自己的实现提供了 RDDs 提供的大部分功能。仍然可以通过调用.rdd 方法将数据流转换为 RDDs。
在 Scala 中有一个从 SchemaRDD 到 DataFrame 的类型别名,用于为某些用例提供源代码兼容性。仍然建议用户更新他们的代码以使用 DataFrame。Java 和 Python 用户需要更新他们的代码。
Unification of the Java and Scala APIs
在 Spark 1.3之前,有独立的 Java 兼容类( JavaSQLContext 和 JavaSchemaRDD )来镜像 Scala API。在 Spark 1.3中,Java API 和 Scala API 是统一的。使用这两种语言的用户都应该使用 SQLContext 和 DataFrame。一般来说,这些类尝试使用来自两种语言的可用类型(例如,数组而不是特定于语言的集合)。在某些情况下,不存在公共类型(例如,用于传递闭包或映射),则使用函数重载。
此外,还删除了 Java 特定类型的API。Scala 和 Java的用户都应该使用 org.apache.spark.sql 中的类。以编程方式描述模式的类型。
Isolation of Implicit Conversions and Removal of dsl Package (Scala-only)
在Spark 1.3之前的许多代码示例都是从 import sqlContext 开始的。将所有的函数从 sqlContext 带入作用域。在 Spark 1.3中,我们已经隔离了将 RDDs 转换为 DataFrames 并将其转换为 SQLContext 内的对象的隐式转换。用户现在应该编写 import sqlContext.implicits._。
此外,隐式转换现在只扩展了由产品(即。使用toDF方法,而不是自动应用。
当用户在DSL中使用函数(现在已被DataFrame API所取代)时,他们通常会导入org.apache.spark.sql.catalyst.dsl。相反,应该使用公共dataframe函数API: import org.apache.spark.sql.functions._。
Removal of the type aliases in org.apache.spark.sql for DataType (Scala-only)
Spark 1.3 删除数据类型的基本sql包中出现的类型别名。Users should instead import the classes in org.apache.spark.sql.types
UDF Registration Moved to sqlContext.udf
(Java & Scala)
用于注册 udf(用于 DataFrame DSL或SQL)的函数已被移动到 SQLContext 中的 udf 对象中。
sqlContext.udf.register("strLen", (s: String) => s.length())
Python UDF注册没有改变。
Python DataTypes No Longer Singletons
在 Python 中使用数据类型时,您需要构造它们(例如StringType()),而不是引用单例。
Compatibility with Apache Hive(兼容)
Spark SQL被设计成与 Hive Metastore、SerDes 和 udf 兼容。目前,Hive SerDes 和 udf 基于 Hive 1.2.1, Spark SQL可以连接到不同版本的 Hive Metastore(从0.12.0到1.2.1)。Also see [Interacting with Different Versions of Hive Metastore] (#interacting-with-different-versions-of-hive-metastore)).
Deploying in Existing Hive Warehouses
Spark SQL Thrift JDBC 服务器被设计为与现有的 Hive 安装“开箱即用”。您不需要修改现有的 Hive Metastore,也不需要更改表的数据位置或分区。
Supported Hive Features
Spark SQL支持绝大多数的Hive功能,如:
- Hive query statements, including:
SELECT
GROUP BY
ORDER BY
CLUSTER BY
SORT BY
- All Hive operators, including:
- Relational operators (
=
,⇔
,==
,<>
,<
,>
,>=
,<=
, etc) - Arithmetic operators (
+
,-
,*
,/
,%
, etc) - Logical operators (
AND
,&&
,OR
,||
, etc) - Complex type constructors
- Mathematical functions (
sign
,ln
,cos
, etc) - String functions (
instr
,length
,printf
, etc)
- Relational operators (
- User defined functions (UDF)
- User defined aggregation functions (UDAF)
- User defined serialization formats (SerDes)
- Window functions
- Joins
JOIN
{LEFT|RIGHT|FULL} OUTER JOIN
LEFT SEMI JOIN
CROSS JOIN
- Unions
- Sub-queries
SELECT col FROM ( SELECT a + b AS col from t1) t2
- Sampling
- Explain
- Partitioned tables including dynamic partition insertion
- View
- All Hive DDL Functions, including:
CREATE TABLE
CREATE TABLE AS SELECT
ALTER TABLE
- Most Hive Data types, including:
TINYINT
SMALLINT
INT
BIGINT
BOOLEAN
FLOAT
DOUBLE
STRING
BINARY
TIMESTAMP
DATE
ARRAY<>
MAP<>
STRUCT<>
Unsupported Hive Functionality
下面是我们还不支持的 Hive 特性列表。大多数这些特性很少在 Hive 部署中使用。
Major Hive Features
- 带有 bucket 的表:bucket 是 Hive 表分区中的散列分区。Spark SQL 还不支持 bucket。
Esoteric Hive Features
UNION
type- Unique join
- Column statistics collecting: Spark SQL目前不支持使用扫描来收集列统计信息,只支持填充 hive metastore 的 sizeInBytes 字段。
Hive Input/Output Formats
- File format for CLI: 对于显示回 CLI 的结果,Spark SQL 只支持 TextOutputFormat。
- Hadoop archive
Hive Optimizations
一些 Hive 优化还没有包括在 Spark。由于 Spark SQL 的内存计算模型,其中一些(如索引)就不那么重要了。其他的则是为未来的Spark SQL 版本准备的。
- 块级位图索引和虚拟列 (used to build indexes)
- 自动确定连接和 groupbys 的减少器的数量:目前在Spark SQL中,您需要使用控制并行度的后洗牌 “
SET spark.sql.shuffle.partitions=[num_tasks];
”. - Meta-data only query: 对于只能使用元数据回答的查询,Spark SQL仍然会启动任务来计算结果。
- Skew data flag: 在Hive中,Spark SQL不遵循倾斜数据标志。
STREAMTABLE
hint in join: Spark SQL不遵循流表提示。- Merge multiple small files for query results: 如果结果输出包含多个小文件,Hive 可以选择将小文件合并到更少的大文件中,以避免 HDFS 元数据溢出。Spark SQL 不支持这一点。
参考
Data Types(数据类型)
Spark SQL 和 DataFrames 支持以下数据类型:
- Numeric types
ByteType
: 表示1字节带符号整数。数字的范围是从-128到127。ShortType
: 表示2字节带符号整数。数字的范围是从-32768到32767。IntegerType
: 表示4字节有符号整数。数字的范围是从-2147483648到2147483647。LongType
: 表示8字节带符号整数。数字的范围是-9223372036854775808到9223372036854775807。FloatType
: 表示4字节的单精度浮点数。DoubleType
: 表示8字节双精度浮点数。DecimalType
: 表示任意精度带符号的十进制数。内部支持java.math.BigDecimal。 一个BigDecimal
由任意精度整数无标度值和32位整数标度组成。
- String type
StringType
: 表示字符串值。
- Binary type
BinaryType
: 表示字节序列值。
- Boolean type
BooleanType
: 是布尔值。
- Datetime type
TimestampType
: 表示由年、月、日、小时、分钟和秒字段值组成的值。DateType
: 表示由字段年、月、日的值组成的值。
- Complex types
ArrayType(elementType, containsNull)
:表示由一系列元素组成的值,这些元素的类型为 elementType。containsNull 用于指示 ArrayType 值中的元素是否可以具有空值。MapType(keyType, valueType, valueContainsNull)
: 表示由一组键值对组成的值。键的数据类型由 keyType 描述,值的数据类型由 valueType 描述。对于 MapType 值,键不允许有空值。valueContainsNull 用于指示映射类型值的值是否可以为空。StructType(fields)
: 表示由一系列StructFields(字段)描述的结构的值。StructField(name, dataType, nullable)
: 表示结构类型中的字段。字段的名称由名称指示。字段的数据类型由数据类型表示。nullable用于指示此字段的值是否可以为空值。
Spark SQL 的所有数据类型都位于 org.apache.spark.sql.types 包中。你可以通过这样做来访问它们
import org.apache.spark.sql.types._
Data type | Value type in Scala | API to access or create a data type |
---|---|---|
ByteType | Byte | ByteType |
ShortType | Short | ShortType |
IntegerType | Int | IntegerType |
LongType | Long | LongType |
FloatType | Float | FloatType |
DoubleType | Double | DoubleType |
DecimalType | java.math.BigDecimal | DecimalType |
StringType | String | StringType |
BinaryType | Array[Byte] | BinaryType |
BooleanType | Boolean | BooleanType |
TimestampType | java.sql.Timestamp | TimestampType |
DateType | java.sql.Date | DateType |
ArrayType | scala.collection.Seq | ArrayType(elementType, [containsNull]) Note: The default value of containsNull is true. |
MapType | scala.collection.Map | MapType(keyType, valueType, [valueContainsNull]) Note: The default value of valueContainsNull is true. |
StructType | org.apache.spark.sql.Row | StructType(fields) Note: fields is a Seq of StructFields. Also, two fields with the same name are not allowed. |
StructField | The value type in Scala of the data type of this field (For example, Int for a StructField with the data type IntegerType) | StructField(name, dataType, [nullable]) Note: The default value of nullable is true. |
NaN语义
在处理与标准浮点语义不完全匹配的浮点类型或双类型时,有一种特殊的非数字(NaN)处理方法。具体地说:
- NaN = NaN 返回 true
- 在聚合中,所有的 NaN 值都被分组在一起。
- NaN 被视为连接键中的一个正常值。
- NaN 值最后按升序排列,比任何其他数值都大。
原文地址:http://spark.apache.org/docs/2.1.1/sql-programming-guide.html