Spark SQL的DataFrame接口支持多种数据源的操作。可以使用关系转换进行操作,也可以被注册为临时视图。将DataFrame注册为临时视图,即可以通过SQL进行数据查询。
Spark SQL的默认数据源格式为Parquet文件格式,修改配置项spark.sql.sources.default即可更改默认的数据源格式。
操作默认数据源
//load用于read读取文件时加载文件中的数据
val usersDF = spark.read.load("../examples/src/main/resources/users.parquet")
//save用于write写文件时存储写出的数据,未指定具体保存路径的情况下,则文件保存在bin目录下
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
手动指定选项
当数据源格式不是默认的Parquet格式的文件时,需要手动指定将要使用的数据源格式,数据源格式需要指定全名(例如org.apache.spark.sql.parquet),但内置的来源,则使用短名称(json, parquet, jdbc, orc, libsvm, csv, text)来指定数据的格式。从任何数据源类型加载的DataFrame都可以使用此语法转换为其他类型。
可以使用SparkSession提供的read.load方法加载数据,使用write.save方法保存数据。
1.1 加载json文件:
val peopleDF = spark.read.format("json").load("../examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
1.2 加载csv文件:
val peopleDFCsv = spark.read.format("csv").option("sep", ";").option("inferSchema", "true").option("header", "true").load("../examples/src/main/resources/people.csv")
peopleDFCsv.select("name", "age").write.format("parquet").save("namesAndAges2.parquet")
在写操作期间还会使用额外的选项。例如:可以控制ORC数据源的Bloom过滤器和字典编码。
//创建bloom过滤器,favorite_color并为name和使用字典编码favorite_color。
usersDF.write.format("orc").option("orc.bloom.filter.columns", "favorite_color").option("orc.dictionary.key.threshold", "1.0").save("users_with_options.orc")
除了使用读取API将文件加载到DataFrame中并进行查询之外,还可以直接在文件上运行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” | 将DataFrame保存到数据源时,如果已经存在数据/表,则预期现有数据将被DataFrame的内容覆盖。 |
SaveMode.Ignore | “ignore” | 将DataFrame保存到数据源时,如果已经存在数据,则预期保存操作将不保存DataFrame的内容并且不更改现有数据。这类似于CREATE TABLE IF NOT EXISTSSQL中的。 |
与createOrReplaceTempView命令不同,saveAsTable将具体化DataFrame的内容,并在Hive元存储中创建一个指向数据的指针。即使重新启动Spark程序,持久表仍将存在,只要保持与同一metastore的连接即可。可以使用表名在SparkSession上调用方法来创建持久表的DataFrame 。
对于文本,Parquet,json等基于文件的数据源,保存时可以通过path选项指定自定义表路径,例如df.write.option(“path”, “/some/path”).saveAsTable(“t”)。(存储在指定路径下的文件格式为parquet)。删除表后,自定义表路径将不会删除,且表数据仍然存在。如果未指定自定义表路径,Spark会将数据写入仓库目录下的默认表路径。删除表时,默认表路径也将被删除。
从Spark 2.1开始,持久数据源表在Hive元存储中存储了按分区的元数据。这带来了几个好处:
1)由于元存储只能返回查询的必要分区,因此不再需要在第一个查询中将所有分区发现到表中。
2)Hive DDL,例如ALTER TABLE PARTITION … SET LOCATION现在可用于使用Datasource API创建的表。
需要注意的是,在创建外部数据源表(带有path选项的表)时,默认情况下不会收集分区信息。要同步元存储中的分区信息,可以调用MSCK REPAIR 表名,例:MSCK REPAIR people。
peopleDF.write.bucketBy(42, "name").sortBy("age").saveAsTable("people_bucketed")
当使用DataSet的API时,对于save和saveastable,都可以使用分区。
//会在HDFS上的当前用户路径下产生一个文件夹namesPartByColor.parquet,里面包含了两个分区文件夹
usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")
//会在/user/hive/warehouse/namespartbycolor产生一个分区表。
userDF.write.partitionBy("favorite_color").format("parquet").saveAsTable("namesPartByColor")
可以对单个表同时使用分区和分桶:
usersDF.write.partitionBy("favorite_color").bucketBy(42, "name").saveAsTable("users_partitioned_bucketed")
partitionBy按照以下二.2节“分区自动识别”部分中的描述创建目录结构。因此,它对具有高基数的列的适用性有限。相反, bucketBy将数据分布在固定数量的存储桶中,并且当许多唯一值不受限制时可以使用。
Parquet是一种列存储格式的文件,支持许多的数据处理系统。Spark SQL提供对Parquet文件的读写支持,该文件会自动保留原始数据的结构。编写Parquet文件时,出于兼容性原因,所有列都将自动转换为可为null的类型。
// 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|
// +------------+
分区自动识别
表分区是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)
请注意,分区列的数据类型是自动推断的。目前仅支持numeric, date, timestamp 和String类型。有时,用户可能不希望自动推断分区列的数据类型。对于这些用例,可以配置自动类型推断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将是一个分区列。
模式合并
与Protocol Buffer、Avro和Thrift一样,Parquet也支持模式演化。用户可以从一个简单的架构开始,然后根据需要逐渐向架构中添加更多列。这样,用户可能最终得到具有不同但相互兼容的架构的多个Parquet文件。现在,Parquet数据源能够自动检测到这种情况并合并所有这些文件的模式。
由于架构合并是一项相对昂贵的操作,并且在大多数情况下不是必需的,因此默认情况下,我们从1.5.0开始将其关闭。可以通过以下方式启用:
1). 在读取Parquet文件时,将数据源选项设置mergeSchema为true(如下例所示)。
2). 将全局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()
// 最终的模式由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配置控制 ,且默认情况下处于启用状态。
4.1 Hive/Parquet架构协调
从表模式处理的角度来看,Hive和Parquet之间有两个关键区别。
4.1.1 Hive不区分大小写,而Parquet则区分大小写
4.1.2 Hive认为所有列都可为空,而Parquet中字段的的可为空性(nullability)很重要
由于这个原因,在将Hive Metastore Parquet表转换为Spark SQL Parquet表时,我们必须使Hive Metastore模式与Parquet模式协调一致。协调规则为:
1). 在两个模式中具有相同名称的字段必须具有相同的数据类型,而不考虑可为空性。协调字段应具有Parquet端的数据类型,以便遵守可空性。
2). 需要被协调的架构完全包含在Hive Metastore架构中定义的那些字段(即,如果一些字段仅仅在Hive Metastore的schema中定义,那么这个schema就是需要被协调一致的schema)
a. 任何仅出现在Parquet模式中的所有字段都将被放入已协调一致的模式中。
b. 任何仅出现在Hive Metastore模式中的所有字段都将在已协调一致的模式中添加为可空字段。
4.2 元数据刷新
Spark SQL缓存Parquet元数据以获得更好的性能。启用Hive metastore Parquet表转换后,这些转换表的元数据也会被缓存。如果这些表是通过Hive或其他外部工具更新的,则需要手动刷新它们以确保元数据一致。
// spark is an existing SparkSession
spark.catalog.refreshTable("my_table")
可以使用SparkSession中的setConf方法或使用SQL命令行运行命令SET key=value来完成Parquet的配置。
Property | Default | Meaning |
---|---|---|
spark.sql.parquet.binaryAsString | false | 编写Parquet模式时,其他一些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用于Parquet表,而不是内置支持。 |
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。 |
从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时,矢量化读取器才可以被使用。
Property | Default | Meaning |
---|---|---|
spark.sql.orc.impl | hive | ORC实现的名称。可以是native和hive之一。native表示基于Apache ORC 1.4构建的本机ORC支持。“ hive”是指Hive 1.2.1中的ORC库。 |
spark.sql.orc.enableVectorizedReader | true | 在native实现中启用向量化ORC解码。如果为false,则在native实现中使用新的非矢量化ORC读取器。由于是hive实现,这将被忽略。 |
Spark SQL可以自动推断JSON数据集的架构,并将其作为Dataset[Row]类型加载。在任一Dataset[String]或JSON文件上使用SparkSession.read.json(),也可以实现这种转换。
请注意,以json文件形式提供的文件不是典型的JSON文件。每行必须包含一个单独的,自包含的有效JSON对象。有关更多信息,请参见 JSON Lines文本格式,也称为newline分隔的JSON。
对于常规的多行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|
// +---------------+----+
Spark SQL还支持读写存储在Apache Hive中的数据。但是,由于Hive具有大量依赖关系,因此默认的Spark分发中不包含这些依赖关系。如果可以在类路径上找到Hive依赖项,Spark将自动加载它们。
请注意,这些Hive依赖项也必须存在于所有工作节点(worker nodes)上,因为它们需要访问Hive序列化和反序列化库(SerDes)才能访问存储在Hive中的数据。
通过将hive-site.xml,core-site.xml(对于安全性配置)和hdfs-site.xml(对于HDFS配置)文件放置在Hive conf/中来配置。
使用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 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|
// ...
// 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|
// ...
// 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()
指定Hive表的存储格式
创建Hive表时,需要定义该表应如何从文件系统读取/写入数据,即“输入格式”和“输出格式”。还需要定义该表应如何将数据反序列化为行,或将行序列化为数据,即“ serde”。
以下选项可用于指定存储格式(“ serde”,“ input format”,“ output format”),例如CREATE TABLE src(id int) USING hive OPTIONS(fileFormat ‘parquet’)。
默认情况下,我们将以纯文本形式读取表文件。请注意,创建表时尚不支持Hive存储处理程序,您可以在Hive端使用存储处理程序创建表,并使用Spark SQL读取表。
Property Name | Meaning |
---|---|
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 | 这些选项只能与“文本文件” fileFormat一起使用。它们定义了如何将定界文件读取为行。 |
用定义的所有其他属性OPTIONS将被视为Hive serde属性。
与Hive metastore的交互是Spark SQL对Hive的最重要支持之一,它使Spark SQL能够访问Hive表的元数据。从Spark 1.4.0开始,使用以下描述的配置,可以使用Spark SQL的单个二进制版本来查询Hive元存储的不同版本。请注意,与用于与metastore进行通信的Hive版本无关,Spark SQL在内部将针对Hive 1.2.1进行编译,并将这些类用于内部执行(serdes,UDF,UDAF等)。
以下选项可用于配置用于检索元数据的Hive版本:
Property | Default | Meaning |
---|---|---|
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版本。这些罐子只需要存在于驱动程序中,但是如果您以纱线簇模式运行,则必须确保将它们与您的应用程序打包在一起。 |
spark.sql.hive.metastore.sharedPrefixes | com.mysql.jdbc,org.postgresql,com.microsoft.sqlserver,oracle.jdbc | 以逗号分隔的类前缀列表,应使用在Spark SQL和特定版本的Hive之间共享的类加载器加载。应该共享的类的一个示例是与元存储区进行对话所需的JDBC驱动程序。其他需要共享的类是与已经共享的类进行交互的类。例如,log4j使用的自定义追加程序。 |
spark.sql.hive.metastore.barrierPrefixes | (empty) | 以逗号分隔的类前缀列表,应为Spark SQL与之通信的每个Hive版本显式重新加载。例如,在通常将被共享的前缀中声明的Hive UDF(即org.apache.spark.*)。 |
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
可以使用Data Sources API将远程数据库中的表作为DataFrame或Spark SQL临时视图加载。用户可以在数据源选项中指定JDBC连接属性。 user和password通常用于登录到数据源提供为连接属性。除了连接属性,Spark还支持以下不区分大小写的选项:
Property Name | Meaning |
---|---|
url | 要连接的JDBC URL。特定于源的连接属性可以在URL中指定。例如,jdbc:postgresql://localhost/test?user=fred&password=secret |
dbtable | 应该从中读取或写入的JDBC表。请注意,在读取路径中使用它时,可以使用SQL查询子句中有效的任何东西。例如,除了完整表之外,您还可以在括号中使用子查询。不允许同时指定dbtable和query选项。 |
query | 用于将数据读入Spark的查询。指定的查询将加括号,并在FROM子句中用作子查询。Spark还将为子查询子句分配一个别名。例如,spark将向JDBC源发出以下形式的查询。SELECT FROM (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数据源执行谓词筛选的速度快时,通常会关闭谓词下推。 |
生成MSQL数据库:
CREATE DATABASE spark;
USE spark;
/*Table structure for table `data` */
DROP TABLE IF EXISTS data;
CREATE TABLE data (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(100) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table data */
insert into data(id,name) values (1,'java'),(2,'hadoop'),(3,'spark');
// Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods
// Loading data from a JDBC source
import java.util.Properties
val jdbcDF = spark.read.format("jdbc")
.option("url", "jdbc:mysql://192.168.14.1:3306/spark?useUnicode=true&characterEncoding=utf8")
.option("dbtable", "data")
.option("user", "root")
.option("password", "123456")
.load()
jdbcDF.show()
val connectionProperties = new Properties()
connectionProperties.put("user", "root")
connectionProperties.put("password", "123456")
val jdbcDF2 = spark.read
.jdbc("jdbc:mysql://192.168.14.1:3306/spark?useUnicode=true&characterEncoding=utf8", "data2", connectionProperties)
jdbcDF2.show()
// Specifying the custom data types of the read schema
connectionProperties.put("customSchema", "id Integer, name STRING")
val jdbcDF3 = spark.read
.jdbc("jdbc:mysql://192.168.14.1:3306/spark?useUnicode=true&characterEncoding=utf8", "data3", connectionProperties)
jdbcDF3.show()
// Saving data to a JDBC source
jdbcDF.write.format("jdbc")
.option("url", "jdbc:mysql://192.168.79.1:3306/spark?useUnicode=true&characterEncoding=utf8&useSSL=false")
.option("dbtable", "save")
.option("user", "root")
.option("password", "123456")
.save()
jdbcDF2.write
.jdbc("jdbc:mysql://192.168.14.1:3306/spark?useUnicode=true&characterEncoding=utf8&useSSL=false", "save2", connectionProperties)
// Specifying create table column data types on write
jdbcDF.write
.option("createTableColumnTypes", "id CHAR(64), name VARCHAR(1024)")
.jdbc("jdbc:mysql://192.168.14.1:3306/spark?useUnicode=true&characterEncoding=utf8&useSSL=false", "save3", connectionProperties)