

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

# 适用于 Python 的 Amazon QLDB 驱动程序
<a name="getting-started.python"></a>

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

要处理账本中的数据，您可以使用提供的驱动程序从 Python 应用程序连接到 Amazon QLDB。 AWS 以下主题介绍了如何开始使用适用于 Python 的 QLDB 驱动程序。

**Topics**
+ [

## 驱动程序资源
](#getting-started.python.resources)
+ [

## 先决条件
](#getting-started.python.prereqs)
+ [

## 安装
](#getting-started.python.install)
+ [快速入门教程](driver-quickstart-python.md)
+ [说明书参考](driver-cookbook-python.md)

## 驱动程序资源
<a name="getting-started.python.resources"></a>

有关 Python 驱动程序支持功能的更多信息，请参阅以下资源：
+ API 参考：[3.x](https://amazon-qldb-driver-python.readthedocs.io/en/latest/), [2.x](https://amazon-qldb-driver-python.readthedocs.io/en/v2.0.2/)
+ [驱动程序源代码 (GitHub)](https://github.com/awslabs/amazon-qldb-driver-python)
+ [示例应用程序源代码 (GitHub)](https://github.com/aws-samples/amazon-qldb-dmv-sample-python)
+ [Amazon Ion 代码示例](ion.code-examples.md)

## 先决条件
<a name="getting-started.python.prereqs"></a>

开始使用适用于 Python 的 QLDB 驱动程序之前，您必须执行以下操作:

1. 按照中的 AWS 设置说明进行操作[访问 Amazon QLDB](accessing.md)。这包括以下这些：

   1. 报名参加 AWS.

   1. 创建具有适当 QLDB 权限的用户。

   1. 授权以编程方式访问开发。

1. 从 [Python 下载](https://www.python.org/downloads/)网站下载并安装以下 Python 版本之一：
   + **3.6 或更高版本 **- 适用于 Python v3 的 QLDB 驱动程序
   + **3.4 或更高版本 **- 适用于 Python v2 的 QLDB 驱动程序

1. 设置您的 AWS 凭据和默认凭证 AWS 区域。有关说明，请参阅 适用于 Python (Boto3) 的 AWS SDK 文档中的[快速入门](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration)。

   有关可用区域的完整列表，请参阅 *AWS 一般参考* 中的 [Amazon QLDB 端点和限额](https://docs.aws.amazon.com/general/latest/gr/qldb.html)。

接下来，您可下载完整的教程示例应用程序，也可以只在 Python 项目中安装驱动程序并运行短代码示例。
+ 要在现有项目中安装 QLDB 驱动程序和 适用于 Python (Boto3) 的 AWS SDK ，请继续。[安装](#getting-started.python.install)
+ 要设置项目并运行演示分类账上基本数据事务的简短代码示例，请参阅 [快速入门教程](driver-quickstart-python.md)。
+ 要在完整的教程示例应用程序中运行更深入的数据和管理 API 操作示例，请参阅 [Python 教程](getting-started.python.tutorial.md)。

## 安装
<a name="getting-started.python.install"></a>

QLDB 支持以下驱动程序版本及其 Python 依赖项。


****  

| 驱动程序版本 | Python 版本 | 状态 | 最新发布日期 | 
| --- | --- | --- | --- | 
| [2.x](https://pypi.org/project/pyqldb/2.0.2/) | 3.4 或更高版本 | 量产版 | 2020 年 5 月 7 日 | 
| [3.x](https://pypi.org/project/pyqldb/) | 3.6 或更高版本 | 量产版 | 2021 年 10 月 28 日 | 

要使用（`pip`适用于 Python 的软件包管理器）从 PyPI 安装 QLDB 驱动程序，请在命令行输入以下命令。

------
#### [ 3.x ]

```
pip install pyqldb
```

------
#### [ 2.x ]

```
pip install pyqldb==2.0.2
```

------

安装驱动程序还会安装其依赖项，包括 [适用于 Python (Boto3) 的 AWS SDK](https://aws.amazon.com/sdk-for-python) 和 [Amazon Ion](ion.md) 软件包。

**使用驱动程序连接至分类账**

然后，您可以导入驱动程序，并使用它来连接到分类账。以下 Python 代码示例说明如何为指定的分类账名称创建会话。

------
#### [ 3.x ]

```
from pyqldb.driver.qldb_driver import QldbDriver
qldb_driver = QldbDriver(ledger_name='testLedger')

for table in qldb_driver.list_tables():
    print(table)
```

------
#### [ 2.x ]

```
from pyqldb.driver.pooled_qldb_driver import PooledQldbDriver

qldb_driver = PooledQldbDriver(ledger_name='testLedger')
qldb_session = qldb_driver.get_session()

for table in qldb_session.list_tables():
    print(table)
```

------

有关如何在分类账上运行基本数据事务的简短代码示例，请参阅 [说明书参考](driver-cookbook-python.md)。

# 适用于 Python 的 Amazon QLDB 驱动程序 — 快速入门教程
<a name="driver-quickstart-python"></a>

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

在本教程中，您将学习如何使用适用于 Python 的最新版 Amazon QLDB 驱动程序来设置简单应用程序。本指南包括安装驱动程序的步骤以及*创建、读取、更新和删除*（CRUD）的基本操作的简短代码示例。有关在完整示例应用程序中演示这些操作的更深入的示例，请参阅 [Python 教程](getting-started.python.tutorial.md)。

**Topics**
+ [

## 先决条件
](#driver-quickstart-python.prereqs)
+ [

## 步骤 1：设置您的项目
](#driver-quickstart-python.step-1)
+ [

## 第 2 步：初始化驱动程序
](#driver-quickstart-python.step-2)
+ [

## 第 3 步：创建表和索引
](#driver-quickstart-python.step-3)
+ [

## 第 4 步：插入文档
](#driver-quickstart-python.step-4)
+ [

## 第 5 步：查询文档
](#driver-quickstart-python.step-5)
+ [

## 第 6 步：更新文档
](#driver-quickstart-python.step-6)
+ [

## 运行完整的应用程序
](#driver-quickstart-python.complete)

## 先决条件
<a name="driver-quickstart-python.prereqs"></a>

在开始之前，请务必执行以下操作：

1. 请为 Python 驱动程序完成（[先决条件](getting-started.python.md#getting-started.python.prereqs)如果尚未执行此操作）。这包括注册 AWS、授予开发所需的编程访问权限以及安装 Python 3.6 或更高版本。

1. 创建一个名为 `quick-start` 分类账。

   要了解如何创建分类账，请参阅*控制台入门*中的 [Amazon QLDB 分类账的基本操作](ledger-management.basics.md) 或 [第 1 步：创建新分类账](getting-started-step-1.md)。

## 步骤 1：设置您的项目
<a name="driver-quickstart-python.step-1"></a>

首先，请设置您的 Python 项目。

**注意**  
如果您使用的 IDE 具有自动执行这些设置步骤的功能，则可以直接跳到 [第 2 步：初始化驱动程序](#driver-quickstart-python.step-2)。

1. 为应用程序创建一个文件夹。

   ```
   $ mkdir myproject
   $ cd myproject
   ```

1. 要从 PyPI 安装适用于 Python 的 QLDB 驱动程序，请输入以下 `pip` 命令。

   ```
   $ pip install pyqldb
   ```

   安装驱动程序还会安装其依赖项，包括 [适用于 Python (Boto3) 的 AWS SDK](https://aws.amazon.com/sdk-for-net) 和 [Amazon Ion](ion.md) 软件包。

1. 创建名为 `app.py` 的新文件。

   然后，按以下步骤中逐步添加代码示例，尝试一些基本的 CRUD 操作。或者，您可以跳过本 step-by-step教程，改为运行[完整的应用程序](#driver-quickstart-python.complete)。

## 第 2 步：初始化驱动程序
<a name="driver-quickstart-python.step-2"></a>

初始化连接到名为 `quick-start` 的分类账的驱动程序实例。将以下代码添加到您的 `app.py`文件。

```
from pyqldb.config.retry_config import RetryConfig
from pyqldb.driver.qldb_driver import QldbDriver

# Configure retry limit to 3
retry_config = RetryConfig(retry_limit=3)

# Initialize the driver
print("Initializing the driver")
qldb_driver = QldbDriver("quick-start", retry_config=retry_config)
```

## 第 3 步：创建表和索引
<a name="driver-quickstart-python.step-3"></a>

以下代码示例显示如何运行 `CREATE TABLE` 和 `CREATE INDEX`。

添加以下代码，创建名为 `People` 的表，并为该表上的 `lastName` 字段创建索引。[索引](ql-reference.create-index.md)是优化查询性能和帮助限制[乐观并发控制（OCC）冲突异常](concurrency.md)所必需的。

```
def create_table(transaction_executor):
    print("Creating a table")
    transaction_executor.execute_statement("Create TABLE People")

def create_index(transaction_executor):
    print("Creating an index")
    transaction_executor.execute_statement("CREATE INDEX ON People(lastName)")

# Create a table
qldb_driver.execute_lambda(lambda executor: create_table(executor))

# Create an index on the table
qldb_driver.execute_lambda(lambda executor: create_index(executor))
```

## 第 4 步：插入文档
<a name="driver-quickstart-python.step-4"></a>

以下代码示例显示如何运行 `INSERT` 语句。QLDB 支持 [PartiQL](ql-reference.md) 查询语言（兼容 SQL）和 [Amazon Ion](ion.md) 数据格式（JSON 的超集）。

添加以下代码，在 `People` 表格中插入文档。

```
def insert_documents(transaction_executor, arg_1):
    print("Inserting a document")
    transaction_executor.execute_statement("INSERT INTO People ?", arg_1)

# Insert a document
doc_1 = { 'firstName': "John",
          'lastName': "Doe",
          'age': 32,
        }

qldb_driver.execute_lambda(lambda x: insert_documents(x, doc_1))
```

此示例使用问号（`?`）作为变量占位符，将文档信息传递给语句。该 `execute_statement` 方法同时支持 Amazon Ion 类型和 Python 本机类型。

**提示**  
要使用单个 [INSERT](ql-reference.insert.md) 语句插入多个文档，可以向该语句传递一个[列表](driver-working-with-ion.md#driver-ion-list)类型的参数，如下所示。  

```
# people is a list
transaction_executor.execute_statement("INSERT INTO Person ?", people)
```
传递 Ion 列表时，不要将变量占位符（`?`）括在双尖括号（`<<...>>`）内。在手动 PartiQL 语句中，双尖括号表示名为*bag*的无序集合。

## 第 5 步：查询文档
<a name="driver-quickstart-python.step-5"></a>

以下代码示例显示如何运行 `SELECT` 语句。

添加以下代码，用于从 `People` 表格中查询文档。

```
def read_documents(transaction_executor):
    print("Querying the table")
    cursor = transaction_executor.execute_statement("SELECT * FROM People WHERE lastName = ?", 'Doe')

    for doc in cursor:
        print(doc["firstName"])
        print(doc["lastName"])
        print(doc["age"])

# Query the table
qldb_driver.execute_lambda(lambda executor: read_documents(executor))
```

## 第 6 步：更新文档
<a name="driver-quickstart-python.step-6"></a>

以下代码示例显示如何运行 `UPDATE` 语句。

1. 添加以下代码，通过更新 `age` 到 `42` 来更新 `People` 表中的文档。

   ```
   def update_documents(transaction_executor, age, lastName):
       print("Updating the document")
       transaction_executor.execute_statement("UPDATE People SET age = ? WHERE lastName = ?", age, lastName)
   
   # Update the document
   age = 42
   lastName = 'Doe'
   
   qldb_driver.execute_lambda(lambda x: update_documents(x, age, lastName))
   ```

1. 再次查询表以查看更新的值。

   ```
   # Query the updated document
   qldb_driver.execute_lambda(lambda executor: read_documents(executor))
   ```

1. 要运行该应用程序，请在项目目录中输入以下命令。

   ```
   $ python app.py
   ```

## 运行完整的应用程序
<a name="driver-quickstart-python.complete"></a>

以下代码示例是 `app.py` 应用程序的完整版本。您还可以从头到尾复制并运行此代码示例，而不必单独执行前面的步骤。此应用程序演示了对名为 `quick-start` 的分类账的一些基本的 CRUD 操作。

**注意**  
在运行此代码之前，请确保 `quick-start` 分类账中还没有名为 `People` 的活动表。

```
from pyqldb.config.retry_config import RetryConfig
from pyqldb.driver.qldb_driver import QldbDriver

def create_table(transaction_executor):
    print("Creating a table")
    transaction_executor.execute_statement("CREATE TABLE People")

def create_index(transaction_executor):
    print("Creating an index")
    transaction_executor.execute_statement("CREATE INDEX ON People(lastName)")

def insert_documents(transaction_executor, arg_1):
    print("Inserting a document")
    transaction_executor.execute_statement("INSERT INTO People ?", arg_1)

def read_documents(transaction_executor):
    print("Querying the table")
    cursor = transaction_executor.execute_statement("SELECT * FROM People WHERE lastName = ?", 'Doe')
                                                                                                                                          
    for doc in cursor:
        print(doc["firstName"])
        print(doc["lastName"])
        print(doc["age"])

def update_documents(transaction_executor, age, lastName):
    print("Updating the document")
    transaction_executor.execute_statement("UPDATE People SET age = ? WHERE lastName = ?", age, lastName)

# Configure retry limit to 3
retry_config = RetryConfig(retry_limit=3)

# Initialize the driver
print("Initializing the driver")
qldb_driver = QldbDriver("quick-start", retry_config=retry_config)

# Create a table
qldb_driver.execute_lambda(lambda executor: create_table(executor))

# Create an index on the table
qldb_driver.execute_lambda(lambda executor: create_index(executor))

# Insert a document
doc_1 = { 'firstName': "John",
          'lastName': "Doe",
          'age': 32,
        }

qldb_driver.execute_lambda(lambda x: insert_documents(x, doc_1))

# Query the table
qldb_driver.execute_lambda(lambda executor: read_documents(executor))

# Update the document
age = 42
lastName = 'Doe'

qldb_driver.execute_lambda(lambda x: update_documents(x, age, lastName))

# Query the table for the updated document
qldb_driver.execute_lambda(lambda executor: read_documents(executor))
```

要运行完整的应用程序，请在项目目录中输入以下命令。

```
$ python app.py
```

# 适用于 Python 的Amazon QLDB 驱动程序 — 说明书参考
<a name="driver-cookbook-python"></a>

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

本参考指南显示了 Python 的 Amazon QLDB 驱动程序的常见用例。它提供了 Python 代码示例，演示了如何使用该驱动程序运行基本的*创建、读取、更新和删除*（CRUD）操作。它还包括用于处理 Amazon Ion 数据的代码示例。此外，本指南还重点介绍了使事务具有幂等性和实现唯一性约束的最佳实践。

**注意**  
在适用的情况下，某些用例对于 Python 的 QLDB 驱动程序的每个支持主要版本都配备不同代码示例。

**Contents**
+ [

## 导入驱动程序
](#cookbook-python.importing)
+ [

## 实例化驱动程序
](#cookbook-python.instantiating)
+ [

## CRUD 操作
](#cookbook-python.crud)
  + [

### 创建表
](#cookbook-python.crud.creating-tables)
  + [

### 创建索引
](#cookbook-python.crud.creating-indexes)
  + [

### 阅读文档
](#cookbook-python.crud.reading)
    + [

#### 使用查询参数
](#cookbook-python.reading-using-params)
  + [

### 插入文档
](#cookbook-python.crud.inserting)
    + [

#### 在一条语句内插入多个文档
](#cookbook-python.crud.inserting.multiple)
  + [

### 更新文档
](#cookbook-python.crud.updating)
  + [

### 删除文档
](#cookbook-python.crud.deleting)
  + [

### 在一个事务中运行多条语句
](#cookbook-python.crud.multi-statement)
  + [

### 重试逻辑
](#cookbook-python.crud.retry-logic)
  + [

### 实现唯一限制
](#cookbook-python.crud.uniqueness-constraints)
+ [

## 使用 Amazon Ion
](#cookbook-python.ion)
  + [

### 导入 Ion 模块
](#cookbook-python.ion.import)
  + [

### 创建 Ion 类型
](#cookbook-python.ion.creating-types)
  + [

### 获取 Ion 二进制转储
](#cookbook-python.ion.getting-binary)
  + [

### 获取 Ion 文本转储
](#cookbook-python.ion.getting-text)

## 导入驱动程序
<a name="cookbook-python.importing"></a>

下面的代码示例导入驱动程序。

------
#### [ 3.x ]

```
from pyqldb.driver.qldb_driver import QldbDriver
import amazon.ion.simpleion as simpleion
```

------
#### [ 2.x ]

```
from pyqldb.driver.pooled_qldb_driver import PooledQldbDriver
import amazon.ion.simpleion as simpleion
```

------

**注意**  
此示例还导入了 Amazon Ion 软件包（`amazon.ion.simpleion`）。在本参考中运行某些数据操作时，您需要此软件包来处理 Ion 数据。要了解更多信息，请参阅 [使用 Amazon Ion](#cookbook-python.ion)。

## 实例化驱动程序
<a name="cookbook-python.instantiating"></a>

以下代码示例使用默认设置创建连接到指定分类账名称的驱动程序实例。

------
#### [ 3.x ]

```
qldb_driver = QldbDriver(ledger_name='vehicle-registration')
```

------
#### [ 2.x ]

```
qldb_driver = PooledQldbDriver(ledger_name='vehicle-registration')
```

------

## CRUD 操作
<a name="cookbook-python.crud"></a>

QLDB 作为事务的一部分运行*创建、读取、更新和删除*（CRUD）操作。

**警告**  
作为最佳实践，使写事务严格地幂等。

**使事务幂等**

我们建议将写事务设置为幂等，以避免重试时出现任何意想不到的副作用。如果事务可以运行多次并每次都产生相同的结果，则事务是*幂等的*。

例如，假设有一个事务，要将文档插入名为 `Person` 的表中。事务应该首先检查文档是否已经存在于表中。如果没有这种检查，表最终可能会有重复的文档。

假设 QLDB 在服务器端成功提交了事务，但客户端在等待响应时超时。如果事务不是幂等的，则在重试的情况下可以多次插入相同的文档。

**使用索引避免全表扫描**

我们还建议您在索引字段或文档 ID 上使用*相等*运算符来运行带有`WHERE`谓词子句的语句；例如，`WHERE indexedField = 123`或 `WHERE indexedField IN (456, 789)`。如果没有这种索引查找，QLDB 需要进行表扫描，这可能会导致事务超时或*乐观并发控制*（OCC）冲突。

有关 OCC 的更多信息，请参阅[Amazon QLDB 并发模型](concurrency.md)。

**隐式创建的事务**

[pyqldb.driver.qldb\$1driver.execute\$1lambda](https://amazon-qldb-driver-python.readthedocs.io/en/stable/reference/driver/qldb_driver.html#pyqldb.driver.qldb_driver.QldbDriver.execute_lambda) 方法接受一个 lambda 函数，该函数接收一个 [TransactionExecutor](https://amazon-qldb-driver-python.readthedocs.io/en/stable/reference/execution/executor.html#pyqldb.execution.executor.Executor) 的实例，您可以用它来运行语句。`Executor` 的实例封装了隐式创建的事务。

您可以使用事务执行器的 [execute\$1statement](https://amazon-qldb-driver-python.readthedocs.io/en/stable/reference/execution/executor.html#pyqldb.execution.executor.Executor.execute_statement) 法在 lambda 函数中运行语句。当 lambda 函数返回时，驱动程序会隐式提交事务。

**注意**  
该 `execute_statement` 方法同时支持 Amazon Ion 类型和 Python 本机类型。如果您将 Python 本地类型作为参数传递给 `execute_statement`，则驱动程序会使用 `amazon.ion.simpleion` 模块将其转换为 Ion 类型（前提是支持对给定 Python 数据类型的转换）。有关支持的数据类型和转换规则，请参阅 [simpleion 源代码](https://ion-python.readthedocs.io/en/latest/_modules/amazon/ion/simpleion.html)。

以下各节介绍如何运行基本的 CRUD 操作、指定自定义重试逻辑以及如何实现唯一性约束。

**Contents**
+ [

### 创建表
](#cookbook-python.crud.creating-tables)
+ [

### 创建索引
](#cookbook-python.crud.creating-indexes)
+ [

### 阅读文档
](#cookbook-python.crud.reading)
  + [

#### 使用查询参数
](#cookbook-python.reading-using-params)
+ [

### 插入文档
](#cookbook-python.crud.inserting)
  + [

#### 在一条语句内插入多个文档
](#cookbook-python.crud.inserting.multiple)
+ [

### 更新文档
](#cookbook-python.crud.updating)
+ [

### 删除文档
](#cookbook-python.crud.deleting)
+ [

### 在一个事务中运行多条语句
](#cookbook-python.crud.multi-statement)
+ [

### 重试逻辑
](#cookbook-python.crud.retry-logic)
+ [

### 实现唯一限制
](#cookbook-python.crud.uniqueness-constraints)

### 创建表
<a name="cookbook-python.crud.creating-tables"></a>

```
def create_table(transaction_executor):
    transaction_executor.execute_statement("CREATE TABLE Person")

qldb_driver.execute_lambda(lambda executor: create_table(executor))
```

### 创建索引
<a name="cookbook-python.crud.creating-indexes"></a>

```
def create_index(transaction_executor):
    transaction_executor.execute_statement("CREATE INDEX ON Person(GovId)")

qldb_driver.execute_lambda(lambda executor: create_index(executor))
```

### 阅读文档
<a name="cookbook-python.crud.reading"></a>

```
# Assumes that Person table has documents as follows:
# { "GovId": "TOYENC486FH", "FirstName": "Brent" }

def read_documents(transaction_executor):
    cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'")

    for doc in cursor:
        print(doc["GovId"]) # prints TOYENC486FH
        print(doc["FirstName"]) # prints Brent

qldb_driver.execute_lambda(lambda executor: read_documents(executor))
```

#### 使用查询参数
<a name="cookbook-python.reading-using-params"></a>

以下代码示例使用原生类型查询参数。

```
cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", 'TOYENC486FH')
```

以下代码示例使用 Ion 类型查询参数。

```
name = ion.loads('Brent')
cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE FirstName = ?", name)
```

以下代码示例使用了多个查询参数。

```
cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", 'TOYENC486FH', "Brent")
```

以下代码示例使用查询参数列表。

```
gov_ids = ['TOYENC486FH','ROEE1','YH844']
cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId IN (?,?,?)", *gov_ids)
```

**注意**  
当您在没有索引查找的情况下运行查询时，它会调用全表扫描。在此示例中，我们建议在`GovId`字段上设置 [索引](ql-reference.create-index.md)以优化性能。如果不开启`GovId`索引，查询可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

### 插入文档
<a name="cookbook-python.crud.inserting"></a>

以下代码示例插入本地数据类型。

```
def insert_documents(transaction_executor, arg_1):
    # Check if doc with GovId:TOYENC486FH exists
    # This is critical to make this transaction idempotent
    cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", 'TOYENC486FH')
    # Check if there is any record in the cursor
    first_record = next(cursor, None)

    if first_record:
        # Record already exists, no need to insert
        pass
    else:
        transaction_executor.execute_statement("INSERT INTO Person ?", arg_1)

doc_1 = { 'FirstName': "Brent",
          'GovId': 'TOYENC486FH',
        }

qldb_driver.execute_lambda(lambda executor: insert_documents(executor, doc_1))
```

以下代码示例插入 Ion 数据类型。

```
def insert_documents(transaction_executor, arg_1):
    # Check if doc with GovId:TOYENC486FH exists
    # This is critical to make this transaction idempotent
    cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", 'TOYENC486FH')
    # Check if there is any record in the cursor
    first_record = next(cursor, None)

    if first_record:
        # Record already exists, no need to insert
        pass
    else:
        transaction_executor.execute_statement("INSERT INTO Person ?", arg_1)

doc_1 = { 'FirstName': 'Brent',
          'GovId': 'TOYENC486FH',
        }

# create a sample Ion doc
ion_doc_1 = simpleion.loads(simpleion.dumps(doc_1)))

qldb_driver.execute_lambda(lambda executor: insert_documents(executor, ion_doc_1))
```

此事务将文档插入 `Person` 表中。在插入之前，它首先检查文档是否已存在于表格内。**此检查使事务本质上是幂等。**即使您多次运行此事务，也不会造成任何异常副作用。

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

#### 在一条语句内插入多个文档
<a name="cookbook-python.crud.inserting.multiple"></a>

要使用单个 [INSERT](ql-reference.insert.md) 语句插入多个文档，可以向该语句传递一个[列表](driver-working-with-ion.md#driver-ion-list)类型的参数，如下所示。

```
# people is a list
transaction_executor.execute_statement("INSERT INTO Person ?", people)
```

传递 Ion 列表时，不要将变量占位符（`?`）括在双尖括号（`<<...>>`）内。在手动 PartiQL 语句中，双尖括号表示名为*bag*的无序集合。

### 更新文档
<a name="cookbook-python.crud.updating"></a>

以下代码示例使用原生数据类型。

```
def update_documents(transaction_executor, gov_id, name):
    transaction_executor.execute_statement("UPDATE Person SET FirstName = ?  WHERE GovId = ?", name, gov_id)

gov_id = 'TOYENC486FH'
name = 'John'

qldb_driver.execute_lambda(lambda executor: update_documents(executor, gov_id, name))
```

以下代码示例使用 Ion 数据类型。

```
def update_documents(transaction_executor, gov_id, name):
    transaction_executor.execute_statement("UPDATE Person SET FirstName = ? WHERE GovId = ?", name, gov_id)

# Ion datatypes
gov_id = simpleion.loads('TOYENC486FH')
name = simpleion.loads('John')

qldb_driver.execute_lambda(lambda executor: update_documents(executor, gov_id, name))
```

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

### 删除文档
<a name="cookbook-python.crud.deleting"></a>

以下代码示例使用原生数据类型。

```
def delete_documents(transaction_executor, gov_id):
    cursor = transaction_executor.execute_statement("DELETE FROM Person WHERE GovId = ?", gov_id)

gov_id = 'TOYENC486FH'

qldb_driver.execute_lambda(lambda executor: delete_documents(executor, gov_id))
```

以下代码示例使用 Ion 数据类型。

```
def delete_documents(transaction_executor, gov_id):
    cursor = transaction_executor.execute_statement("DELETE FROM Person WHERE GovId = ?", gov_id)

# Ion datatypes
gov_id = simpleion.loads('TOYENC486FH')

qldb_driver.execute_lambda(lambda executor: delete_documents(executor, gov_id))
```

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

### 在一个事务中运行多条语句
<a name="cookbook-python.crud.multi-statement"></a>

```
# This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
# set your UPDATE to filter on vin and insured, and check if you updated something or not.

def do_insure_car(transaction_executor, vin):
    cursor = transaction_executor.execute_statement(
        "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)
    first_record = next(cursor, None)
    if first_record:
        transaction_executor.execute_statement(
            "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin)
        return True
    else:
        return False

def insure_car(qldb_driver, vin_to_insure):
    return qldb_driver.execute_lambda(
        lambda executor: do_insure_car(executor, vin_to_insure))
```

### 重试逻辑
<a name="cookbook-python.crud.retry-logic"></a>

驱动程序 `execute_lambda` 方法具有内置的重试机制，如果发生可重试的异常（例如超时或 OCC 冲突），该机制可以重试事务。

------
#### [ 3.x ]

最大重试次数和退避策略为可配置。

默认的重试限制为 `4`，默认的退避策略是以`10`毫秒为基数的 [Exponential Backoff and Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)。你可以使用 pyqldb.config.retr [y\$1config 的实例为每个驱动程序实例和每个事务设置重试配置。 RetryConfig](https://amazon-qldb-driver-python.readthedocs.io/en/stable/reference/config/retry_config.html#pyqldb.config.retry_config.RetryConfig)。

以下代码示例使用自定义重试限制和驱动程序实例的自定义退避策略指定重试逻辑。

```
from pyqldb.config.retry_config import RetryConfig
from pyqldb.driver.qldb_driver import QldbDriver

# Configuring retry limit to 2
retry_config = RetryConfig(retry_limit=2)
qldb_driver = QldbDriver("test-ledger", retry_config=retry_config)

# Configuring a custom backoff which increases delay by 1s for each attempt.
def custom_backoff(retry_attempt, error, transaction_id):
    return 1000 * retry_attempt

retry_config_custom_backoff = RetryConfig(retry_limit=2, custom_backoff=custom_backoff)
qldb_driver = QldbDriver("test-ledger", retry_config=retry_config_custom_backoff)
```

以下代码示例使用自定义重试限制和自定义回退策略为特定 lambda 执行指定重试逻辑。此`execute_lambda`配置将覆盖为驱动程序实例设置的重试逻辑。

```
from pyqldb.config.retry_config import RetryConfig
from pyqldb.driver.qldb_driver import QldbDriver

# Configuring retry limit to 2
retry_config_1 = RetryConfig(retry_limit=4)
qldb_driver = QldbDriver("test-ledger", retry_config=retry_config_1)

# Configuring a custom backoff which increases delay by 1s for each attempt.
def custom_backoff(retry_attempt, error, transaction_id):
    return 1000 * retry_attempt

retry_config_2 = RetryConfig(retry_limit=2, custom_backoff=custom_backoff)

# The config `retry_config_1` will be overriden by `retry_config_2`
qldb_driver.execute_lambda(lambda txn: txn.execute_statement("CREATE TABLE Person"), retry_config_2)
```

------
#### [ 2.x ]

最大重试次数。您可以通过在初始化`PooledQldbDriver` 时设置 `retry_limit` 属性来配置重试限制。

默认重试限制为 `4`。

------

### 实现唯一限制
<a name="cookbook-python.crud.uniqueness-constraints"></a>

QLDB 不支持唯一索引，但您可在应用程序中实现此行为。

假设您要对 `Person` 表中的 `GovId` 字段实现唯一性约束。据此，可以编写执行以下操作的事务：

1. 断言该表中没有指定 `GovId` 的现有文档。

1. 如果断言通过，请插入文档。

如果一个竞争事务同时通过断言，则只有一个事务将成功提交。另一笔事务将失败，并显示 OCC 冲突异常。

以下代码示例显示了如何实现此唯一约束。

```
def insert_documents(transaction_executor, gov_id, document):
    # Check if doc with GovId = gov_id exists
    cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", gov_id)
    # Check if there is any record in the cursor
    first_record = next(cursor, None)

    if first_record:
        # Record already exists, no need to insert
        pass
    else:
        transaction_executor.execute_statement("INSERT INTO Person ?", document)

qldb_driver.execute_lambda(lambda executor: insert_documents(executor, gov_id, document))
```

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

## 使用 Amazon Ion
<a name="cookbook-python.ion"></a>

以下各节说明了如何使用 Amazon Ion 模块处理 Ion 数据。

**Contents**
+ [

### 导入 Ion 模块
](#cookbook-python.ion.import)
+ [

### 创建 Ion 类型
](#cookbook-python.ion.creating-types)
+ [

### 获取 Ion 二进制转储
](#cookbook-python.ion.getting-binary)
+ [

### 获取 Ion 文本转储
](#cookbook-python.ion.getting-text)

### 导入 Ion 模块
<a name="cookbook-python.ion.import"></a>

```
import amazon.ion.simpleion as simpleion
```

### 创建 Ion 类型
<a name="cookbook-python.ion.creating-types"></a>

以下代码示例从 Ion 文本创建 Ion 对象。

```
ion_text = '{GovId: "TOYENC486FH", FirstName: "Brent"}'
ion_obj = simpleion.loads(ion_text)

print(ion_obj['GovId']) # prints TOYENC486FH
print(ion_obj['Name']) # prints Brent
```

以下代码示例从 Python `dict`创建 Ion 对象。

```
a_dict = { 'GovId': 'TOYENC486FH',
           'FirstName': "Brent"
         }
ion_obj = simpleion.loads(simpleion.dumps(a_dict))

print(ion_obj['GovId']) # prints TOYENC486FH
print(ion_obj['FirstName']) # prints Brent
```

### 获取 Ion 二进制转储
<a name="cookbook-python.ion.getting-binary"></a>

```
# ion_obj is an Ion struct
print(simpleion.dumps(ion_obj)) # b'\xe0\x01\x00\xea\xee\x97\x81\x83\xde\x93\x87\xbe\x90\x85GovId\x89FirstName\xde\x94\x8a\x8bTOYENC486FH\x8b\x85Brent'
```

### 获取 Ion 文本转储
<a name="cookbook-python.ion.getting-text"></a>

```
# ion_obj is an Ion struct
print(simpleion.dumps(ion_obj, binary=False)) # prints $ion_1_0 {GovId:'TOYENC486FH',FirstName:"Brent"}
```

有关使用 Ion 的更多信息，请参阅上的 [Amazon Ion 文档](http://amzn.github.io/ion-docs/) GitHub。有关在 QLDB 中使用 Ion 的更多代码示例，请参阅[使用 Amazon QLDB 中的 Amazon Ion 数据类型](driver-working-with-ion.md)。