前言
本篇文章来源于官方文档。
Spark SQL
通过 DataFrame
接口支持操作各种数据源。一个 DataFrame
能够通过使用关系转换和创建临时视图来操作数据。当你使用临时视图注册一个 DataFrame
时,你可以在这数据上运行 SQL
查询。
注意,本文中的完整代码和所需的资源文件如下
【官方案例】SQLDataSourceExample.scala
通用的读取、保存函数
默认的数据源是 parquet
,当然也可以在 spark.sql.source.default
中自己去配置。
【官方案例】
// 读取users.parquet 文件
val usersDF = spark.read
.load("examples/src/main/resources/users.parquet")
// 查询出2列内容,写入(保存)到新的 parquet 文件
usersDF.select("name", "favorite_color").write
.save("namesAndFavColors.parquet")
手动指定文件(数据源)类型
你可以手动自动将要使用的数据源以及传递给数据源的任何其他选项。数据源通过其全名指定(即org.apache.spark.sql.parquet
)。另外,内置的来源,可以用短的名称(json,
parquet,
jdbc,
orc,
libsvm,
csv,
text)。从任何数据源类型加载的 Datarame 都可以使用这个语法转换为其他类型。
【官方案例】
// 加载 json 文件
val peopleDF = spark.read.format("json")
.load("examples/src/main/resources/people.json")
// 查询两列并格式化为 parquet 类型,保存到 parquet 文件
peopleDF.select("name", "age").write.format("parquet")
.save("namesAndAges.parquet")
// 加载 csv 文件
val peopleDFCsv = spark.read.format("csv")
.option("sep", ";")
.option("inferSchema", "true")
.option("header", "true")
.load("examples/src/main/resources/people.csv")
在写操作时,可能会用到额外的 option
。例如,可以控制 orc
数据源的 Bloom 过滤器和字典编码。
usersDF.write.
format("orc")
.option("orc.bloom.filter.columns", "favorite_color")
.option("orc.dictionary.key.threshold", "1.0")
.save("users_with_options.orc")
直接在文件上运行 SQL
直接用 SQL 查询某个文件。
【官方案例】
val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
保存模式
保存操作可以采取保存模式,这种模式指定了如何去处理现有的数据(假如存在的话)。
另外,这些保存模式不使用任何锁,也不是原子操作。特殊地,在执行覆盖(采取了覆盖模式)时,将在写入新数据前删除旧的数据。
Scala / Java | 任何语言 | 含义 |
---|---|---|
SaveMode.ErrorIfExists (默认) |
"error" or "errorifexists" (默认) |
将DataFrame 保存到数据源时,如果已经存在数据,则将引发异常。 |
SaveMode.Append |
"append" |
将DataFrame 保存到数据源时,如果已经存在数据/表,则应该将DataFrame 的内容附加到现有数据中。 |
SaveMode.Overwrite |
"overwrite" |
覆盖 Schema 意味着将DataFrame 保存到数据源时,如果已经存在数据/表,则预期现有数据将被DataFrame 的内容覆盖。 |
SaveMode.Ignore |
"ignore" |
忽略 Schema 意味着在将DataFrame 保存到数据源时,如果已经存在数据,则预期保存操作将不保存DataFrame 的内容并且不更改现有数据。这类似于CREATE TABLE IF NOT EXISTS SQL中的。 |
保存到持久表
DataFrame
可以使用 saveAsTable
方法把数据作为持久表,保存到 Hive Metastore
。使用这个功能时,不需要现有的 Hive
部署。Spark
可以为你创建一个默认的本地 Hive
Metastore
(使用 Derby
数据库),你要是有兴趣,可以自己集成一 个 Mysql
作为元数据空间。
与 createOrReplaceTempView
方法不同的是, saveAsTable
将具体化 DataFrame
的内容并在 Hive
元空间(metastore
)中创建一个指向数据的指针。即使你重新启动 Spark
程序,持久表仍然会存在,只要你保持与同一个 metastore
的连接即可。通过对具有表名的 SparkSession
调用 table
方法,可以创建持久表的 DataFrame
。
对于基于文件的数据源,例如文本, parquet,json
等,你可以通过 path
选项指定自定义表路径。比如 df.write.option("path", "/some/path").saveAsTable("t")
。删除表后,自定义表路径不会删除,并且表中的数据仍然会存在。如果未指定自定义表路径, Spark
会将数据写入仓库目录下的默认表路径。删除表时,默认表路径也将被删除。
从 Spark 2.1
版本开始,持久数据源表再 Hive
元空间中存储了按分区的元数据,这样做的好处是:
- 由于元空间只能返回查询所需的分区,因此不再需要在第一个查询上发表的所有分区。
Hive DDLs
,比如ALTER TABLE PARTITION ... SET LOCATION
现在可用于使用Datasource API
创建的表。
注意一点,在创建外部数据源表(带有 path
选项的表)时,默认情况下不会收集分区信息,要同步元空间中的分区信息,可以调用 MSCK REPAIR TABLE
。
存储桶、排序和分区
对于基于文件的数据源,也可以对输出进行存储和分类或分区。存储桶和排序仅仅适用于持久表。
【官方案例】
// 分桶
peopleDF.write
.bucketBy(42, "name")
.sortBy("age")
.saveAsTable("people_bucketed")
// 分区
usersDF.write
.partitionBy("favorite_color")
.format("parquet")
.save("namesPartByColor.parquet")
对单个表使用分区和存储桶
usersDF.write
.partitionBy("favorite_color")
.bucketBy(42, "name")
.saveAsTable("users_partitioned_bucketed")
partitionBy
按照 Partition Discovery这一章节中的描述创建目录结构。因此,它对具有高基数的列的适用性有限。相反,bucketBy
将数据分布在固定数量的存储桶中,并且当许多唯一值不受限制时可以使用。
Parquet 文件
Parquet
是许多其他数据处理系统支持列格式。 Spark SQL
提供对 Parquet
文件读写的支持,该文件会自动保留原始数据结构。编写 Parquet
文件时,出于兼容性原因,所有列都将自动转换为可为空。
以编程的方式加载数据
【官方案例】
import spark.implicits._
// 读取 json 文件
val peopleDF = spark.read
.json("examples/src/main/resources/people.json")
// 将 DataFrame 保存为 parquet 文件,用以维持结构信息
peopleDF.write.parquet("people.parquet")
// Parquet 文件是自描述的,因此数据结构会被保留下来
// 加载 parquet 文件的返回值也是 DataFrame 类型
val parquetFileDF = spark.read.parquet("people.parquet")
// Parquet 文件也能通过创建临时视图,然后在 SQL 语句中使用
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 Discovery (分区发现)
表分区是 Hive 等系统中常用的优化方法。在分区表中,数据通常存储在不同的目录中,分区列值编码在每个分区目录的路径中。所有内置文件源(包括Text、CSV、JSON、ORC、Parquet
)都可以自动发现和推断分区信息。
例如,我们可以使用以下目录结构将之前使用的所有填充数据存储到一个分区表中,该目录结构具有两个额外的列 gender
和 country
作为分区列:
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
。禁用类型推断时,字符串类型将用于分区列。
从 Spark 1.6.0 版本开始,默认情况下,分区发现仅仅在给定路径下查找分区。对于上面的示例,如果用户传递 path/to/table/gender=male
给 SparkSession.read.parquet
或 SparkSession.read.read.load
,gender
则不会被视为分区列。如果用户需要指定分区发现应开始的基本路径,则可以 basePath
在数据源选项中进行设置。例如,当 path/to/table/gender=male
是数据的路径,然后用户设置 basePath
为 path/to/table/
, gender
将是一个分区列。
Schema 合并
与 Protocol Buffer
、Avro
和 Thrift
一样, Parquet
也支持 Schema
演化。用户可以从一个简单的架构开始,然后根据需要逐渐向架构中添加更多列。这样,用户可能最终得到具有不同但相互兼容的架构的多个 Parquet
文件。现在, Parquet
数据源能够自动检测到这种情况并合并所有这些文件的 Schema
。
由于架构合并是一项相对昂贵的操作,并且大多数情况下是不必需的。因此在默认情况下,从 Spark 1.5.0
版本开始将其关闭了。可以通过下面的方式启动:
- 将数据源选项设置
mergeSchema
为true
在读取Parquet
文件; - 将全局 SQL 选项设置
spark.sql.parquet.mergeSchema
为true
;
【官方案例】
// This is used to implicitly convert an RDD to a DataFrame.
import spark.implicits._
// 创建一个简单的 DataFrame ,存储到一个分区目录中
val squaresDF = spark.sparkContext
.makeRDD(1 to 5)
.map(i => (i, i * i))
.toDF("value", "square")
squaresDF.write.parquet("data/test_table/key=1")
// 在一个新的分区目录中创建另一个 DataFrame,增加一个新列并删除一个存在的列
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")
// 读取分区表
val mergedDF = spark.read
.option("mergeSchema", "true")
.parquet("data/test_table")
mergedDF.printSchema()
// 最终的 Schema 由 Parquet 文件中的所有 3 列组成
// 分区列出现在现在分区目录路径中
// root
// |-- value: int (nullable = true)
// |-- square: int (nullable = true)
// |-- cube: int (nullable = true)
// |-- key: int (nullable = true)
Hive Metastore Parquet 表转换
在读取和写入 Hive metastore Parquet
表时, Spark SQL
将尝试使用其自己的 Parquet
支持而不是 Hive SerDe
以获得更好的性能。这种行为由 spark.sql.hive.convertMetastoreParquet
配置控制,并且默认为启动状态。
Hive/Parquet 架构协调
从表 Schema
处理的角度看,Hive
和 Parquet
之间有两个关键区别:
Hive
不区分大小写;而Parquet
则不区分大小写。Hive
认为所有列都可为空;而Parquet
中的可为空性很重要。
由于这个原因,在将 Hive Metastore Parquet
表转换为 Spark SQL Parquet
表时, 我们必须使 Hive Metastore Schema
与 Parquet Schema
协调一致。对账规则是:
- 在两个
Schema
中具有 相同名称的字段必须具有相同的数据类型,而不考虑可为空性。协调字段应具有Parquet
端的数据类型,以便遵守可空性。 - 协调的架构完全包含在
Parquet Schema
中的所有字段都将被放入对帐Schema
中;仅在Hive Metastore Schema
中出现的所有字段都将添加为已对帐Schema
中的可为空字段。
元数据刷新
Spark SQL
缓存 Parquet
元数据以获得更好的性能。启用 Hive metastore Parquet
表转换后,这些转换表的元数据也会被缓存。如果这些表是通过 Hive
或其他外部工具更新的,则需要手动刷新它们以确保元数据一致。
【官方案例】
// spark is an existing SparkSession
spark.catalog.refreshTable("my_table")
配置
可以使用 SparkSession
的setConf
方法或使用 SET KEY=VALUE
使用 SQL
运行命令来完成对 Parquet
的配置。
属性名称 | 默认 | 含义 |
---|---|---|
spark.sql.parquet.binaryAsString |
false | 编写Parquet Schema 时,其他一些Parquet产生系统,尤其是Impala,Hive和旧版本的Spark SQL,不会区分二进制数据和字符串。该标志告诉Spark SQL将二进制数据解释为字符串,以提供与这些系统的兼容性。 |
spark.sql.parquet.int96AsTimestamp |
true | 一些Parquet生产系统,尤其是Impala和Hive,将时间戳存储到INT96中。此标志告诉Spark SQL将INT96数据解释为时间戳,以提供与这些系统的兼容性。 |
spark.sql.parquet.compression.codec |
snappy | 设置编写Parquet文件时使用的压缩编解码器。如果在表特定的选项/属性中指定了“压缩”或“ parquet.compression”,则优先级将为“压缩”,“ parquet.compression”,“ spark.sql.parquet.compression.codec”。可接受的值包括:none,未压缩,snappy,gzip,lzo,brotli,lz4,zstd。请注意,在Hadoop 2.9.0之前,zstd 要求安装ZStandardCodec ,brotli 要求安装BrotliCodec 。 |
spark.sql.parquet.filterPushdown |
true | 设置为true时启用Parquet过滤器下推优化。 |
spark.sql.hive.convertMetastoreParquet |
true | 设置为false时,Spark SQL将使用Hive SerDe用于镶木表,而不是内置支持。 |
spark.sql.parquet.mergeSchema |
false | 如果为true,则Parquet数据源将合并从所有数据文件收集的架构,否则从摘要文件或随机数据文件(如果没有摘要文件可用)中选取该架构。 |
spark.sql.parquet.writeLegacyFormat |
false | 如果为true,将以Spark 1.4及更早版本的方式写入数据。例如,十进制值将以Apache Parquet的固定长度字节数组格式编写,其他系统(例如Apache Hive和Apache Impala)将使用该格式。如果为false,将使用Parquet中较新的格式。例如,小数将以基于int的格式编写。如果Parquet输出打算用于不支持这种较新格式的系统,请设置为true。 |
ORC 文件
从 Spark 2.3 版本开始, Spark 支持矢量化 ORC 读取器,该读取器具有针对 ORC 文件的新 ORC 文件格式。
为此,新添加了以下配置。 当spark.sql.orc.impl
设置为native
并且spark.sql.orc.enableVectorizedReader
设置为true
时,矢量化读取器用于本机ORC表(例如,使用子句USING ORC
创建的表)。 对于Hive ORC Serde
表(例如,使用子句USING HIVE OPTIONS(fileFormat'ORC')
创建的表),当spark.sql.hive.convertMetastoreOrc
也设置为true
时,将使用矢量化读取器。
属性名称 | 默认 | 含义 |
---|---|---|
spark.sql.orc.impl |
native |
ORC实现的名称。 它可以是本地的,也可以是Hive 的。 native表示基于Apache ORC 1.4构建的本机ORC支持。 “ hive”是指Hive 1.2.1中的ORC库。 |
spark.sql.orc.enableVectorizedReader |
true |
在本机实现中启用向量化orc解码。 如果为false,则在本机实现中使用新的非矢量化ORC读取器。 对于蜂巢的实现,这将被忽略。 |
JSON 文件
Spark SQL 可以自动推断 JSON 数据集的架构并将其加载为 DataSet[ROW]
。这种转换可以利用SparkSession.read.json()
在任一个DataSet[String]
,或JSON
文件来完成。
注意,以 JSON
文件形式提供的文件不是典型的 JSON
文件。每行必须包含一个单独的,自包含的有效 JSON
对象。 查看更多, please see JSON Lines text format, also called newline-delimited JSON.
对于常规的多行 JSON
文件,将 ,multiLine
选项设置为 true
。
【官方案例】
// 基本类型(Int/String等)和 Product 类型(样例类) 编码器是通过创建 Dataset 时导入此支持。
import spark.implicits._
// 通过路径 path 指向 JSON Dataset
// 路径可以是单个文本文件,也可以是存储文本文件的目录
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)
// 可以使用 printSchema() 打印 Schema
peopleDF.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)
// 用 DataFrame 创建临时视图
peopleDF.createOrReplaceTempView("people")
// SQL 语句可以使用 Spark 提供的 SQL 方法运行
val teenagerNamesDF = spark
.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")
teenagerNamesDF.show()
// +------+
// | name|
// +------+
// |Justin|
// +------+
// 或者,可以为由 Dataset[String] 表示的 JSON 数据集 创建一个 DataFrame,每个字符串存储一个 JSON 对象。
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|
// +---------------+----+
Hive 表
Spark SQL
支持读、写存储在 Apache Hive
中的数据。但是,由于 Hive
具有大量依赖关系,因此默认的 Spark
分发中不包含这些依赖关系。如果可以在类路径上找到 Hive
的依赖项, Spark
将自动加载它们。请注意,这些 Hive
依赖项也必须存在于所有工作节点上,因为它们为了访问存储在 Hive 中的数据,需要 访问 Hive 序列化和反序列化库(SerDes
)。
通过将您的hive-site.xml,core-site.xml
(用于安全配置)和hdfs-site.xml
(用于HDFS配置)文件放置在conf /
中,可以完成Hive的配置。
使用 Hive 时,必须使用 Hive 支持实例化 SparkSession
,包括与持久性 Hive 元存储库的连接,对 Hive SerDes
的支持以及Hive 用户定义的功能。没有现有 Hive 部署的用户仍可以启用 Hive 支持。如果没有 hive-site.xml
配置,则 context
会自动在当前目录中创建 metastore_db
并创建一个由 spark.sql.warehouse.dir
配置的目录 spark-warehouse
,该目录默认为启动 Spark 应用程序的当前目录中的目录。请注意,自Spark 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 指向托管数据库和表的默认位置 spark-warehouse
val warehouseLocation = new File("spark-warehouse").getAbsolutePath
// 将默认位置作为配置
val spark = SparkSession
.builder()
.appName("Spark Hive Example")
.config("spark.sql.warehouse.dir", warehouseLocation)
// 支持 Hive
.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")
// 查询 src
sql("SELECT * FROM src").show()
// +---+-------+
// |key| value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...
// 聚合查询
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// | 500 |
// +--------+
// SQL 查询的结果本身就是 DataFrames ,并且支持所有普通的函数
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")
// DataFrames 中的项属于 Row 类型,这允许你按序号访问每一列
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|
// ...
// 在 SparkSession 中创建临时视图
val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")
// 查询可以将 DataFrame 数据与存储在 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|
// ...
// 创建一个 Hive 管理的 Parquet 表,使用 HQL 语法而不是 Spark SQL 本机语法
// 使用 hive
sql("CREATE TABLE hive_records(key int, value string) STORED AS PARQUET")
// 将 DataFrame 保存到 Hive 管理的表
val df = spark.table("src")
// 设置保存模式为覆盖
df.write.mode(SaveMode.Overwrite).saveAsTable("hive_records")
// 插入数据后,查看一下数据
sql("SELECT * FROM hive_records").show()
// +---+-------+
// |key| value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...
// 准备 Parquet 数据目录
val dataDir = "/tmp/parquet_data"
spark.range(10).write.parquet(dataDir)
// 创建 Hive 外部 Parquet 表
sql(s"CREATE EXTERNAL TABLE hive_bigints(id bigint) STORED AS PARQUET LOCATION '$dataDir'")
// 查看Hive 外部表中的数据
sql("SELECT * FROM hive_bigints").show()
// +---+
// | id|
// +---+
// | 0|
// | 1|
// | 2|
// ... 当 Spark 并行处理分区时,顺序可能不同
// 打开 Hive 动态分区的标志
spark.sqlContext.setConf("hive.exec.dynamic.partition", "true")
spark.sqlContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")
// 使用 DataFrame API 创建一个 Hive 分区表
df.write.partitionBy("key").format("hive").saveAsTable("hive_part_tbl")
// 分区列 key 将移动到 Schema 的末尾
sql("SELECT * FROM hive_part_tbl").show()
// +-------+---+
// | value|key|
// +-------+---+
// |val_238|238|
// | val_86| 86|
// |val_311|311|
// ...
spark.stop()
指定 Hive 表的存储格式
创建 Hive 表时,需要定义该表如何重文件系统读、写数据,即输入格式和输出格式。另外,还需要定义该表应如何将数据反序列化为行,或将行序列化为数据,即 serde
。以下选项可用于指定存储格式(serde、input format、output format
),例如 CREATE TABLE src(id int) USING hive OPTIONS(fileFormat 'parquet')
。默认情况下,我们将以纯文本形式读取表文件。请注意,创建表时尚不支持 Hive 存储处理程序,您可以在 Hive 端使用存储处理程序创建表,并使用 Spark SQL 读取表。
属性名称 | 含义 |
---|---|
fileFormat |
fileFormat 是一种存储格式规范的软件包,其中包括“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种fileFormats 一起使用。 |
fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim |
这些选项只能与“textfile” fileFormat一起使用。它们定义了如何将定界文件读取为行。 |
用定义的所有其他属性 OPTIONS
将被视为 Hive serde
属性。
与 Hive Metastore 的不同版本进行交互
与 Hive metastore 的交互是 Spark SQL 对 Hive 的最重要的支持之一,它使 Spark SQL 能够访问 Hive 表的元数据。从 Spark 1.4.0 开始,使用以下描述的配置,可以使用 Spark SQL 的单个二进制版本来查询 Hive 元空间的不同版本。
注意,与用于与 metatore 进行通信的 Hive 版本无关,Spark SQL 在内部将针对 Hive 1.2.1 进行编译,并将这些类用于内部执行(serdes、UDF、UDAF等)。
以下选项可用于配置用于检索元数据的 Hive 版本:
属性名称 | 默认 | 含义 |
---|---|---|
spark.sql.hive.metastore.version |
1.2.1 |
Hive Metastore的版本。可用的选项是0.12.0 通过2.3.3 。 |
spark.sql.hive.metastore.jars |
builtin |
用于实例化HiveMetastoreClient 的jar的位置。此属性可以是以下三个选项之一: 1. builtin 使用Hive 1.2.1,该模块在-Phive 启用时与Spark组件捆绑在一起。选择此选项时,spark.sql.hive.metastore.version 必须1.2.1 定义或不定义。 2. maven 使用从Maven存储库下载的指定版本的Hive jar。通常不建议将此配置用于生产部署。 3. JVM的标准格式的类路径。该类路径必须包括所有Hive及其依赖项,包括正确的Hadoop版本。这些罐子只需要存在于驱动程序中,但是如果您以纱线簇 Schema 运行,则必须确保将它们与您的应用程序打包在一起。 |
spark.sql.hive.metastore.sharedPrefixes |
com.mysql.jdbc, org.postgresql, com.microsoft.sqlserver, oracle.jdbc |
以逗号分隔的类前缀列表,应使用在Spark SQL和特定版本的Hive之间共享的类加载器加载。应该共享的类的一个示例是与元存储(metastore)区进行对话所需的JDBC驱动程序。其他需要共享的类是与已经共享的类进行交互的类。例如,log4j使用的自定义追加程序。 |
spark.sql.hive.metastore.barrierPrefixes |
(empty) |
以逗号分隔的类前缀列表,应为Spark SQL与之通信的每个Hive版本显式重新加载。例如,在通常将被共享的前缀中声明的Hive UDF(即org.apache.spark.* )。 |
JDBC 到其他数据库
Spark SQL还包括一个数据源,该数据源可以使用JDBC从其他数据库读取数据。与使用JdbcRDD相比,应优先使用此功能。这是因为结果以DataFrame的形式返回,并且可以轻松地在Spark SQL中进行处理或与其他数据源合并。JDBC数据源也更易于从Java或Python使用,因为它不需要用户提供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
例如,要从Spark Shell连接到MySQL数据库,您可以运行以下命令:
bin/spark-shell --driver-class-path ../examples/jars/mysql-connector-java-5.1.47-bin.jar --jars ../examples/jars/mysql-connector-java-5.1.47-bin.jar
用户可以在数据源选项中指定JDBC连接属性。 user
和password
通常用于登录到数据源提供为连接属性。除了连接属性,Spark还支持以下不区分大小写的选项:
属性名称 | 含义 |
---|---|
url |
要连接的JDBC URL。特定于源的连接属性可以在URL中指定。例如,jdbc:postgresql://localhost/test?user=fred&password=secret |
dbtable |
应该从中读取或写入的JDBC表。请注意,在读取路径中使用它时,可以使用在FROM SQL查询子句中有效的任何东西。例如,除了完整表之外,您还可以在括号中使用子查询。不允许同时指定dbtable和query选项。 |
query |
用于将数据读入Spark的查询。指定的查询将加括号,并在FROM 子句中用作子查询。Spark还将为子查询子句分配一个别名。例如,spark将向JDBC源发出以下形式的查询。 SELECT FROM () spark_gen_alias 以下是使用此选项时的一些限制。 1. 不允许同时指定dbtable和query选项。 2. 不允许同时指定query 和partitionColumn 选项。当需要指定partitionColumn 选项时,可以使用dbtable 选项来指定子查询,而分区列可以使用dbtable 中提供的子查询别名来限定。 例: spark.read.format("jdbc") .option("url", jdbcUrl) .option("query", "select c1, c2 from t1") .load() |
driver |
用于连接到该URL的JDBC驱动程序的类名。 |
partitionColumn, lowerBound, upperBound |
如果指定了这些选项,则必须全部指定。另外, numPartitions 必须指定。他们描述了从多个工作程序并行读取时如何对表进行分区。 partitionColumn 必须是相关表格中的数字,日期或时间戳列。请注意,lowerBound 和upperBound 仅用于确定分区的步幅,而不是用于过滤表中的行。因此,表中的所有行都将被分区并返回。此选项仅适用于阅读。 |
numPartitions |
表读写中可用于并行处理的最大分区数。这也确定了并发JDBC连接的最大数量。如果要写入的分区数超过此限制,我们可以通过coalesce(numPartitions) 在写入之前进行调用将其降低到此限制。 |
queryTimeout |
驱动程序将等待Statement对象执行到给定秒数的秒数。零表示没有限制。在写路径中,此选项取决于JDBC驱动程序如何实现API setQueryTimeout ,例如,h2 JDBC驱动程序检查每个查询的超时而不是整个JDBC批处理。默认为0 。 |
fetchsize |
JDBC提取大小,该大小确定每次往返要提取多少行。这可以帮助提高JDBC驱动程序的性能,该驱动程序默认为低获取大小(例如,具有10行的Oracle)。此选项仅适用于阅读。 |
batchsize |
JDBC批处理大小,它决定每次往返插入多少行。这可以帮助提高JDBC驱动程序的性能。此选项仅适用于写作。默认为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 启用时,该选项的原因星火截断,而不是删除和重建其现有的表。这可以更有效,并防止删除表元数据(例如索引)。但是,在某些情况下(例如,新数据具有不同的架构时),它将不起作用。默认为false 。此选项仅适用于写作。 |
cascadeTruncate |
这是与JDBC编写器相关的选项。如果由JDBC数据库(当前为PostgreSQL和Oracle)启用并支持,则此选项允许执行a TRUNCATE TABLE t CASCADE (在PostgreSQL的情况下,TRUNCATE TABLE ONLY t CASCADE 执行a可以防止无意中截断后代表)。这将影响其他表,因此应谨慎使用。此选项仅适用于写作。它默认为isCascadeTruncate 每个JDBCDialect中指定的有关JDBC数据库的默认级联截断行为。 |
createTableOptions |
这是与JDBC编写器相关的选项。如果指定,则此选项允许在创建表(例如CREATE TABLE t (name string) ENGINE=InnoDB. )时设置特定于数据库的表和分区选项。此选项仅适用于写作。 |
createTableColumnTypes |
创建表时要使用的数据库列数据类型,而不是缺省值。数据类型信息应以与CREATE TABLE列语法相同的格式指定(例如:"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数据源执行谓词筛选的速度快时,通常会关闭谓词下推。 |
【官方案例】连接 mysql 读、写数据
// 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)
Spark 读写 Hbase(附加:不属于 Spark SQL 范围)
Apache Avro 数据源指南
本节暂时空着,有空再添加!
故障排除
-
JDBC驱动程序类在客户端会话和所有执行程序上必须对原始类加载器可见。这是因为Java的DriverManager类进行了安全检查,结果导致它忽略了当打开连接时原始类加载器不可见的所有驱动程序。一种方便的方法是修改所有工作程序节点上的compute_classpath.sh以包括驱动程序JAR。
-
某些数据库(例如H2)会将所有名称都转换为大写。您需要使用大写字母在Spark SQL中引用这些名称。
-
用户可以在数据源选项中指定特定于供应商的JDBC连接属性,以进行特殊处理。例如,
spark.read.format("jdbc").option("url", oracleJdbcUrl).option("oracle.jdbc.mapDateToTimestamp", "false")
。oracle.jdbc.mapDateToTimestamp
默认为true,用户通常需要禁用此标志,以避免将Oracle日期解析为时间戳。
致谢
首先,是官方文档!
然后是谷歌翻译!(滑稽脸~)
还有就是文中涉及到的所有的博客、文章等作者!
若有侵权,请留言!