

# 处理架构更新
<a name="handling-schema-updates-chapter"></a>

本节提供了有关为各种数据格式处理架构更新的指导。Athena 是一种基于读取的查询引擎。这意味着，当您在 Athena 中创建一个表时，它会在读取数据时应用架构。它不会改变或者重写底层数据。

如果您期望表架构有所变化，则在创建表架构时应考虑采用一种适合您的需求的数据格式。您的目标是对于不断演进的架构仍能再利用现有的 Athena 查询，同时避免在查询包含分区的表时发生架构不匹配错误。

为实现这些目标，请基于下列主题中的表选择表的数据格式。

**Topics**
+ [支持按数据格式划分的架构更新操作](#summary-of-updates)
+ [了解 Apache ORC 和 Apache Parquet 的索引访问权限](#index-access)
+ [更新架构](make-schema-updates.md)
+ [更新包含分区的表](updates-and-partitions.md)

## 支持按数据格式划分的架构更新操作
<a name="summary-of-updates"></a>

下表总结了数据存储格式及其支持的架构处理方式。使用此表有助于您选择格式，即使架构随时间发生了变化，也可继续使用 Athena 查询。

在此表中，您可以观察到 Parquet 和 ORC 的列式格式具有不同的默认列访问方法。预设情况下，Parquet 将按名称访问列，ORC 按索引（序数值）访问列。因此，Athena 可在创建表时定义一个 SerDe 属性，以切换默认列访问方法，从而在架构演进的过程中实现更大的灵活度。

对于 Parquet，可将 `parquet.column.index.access` 属性设为 `true`，这样可将列访问方法设置为使用列的序号。将此属性设为 `false` 会将列访问方法改为使用列名称。同样，ORC 使用 `orc.column.index.access` 属性控制列访问方法。有关更多信息，请参阅 [了解 Apache ORC 和 Apache Parquet 的索引访问权限](#index-access)。

除了将列重新排序或在表的开头添加列之外，可通过 CSV 和 TSV 进行所有其他架构操作。例如，如果架构演进只需要将列重新命名而不需要删除它们，您可以选择创建 CSV 或 TSV 格式的表。如果您需要删除列，请不要使用 CSV 或 TSV，而要使用任何其他支持的格式，最好是列式格式（例如 Parquet 或 ORC）。


**Athena 中的架构更新和数据格式**  

| 架构更新的预期类型 | 摘要 | CSV（带或不带标头）和 TSV | JSON | AVRO | PARQUET：按名称读取（默认） | PARQUET：按索引读取 | ORC：按索引读取（默认） | ORC：按名称读取 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
|  [重命名列](updates-renaming-columns.md) | 将您的数据存储为 CSV 和 TSV；或存储为 ORC 和 Parquet（如果按索引读取）。 | Y | N | N | N  | Y | Y | N | 
|  [在表的开头或中间添加新列](updates-add-columns-beginning-middle-of-table.md) | 将您的数据存储为 JSON、AVRO；或存储为 Parquet 和 ORC（如果按名称读取）。不要使用 CSV 和 TSV。 | N | Y | Y | Y | N | N | Y | 
|  [在表的末尾添加列](updates-add-columns-end-of-table.md) | 将您的数据以 CSV 或 TSV、JSON、AVRO、ORC 或 Parquet 格式存储。 | Y | Y | Y | Y | Y | Y | Y | 
| [删除列](updates-removing-columns.md) |  将您的数据存储为 JSON、AVRO；或存储为 Parquet 和 ORC（如果按名称读取）。不要使用 CSV 和 TSV。 | N | Y | Y | Y | N | N | Y | 
| [对列重新排序](updates-reordering-columns.md) | 将您的数据存储为 AVRO、JSON；或存储为 ORC 和 Parquet（如果按名称读取）。 | N | Y | Y | Y | N | N | Y | 
| [更改列的数据类型](updates-changing-column-type.md) | 将您的数据存储为任何格式，但请在 Athena 中测试您的查询，以确保数据类型兼容。对于 Parquet 和 ORC，更改数据类型仅适用于分区表。 | Y | Y | Y | Y | Y | Y | Y | 

## 了解 Apache ORC 和 Apache Parquet 的索引访问权限
<a name="index-access"></a>

PARQUET 和 ORC 是列式数据存储格式，可以按索引或名称读取。将数据存储为这两种格式之一，可在执行所有架构操作和运行 Athena 查询时确保不会产生架构不匹配的错误。
+ Athena *预设情况下按索引读取 ORC*，如 `SERDEPROPERTIES ( 'orc.column.index.access'='true')` 中所定义。有关更多信息，请参阅 [ORC：按索引读取](#orc-read-by-index)。
+ Athena *预设情况下按名称读取 Parquet*，在 `SERDEPROPERTIES ( 'parquet.column.index.access'='false')` 中定义。有关更多信息，请参阅 [Parquet：按名称读取](#parquet-read-by-name)。

这些是默认设置，因此在您的 `CREATE TABLE` 查询中指定这些 SerDe 属性是可选操作，它们是隐式使用的。如果使用这些属性，则允许您运行一些架构更新操作，同时防止其他类似操作。要支持这些操作，请运行另一 `CREATE TABLE` 查询并更改 SerDe 设置。

**注意**  
这些 SerDe 属性*不会*自动传播到每个分区。使用 `ALTER TABLE ADD PARTITION` 语句为每个分区设置 SerDe 属性。要自动执行该流程，请编写一个运行 `ALTER TABLE ADD PARTITION` 语句的脚本。

以下各部分详细描述了这些情况。

### ORC：按索引读取
<a name="orc-read-by-index"></a>

预设情况下，*ORC 格式的表是按索引读取的*。这是由以下语法定义的：

```
WITH SERDEPROPERTIES ( 
  'orc.column.index.access'='true')
```

*按索引读取*允许您将列重新命名。但这样的设置无法删除列或在表的中间添加列。

如果按名称读取 ORC，您就可以在 ORC 表的中间添加列，或删除列，请在 `CREATE TABLE` 语句中将 SerDe 属性 `orc.column.index.access` 设为 `false`。如使用此配置，将无法将列重新命名。

**注意**  
在 Athena 引擎版本 2 中，当 ORC 表设置为按名称读取时，Athena 要求 ORC 文件中的所有列名称均为小写。由于 Apache Spark 在生成 ORC 文件时不会使用小写字段名称，因此 Athena 可能无法读取如此生成的数据。解决方法是使用小写对列重命名，或使用 Athena 引擎版本 3。

以下示例演示了如何将 ORC 更改为按名称读取：

```
CREATE EXTERNAL TABLE orders_orc_read_by_name (
   `o_comment` string,
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderpriority` string, 
   `o_orderstatus` string, 
   `o_clerk` string, 
   `o_shippriority` int, 
   `o_orderdate` string
) 
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.orc.OrcSerde' 
WITH SERDEPROPERTIES ( 
  'orc.column.index.access'='false') 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION 's3://amzn-s3-demo-bucket/orders_orc/';
```

### Parquet：按名称读取
<a name="parquet-read-by-name"></a>

预设情况下，*Parquet 格式的表是按名称读取的*。这是由以下语法定义的：

```
WITH SERDEPROPERTIES ( 
  'parquet.column.index.access'='false')
```

*按名称读取*允许您在表中间添加列或删除列。但该设置无法将列重新命名。

要将 Parquet 设为按索引读取，使您可以将列重新命名，则必须在创建表时将 `parquet.column.index.access` SerDe 属性设为 `true`。

# 更新架构
<a name="make-schema-updates"></a>

本主题介绍了无需实际更改数据即可在 `CREATE TABLE` 语句中对架构进行的一些更改。要更新架构，在某些情况下可以使用 `ALTER TABLE` 命令；而在其他情况下，实际上并不需要修改现有表。而是使用新名称创建一个表，该表修改了在原始 `CREATE TABLE` 语句中使用的架构。

根据您期望架构的演进方式，选择一种兼容的数据格式，以继续使用 Athena 查询。

考察一个应用程序，该应用程序从 `orders` 表中读取订单信息，而该表存在两种格式：CSV 和 Parquet。

以下示例用 Parquet 格式创建一个表：

```
CREATE EXTERNAL TABLE orders_parquet (
   `orderkey` int, 
   `orderstatus` string, 
   `totalprice` double, 
   `orderdate` string, 
   `orderpriority` string, 
   `clerk` string, 
   `shippriority` int
) STORED AS PARQUET
LOCATION 's3://amzn-s3-demo-bucket/orders_ parquet/';
```

以下示例用 CSV 格式创建同样的表：

```
CREATE EXTERNAL TABLE orders_csv (
   `orderkey` int, 
   `orderstatus` string, 
   `totalprice` double, 
   `orderdate` string, 
   `orderpriority` string, 
   `clerk` string, 
   `shippriority` int
) 
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION 's3://amzn-s3-demo-bucket/orders_csv/';
```

以下主题将介绍这些表的更新如何影响 Athena 查询。

**Topics**
+ [在表的开头或中间添加新列](updates-add-columns-beginning-middle-of-table.md)
+ [在表的末尾添加列](updates-add-columns-end-of-table.md)
+ [删除列](updates-removing-columns.md)
+ [重命名列](updates-renaming-columns.md)
+ [对列重新排序](updates-reordering-columns.md)
+ [更改列数据类型](updates-changing-column-type.md)

# 在表的开头或中间添加新列
<a name="updates-add-columns-beginning-middle-of-table"></a>

添加列是最常见的架构变化之一。例如，您可以添加新列，用新的数据来充实表。或者，如果一个现有列的源发生了变化，您可以添加一个新列，同时保留该列表的前一版本，以调整依赖它们的应用程序。

要将列添加到表的开头或中间，并继续针对现有表运行查询，请使用 AVRO、JSON 以及 Parquet 和 ORC（如果它们的 SerDe 属性设为按名称读取）。有关信息，请参阅[了解 Apache ORC 和 Apache Parquet 的索引访问权限](handling-schema-updates-chapter.md#index-access)。

不要在 CSV 和 TSV 表的开头或中间添加列，因为这些格式取决于排序。如果在这些情况下添加列，分区架构改变将导致架构不匹配的错误。

 以下示例创建了一个新表，该表基于 JSON 数据在表的中间添加了一个 `o_comment` 列。

```
CREATE EXTERNAL TABLE orders_json_column_addition (
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderstatus` string, 
   `o_comment` string, 
   `o_totalprice` double, 
   `o_orderdate` string, 
   `o_orderpriority` string, 
   `o_clerk` string, 
   `o_shippriority` int, 
) 
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://amzn-s3-demo-bucket/orders_json/';
```

# 在表的末尾添加列
<a name="updates-add-columns-end-of-table"></a>

如果您使用 Athena 支持的任意格式（例如 Parquet、ORC、Avro、JSON、CSV 和 TSV）创建表，则可使用 `ALTER TABLE ADD COLUMNS` 语句在现有列之后但在分区列之前添加列。

以下示例在 `orders_parquet` 表末尾的任何分区列之前添加一个 `comment` 列：

```
ALTER TABLE orders_parquet ADD COLUMNS (comment string)
```

**注意**  
要在运行 `ALTER TABLE ADD COLUMNS` 后在 Athena 查询编辑器中查看新的表列，请手动刷新编辑器中的表列表，然后重新展开表。

# 删除列
<a name="updates-removing-columns"></a>

如果表中的列不再包含数据，您可能需要删除它们，或者，您可能需要限制对于列数据的访问。
+ 您可以从 JSON、Avro 表，以及按名称读取的 Parquet 和 ORC 表中删除列。有关信息，请参阅[了解 Apache ORC 和 Apache Parquet 的索引访问权限](handling-schema-updates-chapter.md#index-access)。
+ 如果您希望保留已在 Athena 中创建的表，我们不建议从 CSV 和 TSV 表中删除列。删除列会破坏架构，需要您重新创建不包含已删除列的表。

在本示例中，将从 Parquet 表中删除一列``totalprice``并运行查询。在 Athena 中，Parquet 默认是按名称读取的，因此我们省略了指定按名称读取的 SERDEPROPERTIES 配置。请注意，即使更改了架构，以下查询也会成功：

```
CREATE EXTERNAL TABLE orders_parquet_column_removed (
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderstatus` string, 
   `o_orderdate` string, 
   `o_orderpriority` string, 
   `o_clerk` string, 
   `o_shippriority` int, 
   `o_comment` string
) 
STORED AS PARQUET
LOCATION 's3://amzn-s3-demo-bucket/orders_parquet/';
```

# 重命名列
<a name="updates-renaming-columns"></a>

有时，您可能需要重命名表列，以便纠正拼写、让列名称含义更清晰或重用现有列以避免列重新排序。

如果将数据存储为 CSV 和 TSV，或 Parquet 和 ORC（配置为按索引读取），则可以将列重新命名。有关信息，请参阅[了解 Apache ORC 和 Apache Parquet 的索引访问权限](handling-schema-updates-chapter.md#index-access)。

Athena 会按照架构中的列顺序读取 CSV 和 TSV 数据并以同样的顺序返回。它不会使用列名称来将数据映射到列，因此，如果不中断 Athena 查询，您就可以重命名 CSV 或 TSV 格式列。

重命名列的一种策略是基于相同的基础数据创建新表，但使用新的列名。以下示例会创建一个名为 `orders_parquet_column_renamed` 新 `orders_parquet` 表。该示例将列 ``o_totalprice`` 名称更改为 ``o_total_price``，然后在 Athena 中运行查询：

```
CREATE EXTERNAL TABLE orders_parquet_column_renamed (
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderstatus` string, 
   `o_total_price` double, 
   `o_orderdate` string, 
   `o_orderpriority` string, 
   `o_clerk` string, 
   `o_shippriority` int, 
   `o_comment` string
) 
STORED AS PARQUET
LOCATION 's3://amzn-s3-demo-bucket/orders_parquet/';
```

对于 Parquet 表而言，以下查询可以运行，但经过重命名的列不会显示数据，因为列是按名称访问的（Parquet 的默认设置）而不是按索引访问的：

```
SELECT * 
FROM orders_parquet_column_renamed;
```

对于 CSV 表的查询看起来类似：

```
CREATE EXTERNAL TABLE orders_csv_column_renamed (
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderstatus` string, 
   `o_total_price` double, 
   `o_orderdate` string, 
   `o_orderpriority` string, 
   `o_clerk` string, 
   `o_shippriority` int, 
   `o_comment` string
) 
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION 's3://amzn-s3-demo-bucket/orders_csv/';
```

对于 CSV 表而言，以下查询可以运行，并将显示所有列的数据，包括经过重新命名的列：

```
SELECT * 
FROM orders_csv_column_renamed;
```

# 对列重新排序
<a name="updates-reordering-columns"></a>

只有表的数据格式是按名称读取的，才可以将列重新排序，例如 JSON 或默认为按名称读取的 Parquet。如有需要，也可以将 ORC 设为按名称读取。有关信息，请参阅[了解 Apache ORC 和 Apache Parquet 的索引访问权限](handling-schema-updates-chapter.md#index-access)。

以下示例创建了一个新表，其中的列顺序不同：

```
CREATE EXTERNAL TABLE orders_parquet_columns_reordered (
   `o_comment` string,
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderpriority` string, 
   `o_orderstatus` string, 
   `o_clerk` string, 
   `o_shippriority` int, 
   `o_orderdate` string
) 
STORED AS PARQUET
LOCATION 's3://amzn-s3-demo-bucket/orders_parquet/';
```

# 更改列数据类型
<a name="updates-changing-column-type"></a>

当现有类型无法再容纳所需的信息量时，您可能需要使用其他列类型。例如，ID 列的值可能超过 `INT` 数据类型的大小，需要使用 `BIGINT` 数据类型。

## 注意事项
<a name="updates-changing-column-type-considerations"></a>

在计划为列使用不同的数据类型时，请考虑以下几点：
+ 在大多数情况下，不能直接更改列的数据类型。而是可以重新创建 Athena 表，并使用新的数据类型定义该列。
+ 只有特定数据类型可以读取为其他数据类型。有关可以进行这种处理的数据类型，请参阅本部分中的表。
+ 对于 Parquet 和 ORC 格式的数据，如果表未进行分区，则将无法为列使用其他数据类型。
+ 对于 Parquet 和 ORC 格式的分区表，一个分区的列类型可以不同于另一个分区的列类型，而且 Athena 将 `CAST` 为所需的类型（如果可能）。有关信息，请参阅[避免带有分区的表出现架构不匹配错误](updates-and-partitions.md#partitions-dealing-with-schema-mismatch-errors)。
+ 对于仅使用 [LazySimpleSerDe](lazy-simple-serde.md) 创建的表，可以使用 `ALTER TABLE REPLACE COLUMNS` 语句将现有列替换为不同的数据类型，但是还必须在语句中重新定义要保留的所有现有列，否则它们将被删除。有关更多信息，请参阅 [ALTER TABLE REPLACE COLUMNS](alter-table-replace-columns.md)。
+ 仅对于 Apache Iceberg 表而言，您可以使用 [ALTER TABLE CHANGE COLUMN](querying-iceberg-alter-table-change-column.md) 语句更改列的数据类型。`ALTER TABLE REPLACE COLUMNS` 不支持 Iceberg 表。有关更多信息，请参阅 [Iceberg 表架构演进](querying-iceberg-evolving-table-schema.md)。

**重要**  
我们强烈建议您在执行数据类型转换时，先测试和验证一下您的查询。如果 Athena 无法使用目标数据类型，则 `CREATE TABLE` 查询可能会失败。

## 使用兼容的数据类型
<a name="updates-changing-column-type-use-compatible-data-types"></a>

尽可能使用兼容的数据类型。下表列出了可被视为其他数据类型的数据类型：


| 原始数据类型 | 可用的目标数据类型 | 
| --- | --- | 
| STRING | BYTE, TINYINT, SMALLINT, INT, BIGINT | 
| BYTE | TINYINT, SMALLINT, INT, BIGINT | 
| TINYINT | SMALLINT, INT, BIGINT | 
| SMALLINT | INT, BIGINT | 
| INT | BIGINT | 
| FLOAT | DOUBLE | 

以下示例为原始 `orders_json` 表使用 `CREATE TABLE` 语句创建一个名为 `orders_json_bigint` 的新表。新表使用 `BIGINT` 而不是 `INT` 作为 ``o_shippriority`` 列的数据类型。

```
CREATE EXTERNAL TABLE orders_json_bigint (
   `o_orderkey` int, 
   `o_custkey` int, 
   `o_orderstatus` string, 
   `o_totalprice` double, 
   `o_orderdate` string, 
   `o_orderpriority` string, 
   `o_clerk` string, 
   `o_shippriority` BIGINT
) 
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://amzn-s3-demo-bucket/orders_json';
```

以下查询可成功运行，与更改数据类型前的原始 `SELECT` 查询类似：

```
Select * from orders_json 
LIMIT 10;
```

# 更新包含分区的表
<a name="updates-and-partitions"></a>

在 Athena 中，一个表及其分区必须使用相同的数据格式，但它们的架构可以不同。当您创建一个新分区时，该分区通常继承表的架构。随着时间的推移，该架构可能开始变得不同。原因包括：
+ 当您的表的架构发生变化时，分区架构没有更新，从而没有保持与表的架构同步。
+ AWS Glue 爬网程序允许您发现具有不同架构的分区中的数据。这意味着，假如您使用 AWS Glue 在 Athena 中创建了一个表，则当爬网程序完成处理后，该表的架构及其分区可能会有所不同。
+ 如果您直接使用 AWS API 添加分区。

如果分区满足以下约束，Athena 会成功地处理具有分区的表。如果不满足这些约束，Athena 会发出 HIVE\$1PARTITION\$1SCHEMA\$1MISMATCH 错误。
+ 每个分区的架构与表的架构兼容。
+ 表的数据格式允许您要执行的更新类型：添加、删除、重新排序列或更改列的数据类型。

  例如，对于 CSV 和 TSV 格式，您可以将列重命名，在表的最后添加新列，并将列的数据类型更改为其他兼容类型，但您无法删除列。对于其他格式，可以添加或删除列，或者将列的数据类型更改为其他兼容类型。有关信息，请参阅[摘要：Athena 中的更新和数据格式](handling-schema-updates-chapter.md#summary-of-updates)。

## 避免带有分区的表出现架构不匹配错误
<a name="partitions-dealing-with-schema-mismatch-errors"></a>

在查询执行开始时，Athena 通过检查表和分区之间每个列数据类型是否兼容来验证表的架构。
+ 对于 Parquet 和 ORC 数据存储类型，Athena 依赖于列名，并将其用于基于列名的架构验证。这消除了具有 Parquet 和 ORC 格式分区的表的 `HIVE_PARTITION_SCHEMA_MISMATCH` 错误。(如果 SerDe 属性设置为按名称访问索引，则对于 ORC 来说此为真：`orc.column.index.access=FALSE`。Parquet 默认按名称读取索引)。
+ 对于 CSV、JSON 和 Avro，Athena 使用基于索引的架构验证。这意味着，如果您遇到架构不匹配错误，则应删除导致架构不匹配的分区并创新创建它，以便 Athena 可以成功地查询它。

 Athena 会将表的架构与分区架构做比较。假如您使用 AWS Glue 爬网程序在 Athena 中创建了一个 CSV、JSON 和 AVRO 格式的表，则在爬网程序完成处理后，该表的架构和其分区的架构可能会不同。如果表的架构与分区架构之间存在不匹配，则您的 Athena 查询将因类似如下的架构验证错误而失败：'crawler\$1test.click\$1avro' is declared as type 'string', but partition 'partition\$10=2017-01-17' declared column 'col68' as type 'double'. ('crawler\$1test.click\$1avro' 声明为类型 'string'，但分区 'partition\$10=2017-01-17' 声明列 'col68' 的类型为 'double'。)

对于此类错误，通常的解决方法是删除导致错误的分区并重新创建它。有关更多信息，请参阅 [ALTER TABLE DROP PARTITION](alter-table-drop-partition.md) 和 [ALTER TABLE ADD PARTITION](alter-table-add-partition.md)。