

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 在 Amazon QLDB 中处理数据与历史记录
<a name="working-with-data"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

以下主题提供了关于*创建、读取、更新和删除*（CRUD）语句的基本示例。您可使用[QLDB 控制台](console_QLDB.md)或 [QLDB Shell](data-shell.md)上的*PartiQL 编辑器*手动运行这些语句。本指南还向您介绍以下流程：当您更改分类账时，QLDB 如何处理您的数据。

QLDB 支持[PartiQL](https://partiql.org/)查询语言。

有关展示如何使用 QLDB 驱动程序以编程方式运行类似语句的代码示例，请参阅[驱动程序入门](getting-started-driver.md)中的教程。

**提示**  
以下是在 QLDB 中使用 PartiQL 的提示和最佳实践小贴士：  
**了解并发和事务限制** — `SELECT` 查询等所有语句都应遵守[乐观并发控制（OCC）](concurrency.md)冲突和[事务限制](limits.md#limits.fixed)，包括 30 秒事务暂停。
**使用索引** - 使用高基数索引，并运行有针对性的查询来优化语句并避免全表扫描。要了解更多信息，请参阅 [优化查询性能](working.optimize.md)。
**使用相等谓词** - 索引查找需要*相等*运算符（`=` 或 `IN`）。不等式运算符（`<`、`>`、`LIKE`、`BETWEEN`）不符合索引查找的条件，因此会生成全表扫描。
**仅使用内部联接** - QLDB 仅支持内部联接。根据最佳实践标准，在为要加入的每个表编制索引的字段上进行联接。为联接条件与相等谓词选择高基数索引。

**Topics**
+ [创建带有索引的表和插入文档](working.create.md)
+ [查询数据](working.userdata.md)
+ [查询文档元数据](working.metadata.md)
+ [通过 BY 子句查询文档 ID](working.metadata.by-clause.md)
+ [更新和删除文档](working.revisions.md)
+ [查询修订历史记录](working.history.md)
+ [对文档修订版执行编校](working.redaction.md)
+ [优化查询性能](working.optimize.md)
+ [获取 PartiQL 语句统计信息](working.statement-stats.md)
+ [查询系统目录](working.catalog.md)
+ [管理表](working.manage-tables.md)
+ [管理索引](working.manage-indexes.md)
+ [IDs 在亚马逊 QLDB 中独树一帜](working.unique-id.md)

# 创建带有索引的表和插入文档
<a name="working.create"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

创建 Amazon QLDB 分类账后，您的第一步是创建包含基本[CREATE TABLE](ql-reference.create-table.md)语句的表。表由[QLDB 文档](ql-reference.docs.md)组成，这些版本是 [Amazon Ion](ion.md) `struct` 格式的数据集。

**Topics**
+ [创建表与索引](#working.create.tables-indexes)
+ [插入文档](#working.create.insert)

## 创建表与索引
<a name="working.create.tables-indexes"></a>

表具有简单的、区分大小写的名称，没有名称空间。QLDB 支持开放内容且不强制架构，因此在创建表时不需要定义属性或数据类型。

```
CREATE TABLE VehicleRegistration
```

```
CREATE TABLE Vehicle
```

`CREATE TABLE`语句返回系统为新表分配的 ID。QLDB IDs 中所有[系统分配](working.unique-id.md)的都是通用唯一标识符 (UUID)，每个标识符都以 Base62 编码的字符串表示。

**注意**  
或者，您可在创建表时为表资源定义标签。要了解如何操作，请参阅[创建时对表格进行标记](working.manage-tables.md#working.manage-tables.tags)。

您还可以在表格创建索引以优化查询性能。

```
CREATE INDEX ON VehicleRegistration (VIN)
```

```
CREATE INDEX ON VehicleRegistration (LicensePlateNumber)
```

```
CREATE INDEX ON Vehicle (VIN)
```

**重要**  
QLDB 需要索引才能高效查找文档。如果没有索引，QLDB 在读取文档时需进行全表扫描。这可能会导致大型表出现性能问题，包括并发冲突与事务超时。  
为避免表扫描，必须在索引字段或文档 ID 上使用*相等*运算符（`=`或`IN`）运行带有`WHERE`谓词子句的语句。有关更多信息，请参阅 [优化查询性能](working.optimize.md)。

创建索引时应注意以下限制：
+ 只能在单个顶级字段创建索引。不支持复合索引、嵌套索引、唯一索引以及基于函数的索引。
+ 您可以为任何 [Ion 数据类型](ql-reference.data-types.md)创建索引，其中包括 `list` 和 `struct`。但是，无论 Ion 类型如何，您都只能通过整个 Ion 值进行索引查找。例如，使用 `list` 类型作为索引时，不能按列表中的一个项目进行索引查找。
+ 只有使用相等谓词时，查询性能才会得到改善；例如`WHERE indexedField = 123` 或 `WHERE indexedField IN (456, 789)`。

  QLDB 不支持查询谓词不等式。因此，未实施范围过滤扫描。
+ 索引字段名称区分大小写，且最大长度可为 128 个字符。
+ 在 QLDB 中创建索引具有异步特点。非空表上完成索引所需的时间取决于表的大小。有关更多信息，请参阅 [管理索引](working.manage-indexes.md)。

## 插入文档
<a name="working.create.insert"></a>

然后，您可将文档插入表格中。QLDB 文档以 Amazon Ion格式存储。以下 PartiQL [INSERT](ql-reference.insert.md)语句包括[Amazon QLDB 控制台入门](getting-started.md)中使用的车辆注册采样数据的子集。

```
INSERT INTO VehicleRegistration
<< {
    'VIN' : '1N4AL11D75C109151',
    'LicensePlateNumber' : 'LEWISR261LL',
    'State' : 'WA',
    'City' : 'Seattle',
    'PendingPenaltyTicketAmount' : 90.25,
    'ValidFromDate' : `2017-08-21T`,
    'ValidToDate' : `2020-05-11T`,
    'Owners' : {
        'PrimaryOwner' : { 'PersonId' : '294jJ3YUoH1IEEm8GSabOs' },
        'SecondaryOwners' : [ { 'PersonId' : '5Ufgdlnj06gF5CWcOIu64s' } ]
    }
},
{
    'VIN' : 'KM8SRDHF6EU074761',
    'LicensePlateNumber' : 'CA762X',
    'State' : 'WA',
    'City' : 'Kent',
    'PendingPenaltyTicketAmount' : 130.75,
    'ValidFromDate' : `2017-09-14T`,
    'ValidToDate' : `2020-06-25T`,
    'Owners' : {
        'PrimaryOwner' : { 'PersonId': 'IN7MvYtUjkp1GMZu0F6CG9' },
        'SecondaryOwners' : []
    }
} >>
```

```
INSERT INTO Vehicle
<< {
    'VIN' : '1N4AL11D75C109151',
    'Type' : 'Sedan',
    'Year' : 2011,
    'Make' : 'Audi',
    'Model' : 'A5',
    'Color' : 'Silver'
} ,
{
    'VIN' : 'KM8SRDHF6EU074761',
    'Type' : 'Sedan',
    'Year' : 2015,
    'Make' : 'Tesla',
    'Model' : 'Model S',
    'Color' : 'Blue'
} >>
```

**PartiQL 语法和语义**
+ 字段名称以单引号（`'...'`）括起来。
+ 字符串值也以单引号（`'...'`）括起来。
+ 时间戳用反引号（``...``）括起来。任何 Ion 字面值都可以用反引号表示。
+ 整数和小数为不需要表示的字面值。

有关 PartiQL 的语法和语义详细信息，请参阅[在 Amazon QLDB 中使用 PartiQL 查询 Ion](ql-reference.query.md)。

`INSERT` 语句创建版本号为零的文档的初始修订版。为了唯一标识每个文档，QLDB 将分配*文档 ID*，以作为元数据的一部分。插入语句返回插入的每个文档 ID。

**重要**  
由于 QLDB 不强制执行架构，因此您可多次将同一个文档插入表中。每个插入语句向日志提交单独的文档条目，QLDB 为每个文档分配唯一的 ID。

若要了解如何查询您插入到表格中的文档，请继续 [查询数据](working.userdata.md)。

# 查询数据
<a name="working.userdata"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

*用户视图* 仅返回用户数据的最新未删除版本。这是 Amazon QLDB 中的默认视图。这意味着，当您只想查询自己的数据时，不需要特殊限定符。

有关以下查询示例的语法和参数的详细信息，请参阅*Amazon QLDB PartiQL 参考*中的[SELECT](ql-reference.select.md)。

**Topics**
+ [基本查询](#working.userdata.basic)
+ [投影和筛选](#working.userdata.projections-filters)
+ [联接](#working.userdata.joins)
+ [嵌套数据](#working.userdata.nested)

## 基本查询
<a name="working.userdata.basic"></a>

基本 `SELECT` 查询会返回您插入表格中的文档。

**警告**  
当你在没有索引查找的情况下运行查询时，它会调用全表扫描。PartiQL 之所以支持此类查询，是因为其与 SQL 兼容。但是，*切勿*在 QLDB 中对生产用例运行表扫描。表扫描可能会导致大型表出现性能问题，包括并发冲突与事务超时。  
为避免表扫描，必须在索引字段或文档 ID 上使用*相等*运算符（`WHERE indexedField = 123`或`WHERE indexedField IN (456, 789)`）运行带有`WHERE`谓词子句的语句。有关更多信息，请参阅 [优化查询性能](working.optimize.md)。

以下查询显示了您之前在[创建带有索引的表和插入文档](working.create.md)中插入的车辆登记文件的结果。结果的顺序不指定，可能因每个`SELECT`查询而异。在 QLDB 中，任何查询都不应该依赖结果顺序。

```
SELECT * FROM VehicleRegistration
WHERE LicensePlateNumber IN ('LEWISR261LL', 'CA762X')
```

```
{
    VIN: "1N4AL11D75C109151",
    LicensePlateNumber: "LEWISR261LL",
    State: "WA",
    City: "Seattle",
    PendingPenaltyTicketAmount: 90.25,
    ValidFromDate: 2017-08-21T,
    ValidToDate: 2020-05-11T,
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    }
},
{
    VIN: "KM8SRDHF6EU074761",
    LicensePlateNumber: "CA762X",
    State: "WA",
    City: "Kent",
    PendingPenaltyTicketAmount: 130.75,
    ValidFromDate: 2017-09-14T,
    ValidToDate: 2020-06-25T,
    Owners: {
        PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
        SecondaryOwners: []
    }
}
```

```
SELECT * FROM Vehicle
WHERE VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    VIN: "1N4AL11D75C109151",
    Type: "Sedan",
    Year: 2011,
    Make: "Audi",
    Model: "A5",
    Color: "Silver"
},
{
    VIN: "KM8SRDHF6EU074761",
    Type: "Sedan",
    Year: 2015,
    Make: "Tesla",
    Model: "Model S",
    Color: "Blue"
}
```

**重要**  
在 PartiQL 中，您可使用单引号表示数据操作语言（DML）或查询语句中的字符串。但是 QLDB 控制台和 QLDB Shell 以 Amazon Ion 文本格式返回查询结果，因此您会看到用双引号括起来的字符串。  
这种语法差异允许 PartiQL 查询语言保持 SQL 兼容性，并使 Amazon Ion 文本格式保持 JSON 兼容性。

## 投影和筛选
<a name="working.userdata.projections-filters"></a>

您可以进行预测（定向`SELECT`）和其他标准筛选条件（`WHERE`条款）。以下查询返回 `VehicleRegistration` 表中文档字段的子集。它可筛选符合以下标准的车辆：
+ **字符串筛选条件** — 它已在西雅图注册。
+ **十进制筛选条件** — 它的待处理工单数量小于 `100.0`。
+ **日期筛选退傲剑** — 其注册日期在 2019 年 9 月 4 日或之后有效。

```
SELECT r.VIN, r.PendingPenaltyTicketAmount, r.Owners
FROM VehicleRegistration AS r
WHERE r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
AND r.City = 'Seattle' --string
AND r.PendingPenaltyTicketAmount < 100.0 --decimal
AND r.ValidToDate >= `2019-09-04T` --timestamp with day precision
```

```
{
    VIN: "1N4AL11D75C109151",
    PendingPenaltyTicketAmount: 90.25,
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    }
}
```

## 联接
<a name="working.userdata.joins"></a>

您也可编写内部联接查询。以下示例显示了隐式内部联接查询，该查询返回所有注册文件以及已注册车辆的属性。

```
SELECT * FROM VehicleRegistration AS r, Vehicle AS v
WHERE r.VIN = v.VIN
AND r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    VIN: "1N4AL11D75C109151",
    LicensePlateNumber: "LEWISR261LL",
    State: "WA",
    City: "Seattle",
    PendingPenaltyTicketAmount: 90.25,
    ValidFromDate: 2017-08-21T,
    ValidToDate: 2020-05-11T,
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    },
    Type: "Sedan",
    Year: 2011,
    Make: "Audi",
    Model: "A5",
    Color: "Silver"
},
{
    VIN: "KM8SRDHF6EU074761",
    LicensePlateNumber: "CA762X",
    State: "WA",
    City: "Kent",
    PendingPenaltyTicketAmount: 130.75,
    ValidFromDate: 2017-09-14T,
    ValidToDate: 2020-06-25T,
    Owners: {
        PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
        SecondaryOwners: []
    },
    Type: "Sedan",
    Year: 2015,
    Make: "Tesla",
    Model: "Model S",
    Color: "Blue"
}
```

或者，您可使用如下显式语法编写相同的内部联接查询。

```
SELECT * FROM VehicleRegistration AS r INNER JOIN Vehicle AS v
ON r.VIN = v.VIN
WHERE r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

## 嵌套数据
<a name="working.userdata.nested"></a>

您可在 QLDB 中使用 PartiQL 查询文档中的嵌套数据。以下示例展示了用于扁平化嵌套数据的相关子查询。此处的 `@` 字符在技术上是可选的。但它明确表示你想在里面放置一个`Owners`结构`VehicleRegistration`，而不是一个名为不同的集合`Owners`（如有）。

```
SELECT 
    r.VIN, 
    o.SecondaryOwners
FROM
    VehicleRegistration AS r, @r.Owners AS o
WHERE
    r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    VIN: "1N4AL11D75C109151",
    SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
},
{
    VIN: "KM8SRDHF6EU074761",
    SecondaryOwners: []
}
```

下图展示了`SELECT` 列表中的子查询，除了内部联接外，该查询还投射嵌套数据。

```
SELECT 
    v.Make, 
    v.Model, 
    (SELECT VALUE o.PrimaryOwner.PersonId FROM @r.Owners AS o) AS PrimaryOwner
FROM 
    VehicleRegistration AS r, Vehicle AS v
WHERE 
    r.VIN = v.VIN AND r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    Make: "Audi",
    Model: "A5",
    PrimaryOwner: ["294jJ3YUoH1IEEm8GSabOs"]
},
{
    Make: "Tesla",
    Model: "Model S",
    PrimaryOwner: ["IN7MvYtUjkp1GMZu0F6CG9"]
}
```

以下查询返回表 `VehicleRegistration` 文档的 `Owners.SecondaryOwners` 列表中每个人的 `PersonId` 和索引（序数）。

```
SELECT s.PersonId, owner_idx
FROM VehicleRegistration AS r, @r.Owners.SecondaryOwners AS s AT owner_idx
WHERE r.VIN = '1N4AL11D75C109151'
```

```
{
    PersonId: "5Ufgdlnj06gF5CWcOIu64s",
    owner_idx: 0
}
```

要了解如何查询您的文档元数据，请继续 [查询文档元数据](working.metadata.md)。

# 查询文档元数据
<a name="working.metadata"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

`INSERT` 语句创建版本号为零的文档的初始修订版。为了唯一标识每个文档，Amazon QLDB 将分配*文档 ID*，以作为元数据的一部分。

除了文档 ID 和版本号外，QLDB 还会将系统为每个文档生成的其他元数据存储至表格。此元数据包含事务信息、日记账属性和文档的哈希值。

系统分配的所有标识符 IDs 都是通用唯一标识符 (UUID)，每个标识符都以 Base62 编码的字符串表示。有关更多信息，请参阅 [IDs 在亚马逊 QLDB 中独树一帜](working.unique-id.md)。

**Topics**
+ [已提交视图](#working.metadata.committed)
+ [加入已提交和用户视图](#working.metadata.committed-joins)

## 已提交视图
<a name="working.metadata.committed"></a>

您可以通过查询*已提交视图*访问文档元数据。此视图从系统定义的表返回文档，该表直接对应于您的用户表。它包括您的数据和系统生成的元数据的最新已提交、未删除修订版。要查询此视图，请在查询中的表名中添加前缀 `_ql_committed_`。（QLDB 中`_ql_`为系统对象保留了前缀。）

```
SELECT * FROM _ql_committed_VehicleRegistration AS r
WHERE r.data.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

使用先前在[创建带有索引的表和插入文档](working.create.md)中插入的数据，此查询的输出显示了每个未删除文档的最新修订版的系统内容。系统文档的 `metadata` 字段中嵌套了元数据，您的用户数据嵌套在`data`字段中。

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{wCsmM6qD4STxz0WYmE+47nZvWtcCz9D6zNtCiM5GoWg=}},
    data:{
        VIN: "1N4AL11D75C109151",
        LicensePlateNumber: "LEWISR261LL",
        State: "WA",
        City: "Seattle",
        PendingPenaltyTicketAmount: 90.25,
        ValidFromDate: 2017-08-21T,
        ValidToDate: 2020-05-11T,
        Owners: {
            PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
            SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
        }
    },
    metadata:{
        id:"3Qv67yjXEwB9SjmvkuG6Cp",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
},
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{wPuwH60TtcCvg/23BFp+redRXuCALkbDihkEvCX22Jk=}},
    data:{
        VIN: "KM8SRDHF6EU074761",
        LicensePlateNumber: "CA762X",
        State: "WA",
        City: "Kent",
        PendingPenaltyTicketAmount: 130.75,
        ValidFromDate: 2017-09-14T,
        ValidToDate: 2020-06-25T,
        Owners: {
            PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
            SecondaryOwners: []
        }
    },
    metadata:{
        id:"JOzfB3lWqGU727mpPeWyxg",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
}
```

**已提交视图字段**
+ `blockAddress` — 分类账的日记账中提交文档修订的数据块的位置。可用于加密验证的地址包含以下两个字段。
  + `strandId` — 包含数据块的日记账链的唯一 ID。
  + `sequenceNo` — 一个索引号，用于指定数据块在链中的位置。
**注意**  
本示例中的两个文档都具有相同的`blockAddress`，其`sequenceNo`相同。由于这些文档是在单个事务中插入的（在本例中为单个语句中），因此它们是在同一个数据块内提交的。
+ `hash` — 唯一表示文档修订版本的 SHA-256 Ion 哈希值。哈希值涵盖了修订版 `data` 和 `metadata` 字段，可用于[加密验证](verification.md)。
+ `data` — 文档的用户数据属性。

  如果您对修订版进行编校，则此`data`结构将替换为`dataHash`字段，该字段的值是已删除`data`结构的 Ion 哈希。
+ `metadata` — 文档的元数据属性。
  + `id` — 系统为文档分配的唯一 ID。
  + `version` — 文档的版本号。这是从零开始的整数，随着每个文档修订版本的增加而递增。
  + `txTime` – 文档修订提交到日记账的时间戳。
  + `txId` – 提交文档修订的事务唯一 ID。

## 加入已提交和用户视图
<a name="working.metadata.committed-joins"></a>

您可编写将已提交视图中的表与用户视图中的表连接起来的查询。例如，您可能希望将一个表`id`文档与另一个表的用户定义字段连接起来。

以下查询分别将两个名为 `DriversLicense` 和 `Person`的表与其 `PersonId`和的文档 `id`字段连接起来，后者使用已提交的视图。

```
SELECT * FROM DriversLicense AS d INNER JOIN _ql_committed_Person AS p
ON d.PersonId = p.metadata.id
WHERE p.metadata.id = '1CWScY2qHYI9G88C2SjvtH'
```

要了解如何在默认用户视图中查询文档 ID 字段，请继续 [通过 BY 子句查询文档 ID](working.metadata.by-clause.md)。

# 通过 BY 子句查询文档 ID
<a name="working.metadata.by-clause"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

虽然您可以定义作为唯一标识符的字段（例如车辆的 VIN），但文档的真正唯一标识符是 `id` 元数据字段，如 [插入文档](working.create.md#working.create.insert) 中所述。因此，您可以使用 `id` 字段在表之间创建关系。

文档 `id` 字段只能在已提交的视图中直接访问，但您也可使用`BY`子句将其投影到默认用户视图中。有关示例，请参阅以下查询和结果。

```
SELECT r_id, r.VIN, r.LicensePlateNumber, r.State, r.City, r.Owners
FROM VehicleRegistration AS r BY r_id
WHERE r_id = '3Qv67yjXEwB9SjmvkuG6Cp'
```

```
{
    r_id: "3Qv67yjXEwB9SjmvkuG6Cp",
    VIN: "1N4AL11D75C109151",
    LicensePlateNumber: "LEWISR261LL",
    State: "WA",
    City: "Seattle",
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    }
}
```

在此查询中，`r_id`是用户定义别名，在`FROM`子句中使用`BY`关键字声明。此 `r_id` 别名绑定至查询结果集中每个文档的 `id` 元数据字段。你可以在`SELECT` 子句中使用这个别名，也可以在*用户视图*的查询 `WHERE` 子句中使用这个别名。

但是，若要访问其他元数据属性，必须查询已提交视图。

## 使用证件 ID 加入
<a name="working.by-clause.joining-on-id"></a>

假设您正在使用一个表的`id`文档作为另一个表用户定义字段中的外键。您可以使用 `BY` 子句为这两个字段上的两个表编写内部联接查询（与上一主题中的[加入已提交和用户视图](working.metadata.md#working.metadata.committed-joins)类似）。

以下示例分别在它们的 `PersonId` 和文档 `id` 字段上连接两个名为 `DriversLicense` 和 `Person` 的表，对后者使用 `BY` 子句。

```
SELECT * FROM DriversLicense AS d INNER JOIN Person AS p BY pid
ON d.PersonId = pid
WHERE pid = '1CWScY2qHYI9G88C2SjvtH'
```

要了解如何对表格中的文档更改，请继续 [更新和删除文档](working.revisions.md)。

# 更新和删除文档
<a name="working.revisions"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

在 Amazon QLDB 中，*文档修订版*是一个 Amazon Ion 结构，它表示由唯一文档 ID 标识的文档序列的单个版本。每个修订版都包含文档完整数据集，包括您的用户数据和系统生成的元数据。每个文档修订版都由文档 ID 和从零开始的版本号组合作为唯一标识。

更新文档时，QLDB 会创建包含相同文档 ID 和递增版本号的新修订版。当从表格中删除文档时，文档的生命周期即告结束。这意味着不能再次创建包含相同文档 ID 的文档修订版。

## 对文档执行修订
<a name="working.revisions.making"></a>

例如，以下语句插入新车辆登记，更新登记城市，然后删除登记。这将导致对文档进行三次修订。

```
INSERT INTO VehicleRegistration
{
    'VIN' : '1HVBBAANXWH544237',
    'LicensePlateNumber' : 'LS477D',
    'State' : 'WA',
    'City' : 'Tacoma',
    'PendingPenaltyTicketAmount' : 42.20,
    'ValidFromDate' : `2011-10-26T`,
    'ValidToDate' : `2023-09-25T`,
    'Owners' : {
        'PrimaryOwner' : { 'PersonId': 'KmA3XPKKFqYCP2zhR3d0Ho' },
        'SecondaryOwners' : []
    }
}
```

**注意**  
插入语句和其他 DML 语句，会返回每个受影响文档的 ID。继续操作之前，请保存此 ID，因为下一个主题中的历史记录功能需要它。您还可以通过下述查询找到文档 OD。  

```
SELECT r_id FROM VehicleRegistration AS r BY r_id
WHERE r.VIN = '1HVBBAANXWH544237'
```

```
UPDATE VehicleRegistration AS r 
SET r.City = 'Bellevue' 
WHERE r.VIN = '1HVBBAANXWH544237'
```

```
DELETE FROM VehicleRegistration AS r 
WHERE r.VIN = '1HVBBAANXWH544237'
```

有关这些 DML 语句语法的更多示例和信息，请参阅*Amazon QLDB PartiQL 参考*中的[UPDATE](ql-reference.update.md) 和 [DELETE](ql-reference.delete.md)。

要在文档中插入和删除特定元素，可以使用以`FROM`关键字开头的`UPDATE`语句或者其他 DML 语句。有关更多信息和示例，请参阅 [FROM（插入、删除或设置）](ql-reference.from.md) 参考。

删除文档后，您将无法再在已提交视图或用户视图中对其进行查询。若要了解如何使用内置历史记录功能查询本文档的修订历史记录，请继续 [查询修订历史记录](working.history.md)。

# 查询修订历史记录
<a name="working.history"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

Amazon QLDB 将每个文档的完整历史记录存储至一个表格中。通过查询内置的历史记录功能，您可以查看之前在[更新和删除文档](working.revisions.md)中插入、更新和删除的车辆登记文件的所有三个修订版。

**Topics**
+ [历史记录函数](#working.history.function)
+ [历史记录查询示例](#working.history.example)

## 历史记录函数
<a name="working.history.function"></a>

QLDB 中的历史函数是 PartiQL 扩展，它从表的系统定义视图返回修订版。因此，它将您的数据和关联元数据包含在与提交的视图相同的架构中。

**语法**

```
SELECT * FROM history( table_name | 'table_id' [, `start-time` [, `end-time` ] ] ) AS h
[ WHERE h.metadata.id = 'id' ]
```Arguments

***table\$1name* \$1 '*table\$1id*'**  
表名或表格 ID。表名是 PartiQL 标识符，可以用双引号或不用引号来表示。表 ID 必须用单引号将表文本括起来。要了解有关使用表格的更多信息 IDs，请参阅[查询非活动表历史记录](working.manage-tables.md#working.history.inactive-table)。

**`*start-time*`, `*end-time*`**  
（可选）指定所有处于活动状态的修订时间范围。*此参数并未指定在事务中向日记账提交修订的时间范围。*  
开始和结束时间是 Ion 时间戳文字，可以用反引号（``...``）表示。要了解更多信息，请参阅 [在 Amazon QLDB 中使用 PartiQL 查询 Ion](ql-reference.query.md)。  
这些时间参数包含以下行为：  
+ *开始时间*和*结束时间*均包含在内。它们必须采用[ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html)日期和时间格式，并使用通用协调时间（UTC）。
+ *开始时间*必须小于或等于*结束时间*，并且可以是过去任何任意日期。
+ *结束时间*必须小于或等于当前的 UTC 日期和时间。
+ 如果您指定*开始时间*，但未指定*结束时间*，则查询会将*结束时间*默认为当前日期和时间。如果两者都未指定，则您的查询将返回整个历史记录。

**'*id*'**  
（可选）若要查询其修订历史记录的文档 ID，用单引号表示。

**提示**  
最佳做法是，使用日期范围（*开始时间* 和 *结束时间*）和文档 ID（`metadata.id`）来限定历史记录查询。QLDB 中的每个 `SELECT` 查询都是在事务中处理的，并且受[事务超时限制](limits.md#limits.fixed)的约束。  
历史记录查询不使用您在表上创建的索引。QLDB 历史记录按文档 ID 编制索引，目前无法创建其他历史索引。包含开始时间和结束时间的历史记录查询将从日期范围限定中获得便利。

## 历史记录查询示例
<a name="working.history.example"></a>

要查询车辆登记文件的历史记录，请使用您之前在[更新和删除文档](working.revisions.md)中保存的`id`。例如，以下历史记录查询会返回在`2019-06-05T00:00:00Z`和`2019-06-05T23:59:59Z`之间处于活动状态的文档 ID `ADR2Ll1fGsU4Jr4EqTdnQF`的所有修订版。

**注意**  
切记，开始和结束时间参数并*未*指定在事务中向日记账提交修订的时间范围。例如，如果修订版本是在`2019-06-05T00:00:00Z`之前提交的，并且在该开始时间之后仍处于活动状态，则此示例查询将在结果中返回该修订版本。

请务必根据需要用自己的值替 换`id`、开始时间和结束时间。

```
SELECT * FROM history(VehicleRegistration, `2019-06-05T00:00:00Z`, `2019-06-05T23:59:59Z`) AS h
WHERE h.metadata.id = 'ADR2Ll1fGsU4Jr4EqTdnQF' --replace with your id
```

您的查询结果应类似于以下内容。

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{B2wYwrHKOWsmIBmxUgPRrTx9lv36tMlod2xVvWNiTbo=}},
    data: {
        VIN: "1HVBBAANXWH544237",
        LicensePlateNumber: "LS477D",
        State: "WA",
        City: "Tacoma",
        PendingPenaltyTicketAmount: 42.20,
        ValidFromDate: 2011-10-26T,
        ValidToDate: 2023-09-25T,
        Owners: {
            PrimaryOwner: { PersonId: "KmA3XPKKFqYCP2zhR3d0Ho" },
            SecondaryOwners: []
        }
    },
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
},
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:17
    },
    hash:{{LGSFZ4iEYWZeMwmAqcxxNyT4wbCtuMOmFCj8pEd6Mp0=}},
    data: {
        VIN: "1HVBBAANXWH544237",
        LicensePlateNumber: "LS477D",
        State: "WA",
        PendingPenaltyTicketAmount: 42.20,
        ValidFromDate: 2011-10-26T,
        ValidToDate: 2023-09-25T,
        Owners: {
            PrimaryOwner: { PersonId: "KmA3XPKKFqYCP2zhR3d0Ho" },
            SecondaryOwners: []
        },
        City: "Bellevue"
    },
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:1,
        txTime:2019-06-05T21:01:442d-3Z,
        txId:"9cArhIQV5xf5Tf5vtsPwPq"
    }
},
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:19
    },
    hash:{{7bm5DUwpqJFGrmZpb7h9wAxtvggYLPcXq+LAobi9fDg=}},
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:2,
        txTime:2019-06-05T21:03:76d-3Z,
        txId:"9GslbtDtpVHAgYghR5FXbZ"
    }
}
```

输出包含元数据属性，这些属性提供了有关每项何时修改以及由哪个事务修改的详细信息。从这些数据中，您可看到以下内容：
+ 该文档其系统分配的`id`唯一标识：`ADR2Ll1fGsU4Jr4EqTdnQF`。这是以 Base62 编码的字符串表示的 UUID。
+ `INSERT` 语句创建版本号为零的文档的初始修订版（版本 `0`）。
+  随后的每次更新都会创建一个具有相同文档 `id` 的新版本，并递增版本号。
+ 该 `txId` 字段表示提交每个修订的事务，`txTime`显示每个修订的提交时间。
+ `DELETE` 语句会创建文档的新修订版，也是最终修订版。此最终修订版仅仅包含元数据。

若要了解如何永久删除修订版，请继续 [对文档修订版执行编校](working.redaction.md)。

# 对文档修订版执行编校
<a name="working.redaction"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

在 Amazon QLDB，`DELETE` 语句只能通过创建将文档标记为已删除的新修订版，从逻辑上删除文档。QLDB 还支持*数据编校*操作，允许您永久删除表历史记录中的非活动文档修订版本。

**注意**  
2021 年 7 月 22 日之前创建的任何分类账目前都不符合编校资格。您可在 Amazon QLDB 控制台查看分类账的创建时间。

编校操作仅删除指定修订版中的用户数据，而日记账序列和文档元数据则保持不变。这样可保持分类账的整体数据完整性。

在开始在 QLDB 中进行数据编校之前，请务必在*Amazon QLDB PartiQL 参考*中查看[修订注意事项和限制](ql-stored-procedures.redact_revision.md#ql-stored-procedures.redact_revision.considerations)。

**Topics**
+ [编校存储过程](#working.redaction.stored-proc)
+ [检查编校是否已完成](#working.redaction.check-completion)
+ [编校示例](#working.redaction.example)
+ [删除和编校当前修订版本](#working.redaction.active-revision)
+ [编校修订版本中的特定字段](#working.redaction.field)

## 编校存储过程
<a name="working.redaction.stored-proc"></a>

您可以使用 [REDACT\$1REVISION](ql-stored-procedures.redact_revision.md) 存储过程永久删除分类账中单个非活动修订版本。此存储过程将删除索引存储和日志存储中指定修订版本中的所有用户数据。但是，它将使日记账序列和文档元数据（包括文档 ID 和哈希）保持不变。*该操作不可逆。*

指定的文档修订版本必须是在历史记录中处于非活动状态的修订版。文档的最新有效修订版不符合此编校条件。

若要编校多个修订版，必须为每个修订版运行一次存储进程。您可为每笔事务编校一个修订版。

**语法**

```
EXEC REDACT_REVISION `block-address`, 'table-id', 'document-id'
```Arguments

`*block-address*`  
要编校的文档修订版本的日记账数据块位置。地址是一种包含两个字段的 Amazon Ion 结构：`strandId` 和 `sequenceNo`。  
这是一个 Ion 字面值，以反引号表示。例如：  

```
`{strandId:"JdxjkR9bSYB5jMHWcI464T", sequenceNo:17}`
```

'*table-id*'  
待编校修订版本的表的唯一 ID，用单引号表示。

'*document-id*'  
待编校修订版本的唯一文档 ID，用单引号表示。

## 检查编校是否已完成
<a name="working.redaction.check-completion"></a>

通过运行此存储过程提交编校请求后，QLDB 将异步处理数据的编校。完成后，修订版中的用户数据（由 `data` 结构表示）将被永久删除。若要检查编校请求是否已完成，您可以使用以下方法之一：
+ [日记账导出](export-journal.md)
+ [日记账流](streams.md)
+ [GetBlock API 操作](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetBlock.html)
+ [GetRevision API 操作](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetRevision.html)
+ [历史记录函数](working.history.md#working.history.function)— **注意：** 在日记账中完成编校后，历史记录查询可能需要一段时间才能显示编校结果。异步编校完成后，您可能会看到一些修订版本在其他修订版本之前被编校，但是历史记录查询最终会显示已完成结果。

修订版本编校完成后，修订版本的 `data` 结构将被新`dataHash`字段所取代。该字段的值是已移除 `data` 结构的 Ion 哈希，如下例所示。因此，分类账保持了其整体数据的完整性，并通过现有验证 API 操作保持加密可验证性。要了解有关验证的更多信息，请参阅[Amazon QLDB 中的数据验证](verification.md)。

## 编校示例
<a name="working.redaction.example"></a>

以您之前在[查询修订历史记录](working.history.md)中查看过的车辆登记文件为例。假设您要编校第二个修订版（`version:1`）。以下查询示例显示了编校之前的此修订版。在查询结果中，将要编辑的`data`结构在中突出显示。*red italics*

```
SELECT * FROM history(VehicleRegistration) AS h
WHERE h.metadata.id = 'ADR2Ll1fGsU4Jr4EqTdnQF' --replace with your id
AND h.metadata.version = 1
```

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:17
    },
    hash:{{LGSFZ4iEYWZeMwmAqcxxNyT4wbCtuMOmFCj8pEd6Mp0=}},
    data: {
        VIN: "1HVBBAANXWH544237",
        LicensePlateNumber: "LS477D",
        State: "WA",
        PendingPenaltyTicketAmount: 42.20,
        ValidFromDate: 2011-10-26T,
        ValidToDate: 2023-09-25T,
        Owners: {
            PrimaryOwner: { PersonId: "KmA3XPKKFqYCP2zhR3d0Ho" },
            SecondaryOwners: []
        },
        City: "Bellevue"
    },
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:1,
        txTime:2019-06-05T21:01:442d-3Z,
        txId:"9cArhIQV5xf5Tf5vtsPwPq"
    }
}
```

请注意查询结果中的`blockAddress`，因为您需要将此值传递给`REDACT_REVISION`存储过程。然后，通过查询[系统目录](working.catalog.md)找到`VehicleRegistration` 表的唯一 ID，如下所示。

```
SELECT tableId FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

使用此表 ID 以及文档 ID 和数据块地址即可运行`REDACT_REVISION`。表 ID 和文档 ID 是字符串文字，必须用单引号括起来，数据块地址是用反引号括起的 Ion 文字。确保将这些参数替换为自己的值。

```
EXEC REDACT_REVISION `{strandId:"JdxjkR9bSYB5jMHWcI464T", sequenceNo:17}`, '5PLf9SXwndd63lPaSIa0O6', 'ADR2Ll1fGsU4Jr4EqTdnQF'
```

**提示**  
当您使用 QLDB 控制台或 QLDB Shell 查询表 ID 或文档 ID（或任何字符串文字值）时，返回的值用*双*引号括起来。但是，在指定`REDACT_REVISION`存储进程的表 ID 和文档 ID 参数时，必须用*单*引号将这些值括起来。  
这是因为您以 PartiQL 格式编写语句，但 QLDB 会以 Amazon Ion 格式返回结果。有关 QLDB 中 PartiQL 的语法和语义详细信息，请参阅[使用 PartiQL 查询 Ion](ql-reference.query.md)。

有效的编校请求会返回一个 Ion 结构，该结构表示您正在编校的文档修订版，如下所示。

```
{
  blockAddress: {
    strandId: "JdxjkR9bSYB5jMHWcI464T",
    sequenceNo: 17
  },
  tableId: "5PLf9SXwndd63lPaSIa0O6",
  documentId: "ADR2Ll1fGsU4Jr4EqTdnQF",
  version: 1
}
```

通过运行此存储过程后，QLDB 将异步处理的编校请求。编校完成后，该`data` 结构将被永久删除，并由一个新 *`dataHash`* 字段取而代之。该字段的值是已移除 `data` 结构的 Ion 哈希，如下所示。

**注意**  
此 `dataHash` 示例仅供参考，不是实际计算的哈希值。

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:17
    },
    hash:{{LGSFZ4iEYWZeMwmAqcxxNyT4wbCtuMOmFCj8pEd6Mp0=}},
    dataHash: {{s83jd7sfhsdfhksj7hskjdfjfpIPP/DP2hvionas2d4=}},
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:1,
        txTime:2019-06-05T21:01:442d-3Z,
        txId:"9cArhIQV5xf5Tf5vtsPwPq"
    }
}
```

## 删除和编校当前修订版本
<a name="working.redaction.active-revision"></a>

有效文档修订版（即每个文档中未删除的最新修订版）不符合数据编校的条件。必须先更新或删除当前修订版本，然后才能对其进行编校。这会将先前处于活动状态的修订版本移至历史记录，使其有资格进行编校。

如果您的用例要求将整个文档标记为已删除，则首先使用[DELETE](ql-reference.delete.md)语句。例如，以下语句在逻辑上删除 VIN为`1HVBBAANXWH544237`的`VehicleRegistration`文档。

```
DELETE FROM VehicleRegistration AS r
WHERE r.VIN = '1HVBBAANXWH544237'
```

然后，如前所述，在删除之前编校之前的修订版。如果需要，您也可以单独编校任何先前的修订版。

如果您的用例要求文档保持活动状态，则首先使用 [UPDATE](ql-reference.update.md) 或 [FROM](ql-reference.from.md) 语句来掩盖或删除要编校的字段。此过程将在以下部分中介绍。

## 编校修订版本中的特定字段
<a name="working.redaction.field"></a>

QLDB 不支持在文档修订版中编校特定字段。为此，您可以先使用[UPDATE-REMOVE](ql-reference.update.md) 或 [FROM-REMOVE](ql-reference.from.md)语句从修订版中移除现有字段。例如，使用以下语句移除 VIN 为`1HVBBAANXWH544237`的`VehicleRegistration`文档中的`LicensePlateNumber`字段。

```
UPDATE VehicleRegistration AS r
REMOVE r.LicensePlateNumber
WHERE r.VIN = '1HVBBAANXWH544237'
```

然后，如前所述，在删除之前编校之前的修订版。如果需要，您还可以单独编校包含此现已删除字段的任何先前修订版。

要了解如何优化查询，请继续 [优化查询性能](working.optimize.md)。

# 优化查询性能
<a name="working.optimize"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

Amazon QLDB 旨在满足高性能联机事务处理（OLTP）的工作负载需求。这意味着 QLDB 已针对一组特定查询模式进行了优化，尽管它支持类似 SQL 的查询功能。涉及应用程序及其数据模型对于使用此查询模式至关重要。否则，随着表的增长，您将遇到严重的性能问题，包括查询延迟、事务超时和并发冲突。

该章节描述了 Amazon QLDB 中的查询限制，并提供了在这些限制条件下优化查询性能指导。

**Topics**
+ [事务超时限制](#working.optimize.txn-timeout)
+ [并发冲突](#working.optimize.occ)
+ [最优查询模式](#working.optimize.patterns)
+ [要避免的查询模式](#working.optimize.avoid)
+ [监控性能](#working.optimize.monitor)

## 事务超时限制
<a name="working.optimize.txn-timeout"></a>

QLDB 中的每个 PartiQL 语句（包括每个 `SELECT` 查询）都是在事务中处理的，并且受[事务超时限制](limits.md#limits.fixed)的约束。在提交之前，事务最多可运行 **30 秒**。超过此限制后，QLDB 会拒绝在事务完成的任何工作，并丢弃运行该事务[会话](concurrency.md#concurrency.sessions)。此限制通过启动事务（而不提交或取消事务）以保护服务的客户端免遭泄漏会话。

## 并发冲突
<a name="working.optimize.occ"></a>

在 QLDB 中，并发控制通过*乐观并发控制*（OCC）实现。次优查询也可能导致更多 OCC 冲突。有关 OCC 的信息，请参阅[Amazon QLDB 并发模型](concurrency.md)。

## 最优查询模式
<a name="working.optimize.patterns"></a>

按最佳实践标准，您应运行带有`WHERE`谓词子句的语句，该子句可以筛选索引字段或文档 ID。QLDB 需要在索引字段上使用*相等*运算符（例如`=`或`IN`）才能高效地查找文档。

以下是 [用户视图](working.userdata.md) 中最佳查询模式的示例。

```
--Indexed field (VIN) lookup using the = operator
SELECT * FROM VehicleRegistration
WHERE VIN = '1N4AL11D75C109151'

--Indexed field (VIN) AND non-indexed field (City) lookup
SELECT * FROM VehicleRegistration
WHERE VIN = '1N4AL11D75C109151' AND City = 'Seattle'

--Indexed field (VIN) lookup using the IN operator
SELECT * FROM VehicleRegistration
WHERE VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')

--Document ID (r_id) lookup using the BY clause
SELECT * FROM VehicleRegistration BY r_id
WHERE r_id = '3Qv67yjXEwB9SjmvkuG6Cp'
```

任何不遵循这些模式的查询都会调用 *全表扫描*。表扫描可能会导致大型表上的查询，或返回大型结果集的查询发生事务超时。它们还可能[导致 OCC 与竞争事务发生冲突](concurrency.md#concurrency.indexes)。

**高基数索引**

我们建议为包含高基数值的字段建立索引。例如，`VehicleRegistration`表中的 `VIN` 和 `LicensePlateNumber` 字段是索引字段，旨在保持唯一性。

避免对低基数字段（例如状态代码、地址州或省以及邮政编码）建立索引。如果您为此类字段编制索引，则查询可能会生成较大的结果集，这些结果集 *更有可能导致事务超时或导致意外的 OCC 冲突*。

**已提交视图查询**

您在[已提交视图](working.metadata.md)中运行的查询遵循与用户视图查询相同的优化准则。您在表上创建的索引也可用于已提交视图中的查询。

**历史记录函数查询**

[历史记录函数](working.history.md)查询不使用您在表上创建的索引。QLDB 历史记录按文档 ID 编制索引，目前无法创建其他历史索引。

最佳做法是，使用日期范围（*开始时间* 和 *结束时间*）和文档 ID（`metadata.id`）来限定历史查询。包含开始时间和结束时间的历史记录查询将从日期范围限定中获得便利。

**内部联接查询**

对于内部联接查询，请使用至少包含联接右侧表的索引字段的联接条件。如果没有连接索引，连接查询将调用多个表扫描 - 对于连接左表中的每个文档，查询将完全扫描右表。除了为至少一个表指定`WHERE`相等谓词外，最佳做法是对要加入的每个表建立索引的字段进行联接。

例如，以下查询将 `VehicleRegistration`和`Vehicle`表连接至各自的字段，这两个`VIN`字段均已编制索引。此查询还有一个`VehicleRegistration.VIN`相等谓词。

```
SELECT * FROM VehicleRegistration AS r INNER JOIN Vehicle AS v
ON r.VIN = v.VIN
WHERE r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

为联接查询中的联接条件与相等谓词选择高基数索引。

## 要避免的查询模式
<a name="working.optimize.avoid"></a>

以下是**次优语句的示例，这些语**句不能很好地扩展 QLDB 中较大的表。我们强烈建议您不要依赖这些类型的查询来获取随时间增长的表，因为您的查询最终将导致事务超时。由于表包含大小不同的文档，因此很难为非索引查询定义精确的限制。

```
--No predicate clause
SELECT * FROM Vehicle

--COUNT() is not an optimized function
SELECT COUNT(*) FROM Vehicle

--Low-cardinality predicate
SELECT * FROM Vehicle WHERE Color = 'Silver'

--Inequality (>) does not qualify for indexed lookup
SELECT * FROM Vehicle WHERE "Year" > 2019

--Inequality (LIKE)
SELECT * FROM Vehicle WHERE VIN LIKE '1N4AL%'

--Inequality (BETWEEN)
SELECT SUM(PendingPenaltyTicketAmount) FROM VehicleRegistration
WHERE ValidToDate BETWEEN `2020-01-01T` AND `2020-07-01T`

--No predicate clause
DELETE FROM Vehicle

--No document id, and no date range for the history() function
SELECT * FROM history(Vehicle)
```

通常，我们*不建议*在 QLDB 中为生产用例运行以下类型查询模式：
+ 联机分析处理（OLAP）查询
+ 没有谓词子句的探索性查询
+ 报告查询
+ 文本搜索

相反，我们建议将您的数据流式传输到专门构建的数据库服务，该服务针对分析用例进行了优化。例如，您可以将 QLDB 数据流式传输到 A OpenSearch mazon 服务，以提供对文档的全文搜索功能。有关演示此用例的示例应用程序，请参阅 GitHub 存储库 [aws-sam amazon-qldb-streaming-amazon](https://github.com/aws-samples/amazon-qldb-streaming-amazon-opensearch-service-sample-python) ples/-。opensearch-service-sample-python有关 QLDB 流的信息，请参阅 [Amazon QLDB 流式传输日记账数据](streams.md)。

## 监控性能
<a name="working.optimize.monitor"></a>

QLDB 驱动程序在语句结果对象中提供消耗的 I/O 使用情况和计时信息。您可使用这些指标识别效率低下的 PartiQL 语句。要了解更多信息，请继续 [获取 PartiQL 语句统计信息](working.statement-stats.md)。

您还可以使用 Amazon CloudWatch 来跟踪账本在数据操作方面的表现。监控指定`LedgerName` 和 `CommandType`的`CommandLatency`指标。有关更多信息，请参阅 [使用 Amazon 进行监控 CloudWatch](monitoring-cloudwatch.md)。要了解 QLDB 如何使用命令管理数据操作，请参阅[驱动程序会话管理](driver-session-management.md)。

# 获取 PartiQL 语句统计信息
<a name="working.statement-stats"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

Amazon QLDB 提供语句执行统计信息，可帮助您通过运行更高效的 PartiQL 语句来优化 QLDB 的使用。QLDB 将这些统计信息与语句结果一起返回。它们包含量化消耗的 I/O 使用情况和服务器端处理时间的指标，您可以使用这些指标来识别低效的语句。

此功能当前用于[QLDB 控制台](console_QLDB.md)、 [QLDB Shell](data-shell.md)以及最新版 [QLDB 驱动程序](getting-started-driver.md)（所有语言）的*PartiQL 编辑器*。您还可在控制台上查看查询历史的语句统计信息。

**Topics**
+ [I/O 使用率](#statement-stats.io-usage)
+ [计时信息](#statement-stats.timing-information)

## I/O 使用率
<a name="statement-stats.io-usage"></a>

I/O 使用率指标介绍了读取 I/O 请求的数量。如果读取 I/O 请求的数量高于预期，则表示语句未经优化，例如缺少索引。我们建议您在上一主题 *优化查询性能* 中复习[最优查询模式](working.optimize.md#working.optimize.patterns)。

**注意**  
在非空表上运行`CREATE INDEX`语句时，I/O 使用率指标仅包含同步索引创建调用的读取请求。  
QLDB 异步为表中的任何现有文档创建索引。这些异步读取请求不包括在语句结果的 I/O 使用率指标中。异步读取请求单独收费，且在索引构建完成后计入您的读取 I/O 总数。

### 使用 QLDB 控制台
<a name="statement-stats.io-usage.con"></a>

若要使用 QLDB 控制台获取语句的读取 I/O 使用情况，请执行以下步骤：

1. [在 /qldb 上打开亚马逊 QLDB 控制台。https://console.aws.amazon.com](https://console.aws.amazon.com/qldb)

1. 在导航窗格中，选择 **‬PartiQL 编辑器**。

1. 从分类账下拉列表中选择分类帐。

1. 在查询编辑器窗口中，输入您选择的任何语句，然后选择 **运行**。以下是查询示例。

   ```
   SELECT * FROM testTable WHERE firstName = 'Jim'
   ```

   要运行语句，你可对 Windows 使用快捷键Ctrl\$1Enter，对 macOS 使用Cmd\$1Return。有关更多键盘快捷键的信息，请参阅 ‭[PartiQL 编辑器键盘快捷键](console_QLDB.md#console_QLDB.partiql-editor-shortcuts)。

1. 在查询编辑器窗口下方，您的查询结果包括*读取 I/O*，此语句发出的读取请求数。

您还可通过执行以下步骤来查看查询历史记录的读取 I/O：

1. 在导航窗格中，选择**PartiQL 编辑器**下的**最新查询**。

1. **读取 I/O** 列显示每条语句发出的读取请求数。

### 使用 QLDB 驱动程序
<a name="statement-stats.io-usage.driver"></a>

要使用 QLDB 驱动程序获取语句 I/O 使用情况，请调用结果的流指针或缓冲指针的`getConsumedIOs`操作。

以下代码示例显示如何从语句结果的*流指针* 中读取 I/O。

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.IOUsage;
import software.amazon.qldb.Result;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

driver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    for (IonValue ionValue : result) {
        // User code here to handle results
    }

    IOUsage ioUsage = result.getConsumedIOs();
    long readIOs = ioUsage.getReadIOs();
});
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

// This is one way of creating Ion values. We can also use a ValueFactory.
// For more details, see: https://docs.aws.amazon.com/qldb/latest/developerguide/driver-cookbook-dotnet.html#cookbook-dotnet.ion
IIonValue ionFirstName = IonLoader.Default.Load("Jim");

await driver.Execute(async txn =>
{
    IAsyncResult result = await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    // Iterate through stream cursor to accumulate read IOs.
    await foreach (IIonValue ionValue in result)
    {
        // User code here to handle results.
        // Warning: It is bad practice to rely on results within a lambda block, unless
        // it is to check the state of a result. This is because lambdas are retryable.
    }

    var ioUsage = result.GetConsumedIOs();
    var readIOs = ioUsage?.ReadIOs;
});
```

**注意**  
要转换为同步代码，请移除 `await` 和 `async` 关键字，然后将 `IAsyncResult` 类型更改为 `IResult`。

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    if err != nil {
        panic(err)
    }

    for result.Next(txn) {
        // User code here to handle results
    }

    ioUsage := result.GetConsumedIOs()
    readIOs := *ioUsage.GetReadIOs()
    fmt.Println(readIOs)
    return nil,nil
})
```

------
#### [ Node.js ]

```
import { IOUsage, ResultReadable, TransactionExecutor } from "amazon-qldb-driver-nodejs";

await driver.executeLambda(async (txn: TransactionExecutor) => {
    const result: ResultReadable = await txn.executeAndStreamResults("SELECT * FROM testTable WHERE firstName = ?", "Jim");

    for await (const chunk of result) {
        // User code here to handle results
    }

    const ioUsage: IOUsage = result.getConsumedIOs();
    const readIOs: number = ioUsage.getReadIOs();
});
```

------
#### [ Python ]

```
def get_read_ios(transaction_executor):
    cursor = transaction_executor.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    for row in cursor:
        # User code here to handle results
        pass

    consumed_ios = cursor.get_consumed_ios()
    read_ios = consumed_ios.get('ReadIOs')

qldb_driver.execute_lambda(lambda txn: get_read_ios(txn))
```

------

以下代码示例显示如何从语句结果的*缓冲指针*中读取 I/O。这将返回来自 `ExecuteStatement` 和`FetchPage` 请求的读取 I/O 总数。

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.IOUsage;
import software.amazon.qldb.Result;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

Result result = driver.execute(txn -> {
    return txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

IOUsage ioUsage = result.getConsumedIOs();
long readIOs = ioUsage.getReadIOs();
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

IIonValue ionFirstName = IonLoader.Default.Load("Jim");

IAsyncResult result = await driver.Execute(async txn =>
{
    return await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

var ioUsage = result.GetConsumedIOs();
var readIOs = ioUsage?.ReadIOs;
```

**注意**  
要转换为同步代码，请移除 `await` 和 `async` 关键字，然后将 `IAsyncResult` 类型更改为 `IResult`。

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err :=  txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")
    if err != nil {
        return nil, err
    }
    return txn.BufferResult(result)
})

if err != nil {
    panic(err)
}

qldbResult := result.(*qldbdriver.BufferedResult)
ioUsage := qldbResult.GetConsumedIOs()
readIOs := *ioUsage.GetReadIOs()
fmt.Println(readIOs)
```

------
#### [ Node.js ]

```
import { IOUsage, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";

const result: Result = await driver.executeLambda(async (txn: TransactionExecutor) => {
    return await txn.execute("SELECT * FROM testTable WHERE firstName = ?", "Jim");
});

const ioUsage: IOUsage = result.getConsumedIOs();
const readIOs: number = ioUsage.getReadIOs();
```

------
#### [ Python ]

```
cursor = qldb_driver.execute_lambda(
    lambda txn: txn.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim"))

consumed_ios = cursor.get_consumed_ios()
read_ios = consumed_ios.get('ReadIOs')
```

------

**注意**  
流指针是有状态的，因为它会对结果集分页。因此，`getConsumedIOs` 和`getTimingInformation`操作会返回您调用这些指标时的累积指标。  
缓冲后的指针将结果集缓存至内存中，并返回累积的指标总数。

## 计时信息
<a name="statement-stats.timing-information"></a>

计时信息指标描述了服务器端处理时间（以毫秒为单位）。服务器端处理时间定义为 QLDB 在处理语句上所需时间。这不包括网络通话或暂停时间。该指标将 QLDB 服务端的处理时间与客户端处理时间区分开来。

### 使用 QLDB 控制台
<a name="statement-stats.timing-information.con"></a>

要使用 QLDB 控制台获取语句时序信息，请执行以下步骤：

1. [在 /qldb 上打开亚马逊 QLDB 控制台。https://console.aws.amazon.com](https://console.aws.amazon.com/qldb)

1. 在导航窗格中，选择 **‬PartiQL 编辑器**。

1. 从分类账下拉列表中选择分类帐。

1. 在查询编辑器窗口中，输入您选择的任何语句，然后选择 **运行**。以下是查询示例。

   ```
   SELECT * FROM testTable WHERE firstName = 'Jim'
   ```

   要运行语句，你可对 Windows 使用快捷键Ctrl\$1Enter，对 macOS 使用Cmd\$1Return。有关更多键盘快捷键的信息，请参阅 ‭[PartiQL 编辑器键盘快捷键](console_QLDB.md#console_QLDB.partiql-editor-shortcuts)。

1. 在查询编辑器窗口下方，您的查询结果包括*服务器端延迟*，即 QLDB 收到语句请求与发送响应之间的时间长度。这是总查询时长子集。

您还可通过执行以下步骤来查看查询历史记录的计时信息：

1. 在导航窗格中，选择**PartiQL 编辑器**下的**最新查询**。

1. **执行时间（ms）**列将会显示每条语句的计时信息。

### 使用 QLDB 驱动程序
<a name="statement-stats.timing-information.driver"></a>

要使用 QLDB 驱动程序获取语句的计时信息，请调用结果的流指针或缓冲指针的`getTimingInformation`操作。

以下代码示例显示如何从语句结果的*流指针* 中获取处理时间。

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.Result;
import software.amazon.qldb.TimingInformation;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

driver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    for (IonValue ionValue : result) {
        // User code here to handle results
    }

    TimingInformation timingInformation = result.getTimingInformation();
    long processingTimeMilliseconds = timingInformation.getProcessingTimeMilliseconds();
});
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

IIonValue ionFirstName = IonLoader.Default.Load("Jim");

await driver.Execute(async txn =>
{
    IAsyncResult result = await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    // Iterate through stream cursor to accumulate processing time.
    await foreach(IIonValue ionValue in result)
    {
        // User code here to handle results.
        // Warning: It is bad practice to rely on results within a lambda block, unless
        // it is to check the state of a result. This is because lambdas are retryable.
    }

    var timingInformation = result.GetTimingInformation();
    var processingTimeMilliseconds = timingInformation?.ProcessingTimeMilliseconds;
});
```

**注意**  
要转换为同步代码，请移除 `await` 和 `async` 关键字，然后将 `IAsyncResult` 类型更改为 `IResult`。

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    if err != nil {
        panic(err)
    }

    for result.Next(txn) {
        // User code here to handle results
    }

    timingInformation := result.GetTimingInformation()
    processingTimeMilliseconds := *timingInformation.GetProcessingTimeMilliseconds()
    fmt.Println(processingTimeMilliseconds)
    return nil, nil
})
```

------
#### [ Node.js ]

```
import { ResultReadable, TimingInformation, TransactionExecutor } from "amazon-qldb-driver-nodejs";

await driver.executeLambda(async (txn: TransactionExecutor) => {
    const result: ResultReadable = await txn.executeAndStreamResults("SELECT * FROM testTable WHERE firstName = ?", "Jim");

    for await (const chunk of result) {
        // User code here to handle results
    }

    const timingInformation: TimingInformation = result.getTimingInformation();
    const processingTimeMilliseconds: number = timingInformation.getProcessingTimeMilliseconds();
});
```

------
#### [ Python ]

```
def get_processing_time_milliseconds(transaction_executor):
    cursor = transaction_executor.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    for row in cursor:
        # User code here to handle results
        pass

    timing_information = cursor.get_timing_information()
    processing_time_milliseconds = timing_information.get('ProcessingTimeMilliseconds')

qldb_driver.execute_lambda(lambda txn: get_processing_time_milliseconds(txn))
```

------

以下代码示例显示如何从语句结果的 *缓冲指针* 中获取处理时间。这将返回来自 `ExecuteStatement`和`FetchPage` 请求的总处理时间。

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.Result;
import software.amazon.qldb.TimingInformation;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

Result result = driver.execute(txn -> {
    return txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

TimingInformation timingInformation = result.getTimingInformation();
long processingTimeMilliseconds = timingInformation.getProcessingTimeMilliseconds();
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

IIonValue ionFirstName = IonLoader.Default.Load("Jim");

IAsyncResult result = await driver.Execute(async txn =>
{
    return await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

var timingInformation = result.GetTimingInformation();
var processingTimeMilliseconds = timingInformation?.ProcessingTimeMilliseconds;
```

**注意**  
要转换为同步代码，请移除 `await` 和 `async` 关键字，然后将 `IAsyncResult` 类型更改为 `IResult`。

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err :=  txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")
    if err != nil {
        return nil, err
    }
    return txn.BufferResult(result)
})

if err != nil {
    panic(err)
}

qldbResult := result.(*qldbdriver.BufferedResult)
timingInformation := qldbResult.GetTimingInformation()
processingTimeMilliseconds := *timingInformation.GetProcessingTimeMilliseconds()
fmt.Println(processingTimeMilliseconds)
```

------
#### [ Node.js ]

```
import { Result, TimingInformation, TransactionExecutor } from "amazon-qldb-driver-nodejs";

const result: Result = await driver.executeLambda(async (txn: TransactionExecutor) => {
    return await txn.execute("SELECT * FROM testTable WHERE firstName = ?", "Jim");
});

const timingInformation: TimingInformation = result.getTimingInformation();
const processingTimeMilliseconds: number = timingInformation.getProcessingTimeMilliseconds();
```

------
#### [ Python ]

```
cursor = qldb_driver.execute_lambda(
    lambda txn: txn.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim"))

timing_information = cursor.get_timing_information()
processing_time_milliseconds = timing_information.get('ProcessingTimeMilliseconds')
```

------

**注意**  
流指针是有状态的，因为它会对结果集分页。因此，`getConsumedIOs` 和`getTimingInformation`操作会返回您调用这些指标时的累积指标。  
缓冲后的指针将结果集缓存至内存中，并返回累积的指标总数。

要了解如何查询系统目录，请继续 [查询系统目录](working.catalog.md)。

# 查询系统目录
<a name="working.catalog"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

您在 Amazon QLDB 分类账中创建的每个表都有一个系统分配的唯一 ID。您可通过查询系统目录表`information_schema.user_tables`来查找表的 ID、其索引列表和其他元数据。

系统分配的所有标识符 IDs 都是通用唯一标识符 (UUID)，每个标识符都以 Base62 编码的字符串表示。有关更多信息，请参阅 [IDs 在亚马逊 QLDB 中独树一帜](working.unique-id.md)。

以下示例显示返回 `VehicleRegistration` 表元数据属性的查询结果。

```
SELECT * FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

```
{
    tableId: "5PLf9SXwndd63lPaSIa0O6",
    name: "VehicleRegistration",
    indexes: [
        { indexId: "Djg2nt0yIs2GY0T29Kud1z", expr: "[VIN]", status: "ONLINE" },
        { indexId: "4tPW3fUhaVhDinRgKRLhGU", expr: "[LicensePlateNumber]", status: "BUILDING" }
    ],
    status: "ACTIVE"
}
```

**表的元数据字段**
+ `tableId` – 表的唯一 ID。
+ `name` – 表的名称。
+ `indexes` — 表中的索引列表。
  + `indexId` – 索引的唯一 ID。
  + `expr` — 已编入索引的文档路径。该字段采用的字符串格式为：`[fieldName]`。
  + `status` — 索引的当前状态（`BUILDING`、`FINALIZING`、`ONLINE`、`FAILED`、或`DELETING`）。在状态为`ONLINE`之前，QLDB 不会在查询中使用该索引。
  + `message` — 描述索引`FAILED`处于状态的原因的错误消息。仅在失败的索引中包含此字段。
+ `status` — 表格的当前状态（`ACTIVE`或`INACTIVE`）。当您`DROP`时表格成为`INACTIVE`。

要了解如何使用 `DROP TABLE` 和 `UNDROP TABLE` 语句管理表，请继续[管理表](working.manage-tables.md)。

# 管理表
<a name="working.manage-tables"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

本节介绍如何在 Amazon QLDB 中使用 `DROP TABLE` 和 `UNDROP TABLE` 语句管理表。它还描述了如何在创建表时对其标记。您可以创建的活动表数量和表总数的限额在[Amazon QLDB 资源中的限额和限制](limits.md#limits.fixed)中定义。

**Topics**
+ [创建时对表格进行标记](#working.manage-tables.tags)
+ [删除表格](#working.manage-tables.drop)
+ [查询非活动表历史记录](#working.history.inactive-table)
+ [重新激活表格](#working.history.undrop-table)

## 创建时对表格进行标记
<a name="working.manage-tables.tags"></a>

**注意**  
 只有 `STANDARD` 权限模式分类账支持在创建时对表格进行标记。

您可以标记您的资源。要管理现有表标签，请使用 AWS 管理控制台 或 API 操作 `TagResource`、`UntagResource`、和 `ListTagsForResource`。有关更多信息，请参阅 [为 Amazon QLDB 资源贴标签](tagging.md)。

您还可以在创建表时使用 QLDB 控制台或在 `CREATE TABLE`PartiQL 语句中指定表标签来定义表标签。下面的示例创建了一个名为 `Vehicle` 的表，带有标记 `environment=production`。

```
CREATE TABLE Vehicle WITH (aws_tags = `{'environment': 'production'}`)
```

通过在创建资源时对其进行标记，无需在创建资源后运行自定义标记脚本。标记表后，您可根据这些标签来控制对表的访问。例如：您只能向具有特定标签的表授予完全访问权限。有关 JSON 策略示例，请参阅[基于表格标签对所有操作的完全访问权限](security_iam_id-based-policy-examples.md#security_iam_id-based-policy-examples-full-tags)。

## 删除表格
<a name="working.manage-tables.drop"></a>

要删除表，请使用基本 [DROP TABLE](ql-reference.drop-table.md) 语句。当你在 QLDB 中删除表格时，只是将其停用。

例如，以下语句使`VehicleRegistration`表处于停用状态。

```
DROP TABLE VehicleRegistration
```

`DROP TABLE`语句返回系统分配的表的 ID。状态`VehicleRegistration`应该`INACTIVE`在系统目录表 information\$1schema.user [\$1tables](working.catalog.md) 中。

```
SELECT status FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

## 查询非活动表历史记录
<a name="working.history.inactive-table"></a>

除了表名之外，您还可以使用表 ID 作为第一个输入参数查询QLDB[历史记录函数](working.history.md#working.history.function) 。必须使用表 ID 查询非活动表的历史记录。停用表后，您将无法再使用表名查询历史记录。

首先，通过查询系统目录表查找表 ID。例如，以下查询将返回 `VehicleRegistration` 表的 `tableId`。

```
SELECT tableId FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

然后，您可使用此 ID 从[查询修订历史记录](working.history.md)中运行相同的历史记录查询。以下是从表 ID`5PLf9SXwndd63lPaSIa0O6` 中查询文档 ID`ADR2Ll1fGsU4Jr4EqTdnQF` 历史记录的示例。表 ID 为字符串文本值，必须括至单引号内。

```
--replace both the table and document IDs with your values
SELECT * FROM history('5PLf9SXwndd63lPaSIa0O6', `2000T`, `2019-06-05T23:59:59Z`) AS h
WHERE h.metadata.id = 'ADR2Ll1fGsU4Jr4EqTdnQF'
```

## 重新激活表格
<a name="working.history.undrop-table"></a>

在 QLDB 中停用表后，可以使用[取消删除表](ql-reference.undrop-table.md)语句将其重新激活。

首先，从`information_schema.user_tables`中找到表 ID。例如，以下查询将返回 `VehicleRegistration` 表的 `tableId`。状态应为 `INACTIVE`。

```
SELECT tableId FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

然后使用此 ID 重新激活表。以下是*取消*表 ID`5PLf9SXwndd63lPaSIa0O6` 的示例。此时，表 ID 是用双引号括起的唯一标识符。

```
UNDROP TABLE "5PLf9SXwndd63lPaSIa0O6"
```

现在的`VehicleRegistration`状态应该是`ACTIVE`。

若要了解如何创建、描述和删除索引，请继续 [管理索引](working.manage-indexes.md)。

# 管理索引
<a name="working.manage-indexes"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

本部分介绍如何在 Amazon QLDB 中创建、描述和删除索引。可以为每个表创建的索引数量的限额在[Amazon QLDB 资源中的限额和限制](limits.md#limits.fixed)中定义。

**Topics**
+ [创建索引](#working.manage-indexes.create)
+ [描述索引](#working.manage-indexes.describe)
+ [删除索引](#working.manage-indexes.drop)
+ [常见错误](#working.manage-indexes.errors)

## 创建索引
<a name="working.manage-indexes.create"></a>

如[创建表与索引](working.create.md#working.create.tables-indexes)中所述，您可以使用 [CREATE INDEX](ql-reference.create-index.md) 语句在表上为指定的顶级字段创建索引，如下所示。表名和索引字段名称均为区分大小写。

```
CREATE INDEX ON VehicleRegistration (VIN)
```

```
CREATE INDEX ON VehicleRegistration (LicensePlateNumber)
```

您在表上创建的每个索引都包含一个系统分配的唯一 ID。若要查找此索引 ID，请参阅以下部分 [描述索引](#working.manage-indexes.describe)。

**重要**  
QLDB 需要索引才能高效查找文档。如果没有索引，QLDB 在读取文档时需进行全表扫描。这可能会导致大型表出现性能问题，包括并发冲突与事务超时。  
为避免表扫描，必须在索引字段或文档 ID 上使用*相等*运算符（`=`或`IN`）运行带有`WHERE`谓词子句的语句。有关更多信息，请参阅 [优化查询性能](working.optimize.md)。

创建索引时应注意以下限制：
+ 只能在单个顶级字段创建索引。不支持复合索引、嵌套索引、唯一索引以及基于函数的索引。
+ 您可以为任何 [Ion 数据类型](ql-reference.data-types.md)创建索引，其中包括 `list` 和 `struct`。但是，无论 Ion 类型如何，您都只能通过整个 Ion 值进行索引查找。例如，使用 `list` 类型作为索引时，不能按列表中的一个项目进行索引查找。
+ 只有使用相等谓词时，查询性能才会得到改善；例如`WHERE indexedField = 123` 或 `WHERE indexedField IN (456, 789)`。

  QLDB 不支持查询谓词不等式。因此，未实施范围过滤扫描。
+ 索引字段名称区分大小写，且最大长度可为 128 个字符。
+ 在 QLDB 中创建索引具有异步特点。非空表上完成索引所需的时间取决于表的大小。有关更多信息，请参阅 [管理索引](#working.manage-indexes)。

## 描述索引
<a name="working.manage-indexes.describe"></a>

在 QLDB 中创建索引具有异步特点。非空表上完成索引所需的时间取决于表的大小。要检查索引构建的状态，可以查询系统目录表[information\$1schema.user\$1tables](working.catalog.md)。

例如，以下语句在系统目录中查询 `VehicleRegistration` 表上的所有索引。

```
SELECT VALUE indexes
FROM information_schema.user_tables info, info.indexes indexes
WHERE info.name = 'VehicleRegistration'
```

```
{
    indexId: "Djg2nt0yIs2GY0T29Kud1z",
    expr: "[VIN]",
    status: "ONLINE"
},
{
    indexId: "4tPW3fUhaVhDinRgKRLhGU",
    expr: "[LicensePlateNumber]",
    status: "FAILED",
    message: "aws.ledger.errors.InvalidEntityError: Document contains multiple values for indexed field: LicensePlateNumber"
}
```

**索引字段数**
+ `indexId` – 索引的唯一 ID。
+ `expr` — 已编入索引的文档路径。该字段采用的字符串格式为：`[fieldName]`。
+ `status` — 索引的当前状态。索引的状态可以是以下值之一：
  + `BUILDING` — 正在积极为表建立索引。
  + `FINALIZING` — 已完成索引的构建并开始激活它以供使用。
  + `ONLINE` — 处于活动状态，可在查询中使用。在状态为在线之前，QLDB 不会在查询中使用该索引。
  + `FAILED`— 由于出现不可恢复的错误，无法建立索引。此状态的索引仍计入每个表的索引限额。有关更多信息，请参阅 [常见错误](#working.manage-indexes.errors)。
  + `DELETING` — 用户删除索引后正在主动删除索引。
+ `message` — 描述索引`FAILED`处于状态的原因的错误消息。仅在失败的索引中包含此字段。

### 使用 控制台
<a name="working.manage-indexes.describe.con"></a>

您也可以使用 AWS 管理控制台 来检查索引的状态。

**检查索引的状态（控制台）**

1. [登录并打开亚马逊 QLDB 控制台，网址为 /qldb。 AWS 管理控制台 https://console.aws.amazon.com](https://console.aws.amazon.com/qldb)

1. 在导航窗格中，选择**分类账**。

1. 在**分类帐**列表中，选择要管理其索引的分类帐名称。

1. 在分类账详细信息页面的**表格**选项卡下，选择要检查其索引的表名。

1. 在表格详细信息页面上，找到 **已编入索引的字段** 卡片。**索引状态** 列显示表中每个索引的当前状态。

## 删除索引
<a name="working.manage-indexes.drop"></a>

使用 [DROP INDEX](ql-reference.drop-index.md) 语句删除索引。删除索引时，该索引会从表中永久删除。

首先，从`information_schema.user_tables`中找到索引 ID。例如，以下查询返回`VehicleRegistration`表中已编入索引`LicensePlateNumber`字段的`indexId`。

```
SELECT indexes.indexId
FROM information_schema.user_tables info, info.indexes indexes
WHERE info.name = 'VehicleRegistration' and indexes.expr = '[LicensePlateNumber]'
```

然后，使用此 ID 删除索引。以下是取消表 ID `4tPW3fUhaVhDinRgKRLhGU`的示例。此时，索引 ID 是用双引号括起的唯一标识符。

```
DROP INDEX "4tPW3fUhaVhDinRgKRLhGU" ON VehicleRegistration WITH (purge = true)
```

**注意**  
该子句 `WITH (purge = true)` 是所有 `DROP INDEX` 语句所必需的，并且 `true` 是目前唯一支持的值。  
关键字`purge`区分大小写，并且必须为全小写。

### 使用 控制台
<a name="working.manage-indexes.drop.con"></a>

也可以使用 AWS 管理控制台 删除索引。

**删除索引（控制台）**

1. [登录并打开亚马逊 QLDB 控制台，网址为 /qldb。 AWS 管理控制台 https://console.aws.amazon.com](https://console.aws.amazon.com/qldb)

1. 在导航窗格中，选择**分类账**。

1. 在**分类帐**列表中，选择要管理其索引的分类帐名称。

1. 在分类账详细信息页面的**表格**选项卡下，选择要删除其索引的表名。

1. 在表格详细信息页面上，找到 **已编入索引的字段** 卡片。选择要删除的索引，然后选择 **删除索引**。

## 常见错误
<a name="working.manage-indexes.errors"></a>

本节介绍创建索引时可能遇到的常见错误，并建议可能的解决方案。

**注意**  
处于这种`FAILED`状态的索引仍计入每个表的索引限额。失败的索引还会阻止您修改或删除导致在表上创建索引失败的任何文档。  
您必须明确[删除](#working.manage-indexes.drop) 索引才能将其从限额中删除。

**文档包含索引字段的多个值:*fieldName*.**  
QLDB 无法为指定字段名建立索引，因为该表包含一个文档，该文档具有同一个字段的多个值（即重复的字段名）。  
必须先删除失败索引。然后，在重试创建索引前，请确保表中的所有文档的每个字段名只有一个值。您也可以为没有重复项的字段创建索引。  
如果您尝试插入的文档包含已在表格上编制索引字段的多个值，QLDB 也会返回此错误。

**已超过索引限制：表*tableName*已有*n*索引，无法创建更多索引。**  
QLDB 强制每个表最多只能包含五个索引，包括失败的索引。在创建新索引之前，必须要删除现有索引。

**未定义标识符为:的索引*indexId*。**  
您试图删除指定的表和索引 ID 组合中不包含的索引。若要了解如何检查现有索引，请参阅[描述索引](#working.manage-indexes.describe)。

# IDs 在亚马逊 QLDB 中独树一帜
<a name="working.unique-id"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

本节介绍了 Amazon QLDB 中系统分配唯一标识符的属性和使用指南。它还提供了一些QLDB独有的示例。 IDs

**Topics**
+ [属性](#working.unique-id.properties)
+ [使用量](#working.unique-id.usage)
+ [示例](#working.unique-id.examples)

## 属性
<a name="working.unique-id.properties"></a>

QLDB IDs 中所有系统分配的都是通用唯一标识符 (UUID)。每个 ID 具有以下属性：
+ 128 位 UUID 号
+ 以 Base62 编码的文本表示
+ 22 个字符的固定长度字母数字的字符串（例如`3Qv67yjXEwB9SjmvkuG6Cp`）

## 使用量
<a name="working.unique-id.usage"></a>

在应用程序中使用 IDs 唯一的 QLDB 时，请注意以下准则：

**Do**
+ 将 ID 视为字符串。

**切勿**
+ 尝试解码字符串。
+ 将语义含义归因于字符串（如推导时间分量）。
+ 按语义顺序对字符串排序。

## 示例
<a name="working.unique-id.examples"></a>

以下是 QLDB IDs 中唯一属性的一些示例：
+ 文档 ID
+ 索引 ID
+ Strand ID
+ 表 ID
+ 事务 ID