Spark SQL,DataFrames和Datasets Guide (spark 2.4.3)

本文围绕Spark SQL展开,介绍其用于结构化数据计算,可通过SQL和DataSet API交互。阐述了SparkSession创建、DataFrame和Datasets操作、与RDD交互方法、聚合函数使用等。还提及支持多种数据源,包括Parquet、ORC、JSON等,以及性能调优和分布式SQL引擎使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

Spark SQL是Spark的一个组件,用于结构化数据的计算。与基本Spark RDD API不同,Spark SQL提供的接口为Spark提供了关于数据和正在执行的计算的结构的更多信息。在内部,Spark使用这些额外信息来执行额外的优化。与Sql SQL交互的方法有多种,包括SQL和DataSet API。当计算一个结果时,使用相同的执行引擎,这与您用来表达计算的API/语言无关。这种统一意味着,基于此API提供的表达一个给定转化的方式,开发人员可以轻松地在不同API之间来回切换。

SQL

Spark SQL的一个使用是执行SQL查询。Spark SQL也可以用于从现有的Hive安装中读取数据。有关如何配置此功能的更多信息,请参阅Hive Table部分。当在另一种编程语言中运行SQL时,结果将返回为DataSet/DataFrame。您还可以使用命令行或通过JDBC/ODBC与SQL界面交互。

Datasets and DataFrames

Datasets

Datasets 是分布式数据集合,是Spark 1.6中添加的一个新接口,它提供了RDD的优势(强类型,使用强大的lambda函数的能力)和Spark SQL优化执行引擎的优点。Dataset可以由JVM对象构造,然后使用功能转换(map, flatMap, filter等)进行操作。Datasets API在Scala和 Java中可用。Python没有对Dataset API的支持。但由于Python的动态特性,数据集API的许多好处已经可用(即,您可以通过row.columnName按名称访问行的字段)。R的情况类似。

DataFrames

DataFrame是由分布式数据集合(Datasets)组成的一系列命名列,它与关系数据库的表类似,但有很多优化的地方。DataFrame支持多种数据源,包括结构化数据、Hive的表、外部数据库、RDDs等。DataFrame API支持scala 、java、Python和R语言。在Scala和Java中,DataFrame由Dataset的Rows 表示。在Scala API中,DataFrame它只是一个类型别名Dataset[Row]。而在Java API中,用户需要使用Dataset来表示DataFrame。

开始

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 仓库中查找examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala
SparkSession 在Spark 2.0中提供了对Hive功能的内置支持,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表读取数据的功能。要使用这些功能,不需要对现有的hive进行设置。

创建DataFrame

使用SparkSession,可以从现有的RDD,Hive表或Spark数据源创建DataFrame 。
示例,以下内容基于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|
// +----+-------+

在spark 仓库examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala中可以找到完整示例。

DataFrame操作

DataFrame支持结构化数据领域常用的数据操作,支持Scala、Java、Python和R语言,基本操作示例:

// 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|
// +----+-----+

完整的例子参考spark 仓库 examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala
除了简单的列引用和表达式之外,数据集还具有丰富的函数库,包括字符串操作,日期算术,常用数学运算等。完整列表可在DataFrame函数参考中找到。

运行SQL查询程序

SparkSession使应用程序以编程方式运行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 仓库 examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala

全局临时视图

Spark SQL中的临时视图是会话范围的,如果创建它的会话终止,则会消失。如果希望有一个临时视图在所有会话之间共享,并在Spark应用程序终止之前保持活动状态,则可以创建一个全局临时视图。全局临时视图绑定到系统保留的数据库global_temp中,我们必须使用限定名称来引用它,例如:SELECT * FROM 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|
// +----+-------+

完整代码在spark 仓库的位置examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala

创建Datasets

Datasets与RDDS相似,但是,它们不使用Java序列化或Kryo,而是使用专门的编码器来序列化对象以在网络上进行处理或传输。虽然编码器和标准序列化都负责将对象转换成字节,但编码器是动态生成的代码,并使用一种允许Spark执行许多操作(如过滤、排序和哈希)的格式,而不必将字节反序列化为对象。

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|
// +----+-------+

完整例子位于:examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala

与RDD交互的操作

Spark SQL支持两种不同方法将现有RDDS转换成Datasets。第一种方法基于反射方式把RDD转化为Datasets。这种基于反射的方法可以使代码更简洁。
创建Datasets的第二种方法是通过编程接口,它允许您构造一个schema,然后将其应用到现有的RDD。虽然这个方法比较繁琐,但是它允许您在运行之前当列和列的类型都不确定时构建Datasets。

使用反射方式推导Schema

Scala SQL的Scala接口支持将包case classes(样例类)的RDD自动转换为DataFrame。case classes(样例类)定义表的结构。通过反射来读取case class(样例类)的参数的名称,并成为列的名称。case classes(样例类)也可以嵌套或包含复杂的类型,如Seqs(队列)或数组。这些RDD可以被隐式转换为DataFrame,然后将其注册为表,用于后续SQL语句。

// 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))

完整代码位于spark 仓库 examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala

编程接口方式指定表结构

当无法提前定义 case classes(样例类)(例如,记录结构被编码在字符串中,或传递的是文本数据集,并且字段将针对不同的用户进行不同的投影)时,可以通过三个步骤以编程方式创建DataFrame。

  • Create an RDD of Rows from the original RDD;(从最初的RDD上创建Row)
  • Create the schema represented by a StructType matching the structure of Rows in the RDD created in Step 1.(创建一个和步骤一中创建的Row结构相同的StructType来代表表的结构)
  • Apply the schema to the RDD of Rows via createDataFrame method provided by SparkSession. (通过SparkSession的createDataFrame方法将步骤二中创建的表结构应用到Rows上)
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|
// +-------------+

完整代码位于spark仓库 examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala

聚合

DataFrames内置了常见的聚合的函数,如count(), countDistinct(), avg(), max(), min()(等等),而这些函数是为DataFrames设计的, Spark SQL 在Scala和Java中也提供一些类型安全的版本,可以与强类型数据集一起工作。此外,不限制预定义的聚合函数,用户可以创建自己的聚合函数。

用户定义的隐式聚合函数

用户需要继承UserDefinedAggregateFunction这个抽象类,来实现自定义的隐式转化函数。例如,用户定义的平均值可能如下所示:

import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.MutableAggregationBuffer
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.types._

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|
// +--------------+

完整例子 examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedUntypedAggregation.scala

类型安全的自定义聚合函数

继承Aggregator这个抽象类,来实现自定义的类型安全的自定义聚合函数,例如,类型安全的用户定义平均值可能如下所示:

import org.apache.spark.sql.{Encoder, Encoders, SparkSession}
import org.apache.spark.sql.expressions.Aggregator

case class Employee(name: String, salary: Long)
case class Average(var sum: Long, var count: Long)

object MyAverage extends Aggregator[Employee, Average, Double] {
  // A zero value for this aggregation. Should satisfy the property that any b + zero = b
  def zero: Average = Average(0L, 0L)
  // Combine two values to produce a new value. For performance, the function may modify `buffer`
  // and return it instead of constructing a new object
  def reduce(buffer: Average, employee: Employee): Average = {
    buffer.sum += employee.salary
    buffer.count += 1
    buffer
  }
  // Merge two intermediate values
  def merge(b1: Average, b2: Average): Average = {
    b1.sum += b2.sum
    b1.count += b2.count
    b1
  }
  // Transform the output of the reduction
  def finish(reduction: Average): Double = reduction.sum.toDouble / reduction.count
  // Specifies the Encoder for the intermediate value type
  def bufferEncoder: Encoder[Average] = Encoders.product
  // Specifies the Encoder for the final output value type
  def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}

val ds = spark.read.json("examples/src/main/resources/employees.json").as[Employee]
ds.show()
// +-------+------+
// |   name|salary|
// +-------+------+
// |Michael|  3000|
// |   Andy|  4500|
// | Justin|  3500|
// |  Berta|  4000|
// +-------+------+

// Convert the function to a `TypedColumn` and give it a name
val averageSalary = MyAverage.toColumn.name("average_salary")
val result = ds.select(averageSalary)
result.show()
// +--------------+
// |average_salary|
// +--------------+
// |        3750.0|
// +--------------+

完整的例子examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedTypedAggregation.scala。

数据源

Spark SQL支持通过DataFrame接口对各种数据源进行操作。DataFrame可以使用关系转换进行操作,也可以用于创建临时视图。将DataFrame注册为临时视图允许您对其数据运行SQL查询。本节介绍使用Spark数据源加载和保存数据的一般方法,然后介绍可用于内置数据源的特定选项。

通用 加载/保存 功能

手动指定选项

您还可以手动指定将要使用的数据源以及要传递给数据源的任何其他选项。数据源通过其全名指定(即org.apache.spark.sql.parquet),但内置的来源,你也可以使用自己的短名称(json,parquet,jdbc,orc,libsvm,csv,text)。从任何数据源类型加载的DataFrame都可以使用此语法转换为其他类型。
加载一个json文件:

val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")

在spark 仓库中完整的例子位置:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala
加载一个csv文件:

val peopleDFCsv = spark.read.format("csv")
  .option("sep", ";")
  .option("inferSchema", "true")
  .option("header", "true")
  .load("examples/src/main/resources/people.csv")

spark 仓库中完整例子位置examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala
加载orc文件

usersDF.write.format("orc")
  .option("orc.bloom.filter.columns", "favorite_color")
  .option("orc.dictionary.key.threshold", "1.0")
  .save("users_with_options.orc")

例子:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

直接在文件上运行SQL

您可以直接使用SQL查询该文件,而不是使用读取API将文件加载到DataFrame并进行查询。

val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")

例子:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

保存模式

Scala/JavaAny LanguageMeaning
SaveMode.ErrorIfExists (default)“error” or “errorifexists” (default)将DataFrame保存到数据源时,如果数据已存在,则会引发异常。
SaveMode.Append“append”将DataFrame保存到数据源时,如果数据/表已存在,则DataFrame的内容应附加到现有数据。
SaveMode.Overwrite“overwrite”覆盖模式意味着在将DataFrame保存到数据源时,如果数据/表已经存在,则预期现有数据将被DataFrame的内容覆盖。
SaveMode.Ignore“ignore”忽略模式意味着在将DataFrame保存到数据源时,如果数据已存在,则预期保存操作不会保存DataFrame的内容而不会更改现有数据。这与CREATE TABLE IF NOT EXISTSSQL类似。

保存数据并持久化表

DataFrames也可以使用该saveAsTable 命令将持久表保存到Hive Metastore中。请注意,使用此功能不需要现有的Hive部署。Spark将为您创建默认的本地Hive Metastore(使用Derby)。与createOrReplaceTempView命令不同, saveAsTable将实现DataFrame的内容并创建指向Hive Metastore中数据的指针。只要您保持与同一Metastore的连接,即使您的Spark程序重新启动后,持久表仍然存在。可以通过使用 SparkSession上的方法来创建持久表 。

对于基于文件的数据源,例如text,parquet,json等,您可以通过path选项指定自定义表路径 ,例如df.write.option(“path”, “/some/path”).saveAsTable(“t”)。删除表时,将不会删除自定义表路径,并且表数据仍然存在。如果未指定自定义表路径,则Spark会将数据写入仓库目录下的默认表路径。删除表时,也将删除默认表路径。

从Spark 2.1开始,持久数据源表将每个分区元数据存储在Hive Metastore中。这带来了几个好处:

  • 由于Metastore只能返回查询所需的分区,因此不再需要在表的第一个查询中发现所有分区。
  • Hive DDL ALTER TABLE PARTITION … SET LOCATION现在可用于使用Datasource API创建的表。
    请注意,在创建外部数据源表(带有path选项的表)时,默认情况下不会收集分区信息。要同步Metastore中的分区信息,可以调用MSCK REPAIR TABLE。

Bucketing(分段), Sorting(排序) and Partitioning(分区)

对于基于文件的数据源,还可以对输出进行存储和排序或分区。分段和排序仅适用于持久表:

  • 把数据保存为Hive表,bucketBy 分桶
peopleDF.write.bucketBy(42, "name").sortBy("age").saveAsTable("people_bucketed")

完整例子examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

  • 分区
    而分区则可以同时使用save和saveAsTable使用数据集API。

    usersDF.write.partitionBy(“favorite_color”).format(“parquet”).save(“namesPartByColor.parquet”)

    也可以对单个表同时使用分桶和分区:

    peopleDF
    .write
    .partitionBy(“favorite_color”)
    .bucketBy(42, “name”)
    .saveAsTable(“people_partitioned_bucketed”)
    完整例子:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

Parquet Files

Parquet是列式存储格式的一种文件类型,Parquet是列式存储格式的一种文件类型,Spark SQL支持读取和写入Parquet文件,这些文件自动保留原始数据的模式。

加载数据

// 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|
// +------------+

完整的例子:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

分区发现Partition Discovery

Table partitioning(表分区)是在像 Hive 这样的系统中使用的常见的优化方法。在 partitioned table(分区表)中,数据通常存储在不同的目录中,partitioning column values encoded(分区列值编码)在每个 partition directory(分区目录)的路径中。Parquet data source(Parquet 数据源)现在可以自动 discover(发现)和 infer(推断)分区信息。例如,我们可以使用以下 directory structure(目录结构)将所有以前使用的 population data(人口数据)存储到 partitioned table(分区表)中,其中有两个额外的列 gender 和 country 作为 partitioning columns(分区列):

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 将自动从路径中提取 partitioning information(分区信息)。现在返回的 DataFrame 的 schema(模式)变成:

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)

请注意,会自动 inferred(推断)partitioning columns(分区列)的 data types(数据类型)。目前,支持 numeric data types(数字数据类型)和 string type(字符串类型)。有些用户可能不想自动推断 partitioning columns(分区列)的数据类型。对于这些用例,automatic type inference(自动类型推断)可以由 spark.sql.sources.partitionColumnTypeInference.enabled 配置,默认为 true。当禁用 type inference(类型推断)时,string type(字符串类型)将用于 partitioning columns(分区列)。

从 Spark 1.6.0 开始,默认情况下,partition discovery(分区发现)只能找到给定路径下的 partitions(分区)。对于上述示例,如果用户将 path/to/table/gender=male 传递给 SparkSession.read.parquet 或 SparkSession.read.load,则 gender 将不被视为 partitioning column(分区列)。如果用户需要指定 partition discovery(分区发现)应该开始的基本路径,则可以在数据源选项中设置 basePath。例如,当 path/to/table/gender=male 是数据的路径并且用户将 basePath 设置为 path/to/table/,gender 将是一个 partitioning column(分区列)。

Schema Merging(模式合并)

像 ProtocolBuffer,Avro 和 Thrift 一样,Parquet 也支持 schema evolution(模式演进)。用户可以从一个 simple schema(简单的架构)开始,并根据需要逐渐向 schema 添加更多的 columns(列)。以这种方式,用户可能会使用不同但相互兼容的 schemas 的 multiple Parquet files(多个 Parquet 文件)。Parquet data source(Parquet 数据源)现在能够自动检测这种情况并 merge(合并)所有这些文件的 schemas。

由于 schema merging(模式合并)是一个 expensive operation(相对昂贵的操作),并且在大多数情况下不是必需的,所以默认情况下从 1.5.0 开始。你可以按照如下的方式启用它:

读取 Parquet 文件时,将 data source option(数据源选项)mergeSchema 设置为 true(如下面的例子所示),或
将 global SQL option(全局 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)

完整例子:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

Hive metastore Parquet table conversion(Hive metastore Parquet table 转换)

当读取和写入 Hive metastore Parquet 表时,Spark SQL 将尝试使用自己的 Parquet support(Parquet 支持),而不是 Hive SerDe 来获得更好的性能。此 behavior(行为)由 spark.sql.hive.convertMetastoreParquet 配置控制,默认情况下 turned on(打开)。

Hive/Parquet Schema Reconciliation

从 table schema processing(表格模式处理)的角度来说,Hive 和 Parquet 之间有两个关键的区别。

  1. Hive 不区分大小写,而 Parquet 不是
  2. Hive 认为所有 columns(列)都可以为空,而 Parquet 中的可空性是 significant(重要)的。
    由于这个原因,当将 Hive metastore Parquet 表转换为 Spark SQL Parquet 表时,我们必须调整 metastore schema 与 Parquet schema。reconciliation 规则是:
  • 在两个 schema 中具有 same name(相同名称)的 Fields(字段)必须具有 same data type(相同的数据类型),而不管 nullability(可空性)。reconciled field 应具有 Parquet 的数据类型,以便 nullability(可空性)得到尊重。

  • reconciled schema(调和模式)正好包含 Hive metastore schema 中定义的那些字段。

    1) 只出现在 Parquet schema 中的任何字段将被 dropped(删除)在 reconciled schema 中。
    2)仅在 Hive metastore schema 中出现的任何字段在 reconciled schema 中作为 nullable field(可空字段)添加。

Metadata Refreshing(元数据刷新)

Spark SQL 缓存 Parquet metadata 以获得更好的性能。当启用 Hive metastore Parquet table conversion(转换)时,这些 converted tables(转换表)的 metadata(元数据)也被 cached(缓存)。如果这些表由 Hive 或其他外部工具更新,则需要手动刷新以确保 consistent metadata(一致的元数据)。

// 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.binaryAsStringfalse一些其他 Parquet-producing systems(Parquet 生产系统),特别是 Impala,Hive 和旧版本的 Spark SQL,在 writing out(写出)Parquet schema 时,不区分 binary data(二进制数据)和 strings(字符串)。该 flag 告诉 Spark SQL 将 binary data(二进制数据)解释为 string(字符串)以提供与这些系统的兼容性。
spark.sql.parquet.int96AsTimestamptrue一些 Parquet-producing systems,特别是 Impala 和 Hive,将 Timestamp 存入INT96。该 flag 告诉 Spark SQL 将 INT96 数据解析为 timestamp 以提供与这些系统的兼容性。
spark.sql.parquet.compression.codecsnappy在编写 Parquet 文件时设置 compression codec(压缩编解码器)的使用。可接受的值包括:uncompressed,snappy,gzip,lzo。
spark.sql.parquet.filterPushdowntrue设置为 true 时启用 Parquet filter push-down optimization。
spark.sql.hive.convertMetastoreParquettrue当设置为 false 时,Spark SQL 将使用 Hive SerDe 作为 parquet tables,而不是内置的支持。
spark.sql.parquet.mergeSchemafalse当为 true 时,Parquet data source(Parquet 数据源)merges(合并)从所有 data files(数据文件)收集的 schemas,否则如果没有可用的 summary file,则从 summary file 或 random data file 中挑选 schema。
spark.sql.parquet.writeLegacyFormatfalse如果为true,则数据将以Spark 1.4及更早版本的方式写入。例如,十进制值将以Apache Parquet的固定长度字节数组格式写入,其他系统(如Apache Hive和Apache Impala)也使用该格式。如果为false,将使用Parquet中的较新格式。例如,小数将以基于int的格式写入。如果Parquet输出旨在用于不支持此较新格式的系统,请设置为true。

ORC Files

从Spark 2.3开始,Spark支持带有ORC文件的新ORC文件格式的矢量化ORC阅读器。为此,新添加了以下配置。

属性名默认值含义
spark.sql.orc.implnative可以选择native 和 hive。native表示在Apache ORC 1.4上构建ORC支持。hive表示Hive 1.2.1中的ORC库。
spark.sql.orc.enableVectorizedReadertrue这个是针对spark.sql.orc.impl属性为native 的。如果false,在native实现中使用新的非向量化ORC阅读器。

JSON Files

Spark SQL 可以 automatically infer(自动推断)JSON dataset 的 schema,并将其作为 Dataset[Row] 加载。这个 conversion(转换)可以在 Dataset[String] 上使用 SparkSession.read.json() 来完成,或 JSON 文件。

请注意,以 a json file 提供的文件不是典型的 JSON 文件。每行必须包含一个 separate(单独的),self-contained valid(独立的有效的)JSON 对象。有关更多信息,请参阅 JSON Lines text format, also called newline-delimited JSON

对于 regular multi-line JSON file(常规的多行 JSON 文件),将 multiLine 选项设置为 true。

// Primitive types (Int, String, etc) and Product types (case classes) encoders are
// supported by importing this when creating a Dataset.
import spark.implicits._

// 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
// a Dataset[String] storing one JSON object per string
val otherPeopleDataset = spark.createDataset(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleDataset)
otherPeople.show()
// +---------------+----+
// |        address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+

完整例子:examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

Hive Tables

Spark SQL 还支持读取和写入存储在 Apache Hive 中的数据。但是,由于 Hive 具有大量依赖关系,因此这些依赖关系不包含在默认 Spark 分发中。如果在类路径中找到 Hive 依赖项,Spark 将自动加载它们。请注意,这些 Hive 依赖关系也必须存在于所有工作节点上,因为它们将需要访问 Hive 序列化和反序列化库(SerDes),以访问存储在 Hive 中的数据。

通过将 hive-site.xml,core-site.xml(用于安全配置)和 hdfs-site.xml(用于 HDFS 配置)文件放在 conf/ 中来完成配置。

当使用 Hive 时,必须用 Hive 支持实例化 SparkSession,包括连接到持续的 Hive 转移,支持 Hive serdes 和 Hive 用户定义的功能。没有现有 Hive 部署的用户仍然可以启用 Hive 支持。当 hive-site.xml 未配置时,上下文会自动在当前目录中创建 metastore_db,并创建由 spark.sql.warehouse.dir 配置的目录,该目录默认为Spark应用程序当前目录中的 spark-warehouse 目录 开始了 请注意,自从2.0.0以来,hive-site.xml 中的 hive.metastore.warehouse.dir 属性已被弃用。而是使用 spark.sql.warehouse.dir 来指定仓库中数据库的默认位置。您可能需要向启动 Spark 应用程序的用户授予写权限。

import java.io.File

import org.apache.spark.sql.{Row, SaveMode, SparkSession}

case class Record(key: Int, value: String)

// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = new File("spark-warehouse").getAbsolutePath

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) USING hive")
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 DataFrames 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|
// ...

// Create a Hive managed Parquet table, with HQL syntax instead of the Spark SQL native syntax
// `USING hive`
sql("CREATE TABLE hive_records(key int, value string) STORED AS PARQUET")
// Save DataFrame to the Hive managed table
val df = spark.table("src")
df.write.mode(SaveMode.Overwrite).saveAsTable("hive_records")
// After insertion, the Hive managed table has data now
sql("SELECT * FROM hive_records").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// Prepare a Parquet data directory
val dataDir = "/tmp/parquet_data"
spark.range(10).write.parquet(dataDir)
// Create a Hive external Parquet table
sql(s"CREATE EXTERNAL TABLE hive_ints(key int) STORED AS PARQUET LOCATION '$dataDir'")
// The Hive external table should already have data
sql("SELECT * FROM hive_ints").show()
// +---+
// |key|
// +---+
// |  0|
// |  1|
// |  2|
// ...

// Turn on flag for Hive Dynamic Partitioning
spark.sqlContext.setConf("hive.exec.dynamic.partition", "true")
spark.sqlContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")
// Create a Hive partitioned table using DataFrame API
df.write.partitionBy("key").format("hive").saveAsTable("hive_part_tbl")
// Partitioned column `key` will be moved to the end of the schema.
sql("SELECT * FROM hive_part_tbl").show()
// +-------+---+
// |  value|key|
// +-------+---+
// |val_238|238|
// | val_86| 86|
// |val_311|311|
// ...

spark.stop()

完整的例子:examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala

指定 Hive 表的存储格式

创建 Hive 表时,需要定义如何 从/向 文件系统 read/write 数据,即 “输入格式” 和 “输出格式”。您还需要定义该表如何将数据反序列化为行,或将行序列化为数据,即 “serde”。以下选项可用于指定存储格式(“serde”, “input format”, “output format”),例如,CREATE TABLE src(id int) USING hive OPTIONS(fileFormat ‘parquet’)。默认情况下,我们将以纯文本形式读取表格文件。请注意,Hive 存储处理程序在创建表时不受支持,您可以使用 Hive 端的存储处理程序创建一个表,并使用 Spark SQL 来读取它。

Property NameMeaning
fileFormatfileFormat是一种存储格式规范的包,包括 “serde”,“input format” 和 “output format”。目前我们支持6个文件格式:‘sequencefile’,‘rcfile’,‘orc’,‘parquet’,‘textfile’和’avro’
inputFormat, outputFormat这两个选项将相应的 “InputFormat” 和 “OutputFormat” 类的名称指定为字符串文字,例如:org.apache.hadoop.hive.ql.io.orc.OrcInputFormat。这两个选项必须成对出现,如果您已经指定了 “fileFormat” 选项,则无法指定它们。
serde此选项指定 serde 类的名称。当指定 fileFormat 选项时,如果给定的 fileFormat 已经包含 serde 的信息,那么不要指定这个选项。目前的 “sequencefile”,“textfile” 和 “rcfile” 不包含 serde 信息,你可以使用这3个文件格式的这个选项。
fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim这些选项只能与 “textfile” 文件格式一起使用。它们定义如何将分隔的文件读入行。

使用 OPTIONS 定义的所有其他属性将被视为 Hive serde 属性。

与不同版本的 Hive Metastore 进行交互

Spark SQL 的 Hive 支持的最重要的部分之一是与 Hive metastore 进行交互,这使得 Spark SQL 能够访问 Hive 表的元数据。从 Spark 1.4.0 开始,使用 Spark SQL 的单一二进制构建可以使用下面所述的配置来查询不同版本的 Hive 转移。请注意,独立于用于与转移点通信的 Hive 版本,内部 Spark SQL 将针对 Hive 1.2.1 进行编译,并使用这些类进行内部执行(serdes,UDF,UDAF等)。
以下选项可用于配置用于检索元数据的 Hive 版本:

Property NameDefaultMeaning
spark.sql.hive.metastore.version1.2.1Hive metastore 版本。可用选项为 0.12.0 至 2.3.3。
spark.sql.hive.metastore.jarsbuiltin应该用于实例化HiveMetastoreClient的jar的位置。此属性可以是以下三个选项之一:1. builtin使用Hive 1.2.1,它在-Phive启用时与Spark程序集捆绑在一起。选择此选项时,spark.sql.hive.metastore.version必须1.2.1定义或不定义。2. mave使用从Maven存储库下载的指定版本的Hive jar。通常不建议将此配置用于生产部署。3. JVM 的标准格式的 classpath。该类路径必须包含所有 Hive 及其依赖项,包括正确版本的 Hadoop。这些jars只需要存在于 driver 程序中,但如果您正在运行在 yarn 集群模式,那么您必须确保它们与应用程序一起打包。
spark.sql.hive.metastore.sharedPrefixescom.mysql.jdbc,org.postgresql,com.microsoft.sqlserver,oracle.jdbc使用逗号分隔的类前缀列表,应使用在 Spark SQL 和特定版本的 Hive 之间共享的类加载器来加载。一个共享类的示例就是用来访问 Hive metastore 的 JDBC driver。其它需要共享的类,是需要与已经共享的类进行交互的。例如,log4j 使用的自定义 appender。
spark.sql.hive.metastore.barrierPrefixes(empty)一个逗号分隔的类前缀列表,应该明确地为 Spark SQL 正在通信的 Hive 的每个版本重新加载。例如,在通常将被共享的前缀中声明的 Hive UDF(即: org.apache.spark.*)。

JDBC To Other Databases(JDBC 连接其它数据库)

Spark SQL 还包括可以使用 JDBC 从其他数据库读取数据的数据源。此功能应优于使用 JdbcRDD。这是因为结果作为 DataFrame 返回,并且可以轻松地在 Spark SQL 中处理或与其他数据源连接。JDBC 数据源也更容易从 Java 或 Python 使用,因为它不需要用户提供 ClassTag。(请注意,这不同于 Spark SQL JDBC 服务器,允许其他应用程序使用 Spark SQL 运行查询)。

要开始使用,您需要在 Spark 类路径中包含特定数据库的 JDBC driver 程序。例如,要从 Spark Shell 连接到 postgres,您将运行以下命令:

bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar

可以使用 Data Sources API 将来自远程数据库的表作为 DataFrame 或 Spark SQL 临时视图进行加载。用户可以在数据源选项中指定 JDBC 连接属性。用户 和 密码通常作为登录数据源的连接属性提供。除了连接属性外,Spark 还支持以下不区分大小写的选项:

属性名称含义
url要连接的JDBC URL。源特定的连接属性可以在URL中指定。例如jdbc:jdbc:postgresql://localhost/test?user=fred&password=secret
dbtable应该读取的 JDBC 表。请注意,可以使用在SQL查询的 FROM 子句中有效的任何内容。例如,您可以使用括号中的子查询代替完整表。
query用于将数据读入Spark的查询。指定的查询将被括起来并在FROM子句中用作子查询。Spark还会为子查询子句分配别名。例如,spark将向JDBC Source发出以下形式的查询。SELECT FROM (<user_specified_query>) spark_gen_alias 使用此选项时,以下是一些限制。不允许同时指定dbtablequery选项。不允许同时指定querypartitionColumn选项。当需要指定partitionColumn选项时,可以使用dbtable选项指定子查询,并且可以使用作为dbtable的一部分提供的子查询别名来限定分区列。例spark.read.format(“jdbc”).option(“url”, jdbcUrl).option(“query”, “select c1, c2 from t1”).load()
driver用于连接到此 URL 的 JDBC driver 程序的类名
partitionColumn, lowerBound, upperBound如果指定了这些选项中的任何一个,则必须全部指定这些选项。另外,必须指定 numPartitions。他们描述如何从多个 worker 并行读取数据时将表给分区。partitionColumn 必须是有问题的表中的数字列。请注意,lowerBound 和 upperBound 仅用于决定分区的大小,而不是用于过滤表中的行。因此,表中的所有行将被分区并返回。此选项仅适用于读操作。
numPartitions在表读写中可以用于并行度的最大分区数。这也确定并发JDBC连接的最大数量。如果要写入的分区数超过此限制,则在写入之前通过调用 coalesce(numPartitions) 将其减少到此限制。
queryTimeout驱动程序等待Statement对象执行到指定秒数的秒数。零意味着没有限制。在写入路径中,此选项取决于JDBC驱动程序如何实现API setQueryTimeout,例如,h2 JDBC驱动程序检查每个查询的超时而不是整个JDBC批处理。它默认为0。
fetchsizeJDBC 抓取的大小,用于确定每次数据往返传递的行数。这有利于提升 JDBC driver 的性能,它们的默认值较小(例如:Oracle 是 10 行)。该选项仅适用于读取操作。
batchsizeJDBC 批处理的大小,用于确定每次数据往返传递的行数。这有利于提升 JDBC driver 的性能。该选项仅适用于写操作。默认值为 1000。
isolationLevel事务隔离级别,适用于当前连接。它可以是 NONE,READ_COMMITTED,READ_UNCOMMITTED,REPEATABLE_READ,或 SERIALIZABLE 之一,对应于 JDBC 连接对象定义的标准事务隔离级别,默认为 READ_UNCOMMITTED。此选项仅适用于写操作。请参考 java.sql.Connection 中的文档。
sessionInitStatement在向远程数据库打开每个数据库会话之后,在开始读取数据之前,此选项将执行自定义SQL语句(或PL / SQL块)。使用它来实现会话初始化代码。例:option(“sessionInitStatement”, “”“BEGIN execute immediate ‘alter session set “_serial_direct_read”=true’; END;”"")
truncate这是一个与 JDBC 相关的选项。启用 SaveMode.Overwrite 时,此选项会导致 Spark 截断现有表,而不是删除并重新创建。这可以更有效,并且防止表元数据(例如,索引)被移除。但是,在某些情况下,例如当新数据具有不同的模式时,它将无法工作。它默认为 false。此选项仅适用于写操作。
cascadeTruncate谨慎使用 级联截断 适用于写操作
createTableOptions这是一个与JDBC相关的选项。如果指定,此选项允许在创建表时设置特定于数据库的表和分区选项(例如:CREATE TABLE t (name string) ENGINE=InnoDB.)。此选项仅适用于写操作。
createTableColumnTypes创建表时要使用的数据库列数据类型,而不是默认值。数据类型信息的格式应与创建表列语法相同(例如:“name char(64),comments varchar(1024)”)。指定的类型应为有效的Spark SQL数据类型。此选项仅适用于写入。
customSchema用于从JDBC连接器读取数据的自定义架构。例如,“id DECIMAL(38, 0), name STRING”。您还可以指定部分字段,其他字段使用默认类型映射。例如,“id DECIMAL(38, 0)”。列名应与JDBC表的相应列名相同。用户可以指定Spark SQL的相应数据类型,而不是使用默认值。此选项仅适用于阅读。
pushDownPredicate用于启用或禁用谓词下推到JDBC数据源的选项。默认值为true,在这种情况下,Spark会尽可能地将过滤器下推到JDBC数据源。否则,如果设置为false,则不会将过滤器下推到JDBC数据源,因此所有过滤器都将由Spark处理。当Spark通过比JDBC数据源更快地执行谓词过滤时,谓词下推通常会被关闭。
// 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)
// Specifying the custom data types of the read schema
connectionProperties.put("customSchema", "id DECIMAL(38, 0), name STRING")
val jdbcDF3 = 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)

// Specifying create table column data types on write
jdbcDF.write
  .option("createTableColumnTypes", "name CHAR(64), comments VARCHAR(1024)")
  .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties)

完整例子examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala

Apache Avro Data Source Guide

部署

该spark-avro模块是外部的,默认情况下不包括在spark-submit或spark-shell。
与任何Spark应用程序一样,spark-submit用于启动您的应用程序。spark-avro_2.12 通过–packages添加依赖关系可以直接添加到spark-submit使用,例如,

./bin/spark-submit --packages org.apache.spark:spark-avro_2.12:2.4.3 ...

对于调试 也可通过park-shell --packages进行添加

./bin/spark-shell --packages org.apache.spark:spark-avro_2.12:2.4.3 ...

详细信息参照应用提交指南

加载和保存功能

要以Avro格式加载/保存数据,您需要将数据源选项指定format为avro(或org.apache.spark.sql.avro)。

val usersDF = spark.read.format("avro").load("examples/src/main/resources/users.avro")
usersDF.select("name", "favorite_color").write.format("avro").save("namesAndFavColors.avro")

更多操作

其他操作

Troubleshooting(故障排除)

  • JDBC driver 程序类必须对客户端会话和所有执行程序上的原始类加载器可见。这是因为 Java 的 DriverManager 类执行安全检查,导致它忽略原始类加载器不可见的所有 driver 程序,当打开连接时。一个方便的方法是修改所有工作节点上的compute_classpath.sh 以包含您的 driver 程序 JAR。
  • 一些数据库,例如 H2,将所有名称转换为大写。您需要使用大写字母来引用 Spark SQL 中的这些名称。
  • 用户可以在数据源选项中指定特定于供应商的JDBC连接属性以进行特殊处理。例如,spark.read.format(“jdbc”).option(“url”, oracleJdbcUrl).option(“oracle.jdbc.mapDateToTimestamp”, “false”)。oracle.jdbc.mapDateToTimestamp默认为true,用户通常需要禁用此标志以避免Oracle日期被解析为时间戳。

性能调优

对于某些工作负载,可以通过缓存内存中的数据或打开一些实验选项来提高性能。

在内存中缓存数据

Spark SQL 可以通过调用 spark.catalog.cacheTable(“tableName”) 或 dataFrame.cache() 来使用内存中的列格式来缓存表。然后,Spark SQL 将只扫描所需的列,并将自动调整压缩以最小化内存使用量和 GC 压力。您可以调用 spark.catalog.uncacheTable(“tableName”) 从内存中删除该表。

内存缓存的配置可以使用 SparkSession 上的 setConf 方法或使用 SQL 运行 SET key=value 命令来完成。

属性名称默认含义
spark.sql.inMemoryColumnarStorage.compressedtrue当设置为 true 时,Spark SQL 将根据数据的统计信息为每个列自动选择一个压缩编解码器。
spark.sql.inMemoryColumnarStorage.batchSize10000控制批量的缓存的大小。更大的批量大小可以提高内存利用率和压缩率,但是在缓存数据时会有 OOM 风险。

其他配置选项

以下选项也可用于调整查询执行的性能。这些选项可能会在将来的版本中被废弃,因为更多的优化是自动执行的。

属性名称默认值含义
spark.sql.files.maxPartitionBytes134217728 (128 MB)在读取文件时,将单个分区的最大字节数。
spark.sql.files.openCostInBytes4194304 (4 MB)按照字节数来衡量的打开文件的估计费用可以在同一时间进行扫描。将多个文件放入分区时使用。最好过度估计,那么具有小文件的分区将比具有较大文件的分区(首先计划的)更快。
spark.sql.broadcastTimeout300广播连接中的广播等待时间超时(秒)
spark.sql.autoBroadcastJoinThreshold10485760 (10 MB)配置执行连接时将广播给所有工作节点的表的最大大小(以字节为单位)。通过将此值设置为-1可以禁用广播。请注意,目前的统计信息仅支持 Hive Metastore 表,其中已运行命令 ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan。
spark.sql.shuffle.partitions200配置分区数 在使用joins 或者aggregations 产生shuffler的时候。

Broadcast Hint for SQL Queries

The BROADCAST hint guides Spark to broadcast each specified table when joining them with another table or view. When Spark deciding the join methods, the broadcast hash join (i.e., BHJ) is preferred, even if the statistics is above the configuration spark.sql.autoBroadcastJoinThreshold. When both sides of a join are specified, Spark broadcasts the one having the lower statistics. Note Spark does not guarantee BHJ is always chosen, since not all cases (e.g. full outer join) support BHJ. When the broadcast nested loop join is selected, we still respect the hint.

import org.apache.spark.sql.functions.broadcast
broadcast(spark.table("src")).join(spark.table("records"), "key").show()

分布式 SQL 引擎

Spark SQL 也可以充当使用其 JDBC/ODBC 或命令行界面的分布式查询引擎。在这种模式下,最终用户或应用程序可以直接与 Spark SQL 交互运行 SQL 查询,而不需要编写任何代码。

运行 Thrift JDBC/ODBC 服务器

这里实现的 Thrift JDBC/ODBC 服务器对应于 Hive 1.2.1 中的 HiveServer2。您可以使用 Spark 或 Hive 1.2.1 附带的直线脚本测试 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> \
  ...

or system properties:

./sbin/start-thriftserver.sh \
  --hiveconf hive.server2.thrift.port=<listening-port> \
  --hiveconf hive.server2.thrift.bind.host=<listening-host> \
  --master <master-uri>
  ...

现在,您可以使用 beeline 来测试 Thrift JDBC/ODBC 服务器:

./bin/beeline 

使用 beeline 方式连接到 JDBC/ODBC 服务器:

beeline> !connect jdbc:hive2://localhost:10000 

Beeline 将要求您输入用户名和密码。在非安全模式下,只需输入机器上的用户名和空白密码即可。对于安全模式,请按照 beeline 文档 中的说明进行操作。

配置Hive是通过将 hive-site.xml, core-site.xml 和 hdfs-site.xml 文件放在 conf/ 中完成的。

您也可以使用 Hive 附带的 beeline 脚本。

Thrift JDBC 服务器还支持通过 HTTP 传输发送 thrift RPC 消息。使用以下设置启用 HTTP 模式作为系统属性或在 conf/ 中的 hive-site.xml 文件中启用:

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> 

运行 Spark SQL CLI

Spark SQL CLI 是在本地模式下运行 Hive 转移服务并执行从命令行输入的查询的方便工具。请注意,Spark SQL CLI 不能与 Thrift JDBC 服务器通信。

要启动 Spark SQL CLI,请在 Spark 目录中运行以下命令:

./bin/spark-sql 

配置 Hive 是通过将 hive-site.xml,core-site.xml 和 hdfs-site.xml 文件放在 conf/ 中完成的。您可以运行 ./bin/spark-sql --help 获取所有可用选项的完整列表。

参考:

  1. https://spark.apache.org/docs/latest/sql-programming-guide.html
  2. https://www.cnblogs.com/BYRans/p/5057110.html
  3. https://blog.csdn.net/s127838498/article/details/84107849
  4. https://www.cnblogs.com/bigbigtree/p/5691206.html
  5. spark2.4.0 视频参考
  6. Parquet 使用
  7. spark 2.2.0 中文文档
  8. ORC FILE 文件结构

备注

  1. spark 仓库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值