Spark 表名大小写问题

根据 Spark 官方文档,表名是大小写不敏感的,大写会自动转小写:

An identifier is a string used to identify a database object such as a table, view, schema, column, etc. Spark SQL has regular identifiers and delimited identifiers, which are enclosed within backticks. Both regular identifiers and delimited identifiers are case-insensitive.

问题描述

Spark 1.4 之后新增了字段名大小写敏感的开关,经测试,开启之后会导致一些问题;

cannot resolve ‘Tb.xx’ given input columns

开启大小写敏感 spark.sql.caseSensitive=true

如果表名包含大写字母,比如 Tb1 然后执行 select Tb1.xx 就会报错,因为 Tb1 表名会被转为 tb1,Spark 的元数据中只有 tb1.xx 字段。

示例如下:

(以下代码除特别说明,均使用 Spark 3.3.2 版本进行测试,Hive 存储元数据,HDFS 存储数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# spark-shell --conf spark.sql.caseSensitive=true

scala> spark.sql("SELECT * FROM VALUES (1) AS (`id`)").write.saveAsTable("Tb1")

// 实际存储的表名是 tb1,数据在 hdfs 上的目录是 Tb1
scala> spark.sql("show tables").show
+---------+------------+-----------+
|namespace| tableName|isTemporary|
+---------+------------+-----------+
| default| tb1| false|
+----------------------------------+

// 修改 select 部分使用 Tb1 会报错,逻辑计划最下面可以看到 spark 将表名转为了小写
// 也就是说 Tb1.id != tb1.id 导致报错 找不到字段
scala> spark.sql("select Tb1.id from Tb1").explain(true)
org.apache.spark.sql.AnalysisException: Column 'Tb1.id' does not exist. Did you mean one of the following? [spark_catalog.default.tb1.id]; line 1 pos 7;
'Project ['Tb1.id]
+- SubqueryAlias spark_catalog.default.tb1
+- Relation default.tb1[id#32] parquet

// 在 Spark 3.2.2 版本中报错信息如下
scala> spark.sql("select Tb1.id from Tb1").show(false)
org.apache.spark.sql.AnalysisException: cannot resolve 'Tb1.id' given input columns: [spark_catalog.default.tb1.id]; line 1 pos 7;
'Project ['Tb1.id]
+- SubqueryAlias spark_catalog.default.tb1
+- Relation default.tb1[id#24] parquet

关闭字段名大小写敏感

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# spark-shell --conf spark.sql.caseSensitive=false

scala> spark.sql("SELECT * FROM VALUES (1) AS (`id`)").write.saveAsTable("Tb2")

// 实际存储的表名是 tb2,数据在 hdfs 上的目录是 **tb2**
scala> spark.sql("show tables").show
+---------+------------+-----------+
|namespace| tableName|isTemporary|
+---------+------------+-----------+
| default| tb2| false|
+----------------------------------+

// 表名和字段大小写均不敏感
scala> spark.sql("select Tb2.iD from Tb2").show
+---+
| iD|
+---+
| 1|
+---+

// 执行计划
scala> spark.sql("select Tb2.iD from Tb2").explain(true)
== Parsed Logical Plan ==
'Project ['Tb2.iD]
+- 'UnresolvedRelation [Tb2], [], false

== Analyzed Logical Plan ==
iD: int
Project [iD#64]
+- SubqueryAlias spark_catalog.default.tb2
+- Relation default.tb2[id#64] parquet

== Optimized Logical Plan ==
Project [iD#64]
+- Relation default.tb2[id#64] parquet

== Physical Plan ==
*(1) ColumnarToRow
+- FileScan parquet default.tb2[id#64] Batched: true, DataFilters: [], Format: Parquet, Location: InMemoryFileIndex(1 paths)[hdfs://hdcluster/user/hive/warehouse/default.db/tb2], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<id:int>

表数据为空

在 Spark 3.2.2 中测试发现,如果开启字段大小写敏感,并且表名存在大写字母 saveAsTable("Tb1"),实际数据会存储在大写的 Tb1 中,而 hive meta 中的 location 目录是小写的 tb1,导致后续使用该表都会去小写的 tb1 目录中读取,从而表现为读到的数据为空。

解决方案

  • 在网上几乎没有找到关于 Spark 中表名大小写的讨论,默认应该都是用小写表名
  • 如果一定要使用大写表名,在查询的时候给表名加个小写的别名即可 FROM Tb1 tb1
  • 或者关掉大小写敏感(此项默认关闭) spark.sql.caseSensitive=false

补充测试:视图名

关闭字段名大小写敏感

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# spark-shell --conf spark.sql.caseSensitive=false

scala> spark.sql("SELECT * FROM VALUES (1) AS (`id`)").createOrReplaceTempView("Tb1")

// 修改表名 Tb1 为 TB1 和 tB1 均可正常识别
scala> spark.sql("select TB1.id from tB1").show
+---+
| id|
+---+
| 1|
+---+

// 仅修改 select 部分的表名,也可以正常识别
scala> spark.sql("select TB1.id from Tb1").show
+---+
| id|
+---+
| 1|
+---+

开启大小写敏感

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# spark-shell --conf spark.sql.caseSensitive=true

scala> spark.sql("SELECT * FROM VALUES (1) AS (`id`)").createOrReplaceTempView("Tb1")

// 修改表名 Tb1 为 tB1 后报错找不到表
scala> spark.sql("select TB1.id from tB1").show
org.apache.spark.sql.AnalysisException: Table or view not found: tB1; line 1 pos 19;
'Project ['TB1.id]
+- 'UnresolvedRelation [tB1], [], false

// 仅修改 select 部分的表名,也可以正常识别
scala> spark.sql("select TB1.id from Tb1").show
org.apache.spark.sql.AnalysisException: Column 'TB1.id' does not exist. Did you mean one of the following? [Tb1.id]; line 1 pos 7;
'Project ['TB1.id]
+- SubqueryAlias Tb1
+- View (`Tb1`, [id#0])
+- Project [id#0]
+- SubqueryAlias AS
+- LocalRelation [id#0]

经测试,通过 createOrReplaceTempView 创建的视图名称在开启 spark.sql.caseSensitive 之后也会区分大小写