

# 使用 RDS for PostgreSQL 的等待事件进行优化
<a name="PostgreSQL.Tuning"></a>

等待事件是 RDS for PostgreSQL 的重要优化工具。当您能查明会话为什么在等待资源以及会话在做什么时，您就能更好地减少瓶颈。您可以使用本节中的信息来查找可能的原因和纠正措施。本节还讨论基本的 PostgreSQL 优化概念。

本节中的等待事件特定于 RDS for PostgreSQL。

**Topics**
+ [RDS for PostgreSQL 优化的基本概念](PostgreSQL.Tuning.concepts.md)
+ [RDS for PostgreSQL 等待事件](PostgreSQL.Tuning.concepts.summary.md)
+ [Client:ClientRead](wait-event.clientread.md)
+ [Client:ClientWrite](wait-event.clientwrite.md)
+ [CPU](wait-event.cpu.md)
+ [IO:BufFileRead 和 IO:BufFileWrite](wait-event.iobuffile.md)
+ [IO:DataFileRead](wait-event.iodatafileread.md)
+ [IO:WALWrite](wait-event.iowalwrite.md)
+ [IPC:并行等待事件](rpg-ipc-parallel.md)
+ [IPC:ProcArrayGroupUpdate](apg-rpg-ipcprocarraygroup.md)
+ [Lock:advisory](wait-event.lockadvisory.md)
+ [Lock:extend](wait-event.lockextend.md)
+ [Lock:Relation](wait-event.lockrelation.md)
+ [Lock:transactionid](wait-event.locktransactionid.md)
+ [Lock:tuple](wait-event.locktuple.md)
+ [LWLock:BufferMapping (LWLock:buffer\$1mapping)](wait-event.lwl-buffer-mapping.md)
+ [LWLock:BufferIO (IPC:BufferIO)](wait-event.lwlockbufferio.md)
+ [LWLock:buffer\$1content (BufferContent)](wait-event.lwlockbuffercontent.md)
+ [LWLock:lock\$1manager (LWLock:lockmanager)](wait-event.lw-lock-manager.md)
+ [LWLock:pg\$1stat\$1statements](apg-rpg-lwlockpgstat.md)
+ [LWLock:SubtransSLRU (LWLock:SubtransControlLock)](wait-event.lwlocksubtransslru.md)
+ [Timeout:PgSleep](wait-event.timeoutpgsleep.md)
+ [Timeout:VacuumDelay](wait-event.timeoutvacuumdelay.md)

# RDS for PostgreSQL 优化的基本概念
<a name="PostgreSQL.Tuning.concepts"></a>

在优化 RDS for PostgreSQL 数据库之前，请务必了解什么是等待事件以及它们发生的原因。还可以查看 RDS for PostgreSQL 的基本内存和磁盘架构。有关有用的架构图，请参阅 [PostgreSQL](https://en.wikibooks.org/wiki/PostgreSQL/Architecture) wikibook。

**Topics**
+ [RDS for PostgreSQL 等待事件](PostgreSQL.Tuning.concepts.waits.md)
+ [RDS for PostgreSQL 内存](PostgreSQL.Tuning.concepts.memory.md)
+ [RDS for PostgreSQL 进程](PostgreSQL.Tuning.concepts.processes.md)

# RDS for PostgreSQL 等待事件
<a name="PostgreSQL.Tuning.concepts.waits"></a>

*等待事件*表示会话正在等待资源。例如，当 RDS for PostgreSQL 等待从客户端接收数据时，会发生等待事件 `Client:ClientRead`。会话通常会等待如下资源。
+ 例如，当会话试图修改缓冲区时，对缓冲区的单线程访问
+ 当前被另一个会话锁定的行
+ 已读取一个数据文件
+ 已写入一个日志文件

例如，为了满足查询，会话可能会执行完整的表扫描。如果数据尚未在内存中，会话将等待磁盘输入/输出完成。当缓冲区读取到内存时，会话可能需要等待，因为其他会话正在访问相同的缓冲区。数据库使用预定义的等待事件记录等待。这些事件按类别进行分组。

等待事件本身并不表示存在性能问题。例如，如果请求的数据不在内存中，则必须从磁盘读取数据。如果一个会话锁定行以进行更新，则另一个会话将等待解锁该行，以便它可以更新该行。提交需要等待对日志文件的写入完成。等待是数据库正常运行不可或缺的组成部分。

另一方面，大量的等待事件通常表示存在性能问题。在这种情况下，您可以使用等待事件数据来确定会话将时间花费在哪里。例如，如果通常在几分钟内运行的报告现在运行需要几个小时，则可以确定对总等待时间贡献最大的等待事件。如果您能确定顶级等待事件的原因，您有时就可以进行更改来提高性能。例如，如果您的会话正在等待已被另一个会话锁定的行，则可以结束锁定会话。

# RDS for PostgreSQL 内存
<a name="PostgreSQL.Tuning.concepts.memory"></a>

RDS for PostgreSQL 内存分为共享内存和本地内存。

**Topics**
+ [RDS for PostgreSQL 中的共享内存](#PostgreSQL.Tuning.concepts.shared)
+ [RDS for PostgreSQL 中的本地内存](#PostgreSQL.Tuning.concepts.local)

## RDS for PostgreSQL 中的共享内存
<a name="PostgreSQL.Tuning.concepts.shared"></a>

RDS for PostgreSQL 会在实例启动时分配共享内存。共享内存分为多个子区域。以下各节提供了最重要子区域的说明。

**Topics**
+ [共享缓冲区](#PostgreSQL.Tuning.concepts.buffer-pool)
+ [预写日志 (WAL) 缓冲区](#PostgreSQL.Tuning.concepts.WAL)

### 共享缓冲区
<a name="PostgreSQL.Tuning.concepts.buffer-pool"></a>

*共享缓冲池*是一个 RDS for PostgreSQL 内存区域，它包含应用程序连接现在正在使用或过去使用的所有页面。*分页*是磁盘数据块的内存版本。共享缓冲池缓存从磁盘读取的数据块。该缓冲池减少了从磁盘重新读取数据的需求，从而提高了数据库的运行效率。

每个表和索引都存储为固定大小的页面数组。每个数据块包含多个元组，它们与行相对应。元组可以存储在任何页面中。

共享缓冲池的内存有限。如果新请求需要一个不在内存中的页面，并且没有更多的内存，RDS for PostgreSQL 会移除一个较少使用的页面来容纳请求。移出策略通过时钟扫描算法来实现。

`shared_buffers` 参数确定服务器用于缓存数据的内存量。根据数据库实例的可用内存，默认值设置为 `{DBInstanceClassMemory/32768}` 字节。

### 预写日志 (WAL) 缓冲区
<a name="PostgreSQL.Tuning.concepts.WAL"></a>

*预写日志（WAL）缓冲区*保存 RDS for PostgreSQL 稍后写入持久存储的事务数据。使用 WAL 机制，RDS for PostgreSQL 可以执行以下操作：
+ 发生故障后恢复数据
+ 通过避免频繁写入磁盘来减少磁盘输入/输出

当客户端更改数据时，RDS for PostgreSQL 会将更改写入 WAL 缓冲区。当客户发出 `COMMIT`，WAL 写入器进程将事务数据写入 WAL 文件。

`wal_level` 参数决定向 WAL 写入的信息量，可能的值包括 `minimal`、`replica` 和 `logical` 等。

## RDS for PostgreSQL 中的本地内存
<a name="PostgreSQL.Tuning.concepts.local"></a>

每个后端进程都会为查询处理分配本地内存。

**Topics**
+ [工作内存区域](#PostgreSQL.Tuning.concepts.local.work_mem)
+ [维护工作内存区域](#PostgreSQL.Tuning.concepts.local.maintenance_work_mem)
+ [临时缓冲区](#PostgreSQL.Tuning.concepts.temp)

### 工作内存区域
<a name="PostgreSQL.Tuning.concepts.local.work_mem"></a>

*工作内存区域*保存执行排序和哈希的查询的临时数据。例如，包含 `ORDER BY` 子句的查询执行排序。查询在哈希联接和聚合中使用哈希表。

`work_mem` 参数表示在写入临时磁盘文件之前，内部排序操作和哈希表要使用的内存量，以 MB 为单位。原定设置值为 4MB。可以同时运行多个会话，且每个会话可以并行运行维护操作。出于这个原因，使用的总工作内存可以是 `work_mem` 设置的倍数。

### 维护工作内存区域
<a name="PostgreSQL.Tuning.concepts.local.maintenance_work_mem"></a>

*维护工作内存区域*缓存数据以进行维护操作。这些操作包括 vacuum 操作、创建索引和添加外键。

`maintenance_work_mem` 参数指定维护操作要使用的最大内存量，以 MB 为单位。原定设置值为 64MB。一个数据库会话一次只能运行一个维护操作。

### 临时缓冲区
<a name="PostgreSQL.Tuning.concepts.temp"></a>

*临时缓冲区*缓存每个数据库会话的临时表。

每个会话都根据需要分配临时缓冲区，但不超过您指定的限制。当会话结束时，服务器将清除缓冲区。

`temp_buffers` 参数设置每个会话使用的临时缓冲区的最大数量，以 MB 为单位。默认值为 8 MB。在会话中首次使用临时表之前，您可以更改 `temp_buffers` 值。

# RDS for PostgreSQL 进程
<a name="PostgreSQL.Tuning.concepts.processes"></a>

RDS for PostgreSQL 使用多个进程。

**Topics**
+ [邮件管理员过程](#PostgreSQL.Tuning.concepts.postmaster)
+ [后端进程](#PostgreSQL.Tuning.concepts.backend)
+ [后台进程](#PostgreSQL.Tuning.concepts.vacuum)

## 邮件管理员过程
<a name="PostgreSQL.Tuning.concepts.postmaster"></a>

*邮件管理员进程*是启动 RDS for PostgreSQL 时开始的第一个进程。邮件管理员过程负有以下主要责任：
+ 分流并监控后台进程
+ 接收来自客户端进程的身份验证请求，并在允许数据库为请求提供服务之前对这些请求进行身份验证

## 后端进程
<a name="PostgreSQL.Tuning.concepts.backend"></a>

如果邮件管理员对客户请求进行身份验证，邮件管理员会分流一个新的后端进程，也称为 postgres 进程。一个客户端进程只连接到一个后端进程。客户端进程和后端进程直接通信，而无需邮件管理员过程的干预。

## 后台进程
<a name="PostgreSQL.Tuning.concepts.vacuum"></a>

邮件管理员过程会分流执行不同后端任务的几个进程。其中一些更重要的事项包括：
+ WAL 写入器

  RDS for PostgreSQL 会将 WAL（预写日志记录）缓冲区中的数据写入日志文件。预写日志记录的原则是，在数据库将描述这些更改的日志记录写入磁盘之后，数据库才能将更改写入数据文件。WAL 机制减少了磁盘 I/O，并允许 RDS for PostgreSQL 在出现故障后使用日志恢复数据库。
+ 后台写入器

  此进程会定期将内存缓冲区中的脏（已修改）分页写入数据文件。当后端进程在内存中修改分页时，分页会变脏。
+ Autovacuum 守护进程

  守护进程由以下各项组成：
  + Autovacuum 启动程序
  + Autovacuum 工件进程

  当 Autovacuum 开启时，它会检查包含大量插入的、更新的或删除的元组的表。守护进程要承担以下责任：
  + 恢复或重复使用更新或删除的行占用的磁盘空间
  + 更新计划人员使用的统计数据
  + 防止因事务 ID 重叠而导致旧数据丢失

  Autovacuum 功能自动执行 `VACUUM` 和 `ANALYZE` 命令。`VACUUM` 具有以下变体：标准和完整版。标准 vacuum 与其他数据库操作并行运行。`VACUUM FULL` 需要对您工作所在的表具有专有锁定。因此，它不能与访问同一表的操作并行运行。`VACUUM` 创建了大量的输入/输出流量，这可能会导致其他活动会话的性能不佳。

# RDS for PostgreSQL 等待事件
<a name="PostgreSQL.Tuning.concepts.summary"></a>

下表列出了 RDS for PostgreSQL 最常指示性能问题的等待事件，并总结了最常见的原因和纠正措施。


| 等待事件 | 定义 | 
| --- | --- | 
|  [Client:ClientRead](wait-event.clientread.md)  |  当 RDS for PostgreSQL 等待从客户端接收数据时，会发生此事件。  | 
|  [Client:ClientWrite](wait-event.clientwrite.md)  |  当 RDS for PostgreSQL 等待将数据写入客户端时，会发生此事件。  | 
|  [CPU](wait-event.cpu.md)  | 当线程在 CPU 中处于活动状态或正在等待 CPU 时，会发生此事件。 | 
|  [IO:BufFileRead 和 IO:BufFileWrite](wait-event.iobuffile.md)  |  这些事件发生在 RDS for PostgreSQL 创建临时文件时。  | 
|  [IO:DataFileRead](wait-event.iodatafileread.md)  |  当由于分页在共享内存中不可用，连接等待后端进程从存储中读取所需分页时，会发生此事件。  | 
| [IO:WALWrite](wait-event.iowalwrite.md)  | 当 RDS for PostgreSQL 正在等待将预写日志（WAL）缓冲区写入 WAL 文件时，会发生此事件。  | 
|  [Lock:advisory](wait-event.lockadvisory.md)  |  当 PostgreSQL 应用程序使用锁定来协调多个会话之间的活动时，会发生此事件。  | 
|  [Lock:extend](wait-event.lockextend.md) |  当后端进程正在等待锁定关系以对其进行扩展，而另一个进程出于同样目的锁定该关系时，会发生此事件。  | 
|  [Lock:Relation](wait-event.lockrelation.md)  |  当查询等待获取当前被另一个事务锁定的表或视图上的锁定时，会发生此事件。  | 
|  [Lock:transactionid](wait-event.locktransactionid.md)  | 当事务正在等待行级锁定时，会发生此事件。 | 
|  [Lock:tuple](wait-event.locktuple.md)  |  在后端进程等待获取元组锁定时，会发生此事件。  | 
|  [LWLock:BufferMapping (LWLock:buffer\$1mapping)](wait-event.lwl-buffer-mapping.md)  |  当会话正在等待将数据块与共享缓冲池中的缓冲区关联起来时，会发生此事件。  | 
|  [LWLock:BufferIO (IPC:BufferIO)](wait-event.lwlockbufferio.md)  |  当 RDS for PostgreSQL 正在等待其他进程在同时尝试访问页面时完成输入/输出（I/O）操作时，会发生此事件。  | 
|  [LWLock:buffer\$1content (BufferContent)](wait-event.lwlockbuffercontent.md)  |  当某个会话等待读取或写入内存中的某个数据页面，而另一个会话正锁定该页面以进行写入时，会发生此事件。  | 
|  [LWLock:lock\$1manager (LWLock:lockmanager)](wait-event.lw-lock-manager.md)  | 当 RDS for PostgreSQL 引擎维护共享锁的内存区域以便在无法使用快速路径锁时分配、检查和取消分配锁时，会发生此事件。 | 
|  [LWLock:SubtransSLRU (LWLock:SubtransControlLock)](wait-event.lwlocksubtransslru.md)  |  当进程正在等待访问子事务的最近使用最少（SLRU）的简单缓存时，发生此事件。  | 
|  [Timeout:PgSleep](wait-event.timeoutpgsleep.md)  |  当服务器进程调用 `pg_sleep` 函数并且正在等待睡眠超时过期时，会发生此事件。  | 
|  [Timeout:VacuumDelay](wait-event.timeoutvacuumdelay.md)  | 此事件表明 vacuum 进程正处于休眠状态，因为已达到估计的成本上限。 | 

# Client:ClientRead
<a name="wait-event.clientread"></a>

当 RDS for PostgreSQL 等待从客户端接收数据时，会发生 `Client:ClientRead` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.clientread.context.supported)
+ [上下文](#wait-event.clientread.context)
+ [等待次数增加的可能原因](#wait-event.clientread.causes)
+ [操作](#wait-event.clientread.actions)

## 支持的引擎版本
<a name="wait-event.clientread.context.supported"></a>

RDS for PostgreSQL 版本 10 及更高版本支持此等待事件信息。

## 上下文
<a name="wait-event.clientread.context"></a>

RDS for PostgreSQL 数据库实例正在等待从客户端接收数据。RDS for PostgreSQL 数据库实例必须先从客户端接收数据，然后才能向客户端发送更多数据。实例在从客户端接收数据之前等待的时间为 `Client:ClientRead` 事件。

## 等待次数增加的可能原因
<a name="wait-event.clientread.causes"></a>

`Client:ClientRead` 显示在主要等待中的常见原因包括以下各项：

**网络延迟增加**  
RDS for PostgreSQL 数据库实例和客户端之间的网络延迟可能会增加。较高的网络延迟会增加数据库实例从客户端接收数据所需的时间。

**客户端负载增加**  
客户端上可能存在 CPU 压力或网络饱和。客户端负载的增加可能会延迟从客户端向 RDS for PostgreSQL 数据库实例传输数据的时间。

**过多的网络往返次数**  
RDS for PostgreSQL 数据库实例和客户端之间的大量网络往返可能会延迟将数据从客户端传输到 RDS for PostgreSQL 数据库实例的过程。

**大型复制操作**  
在复制操作期间，数据将从客户端的文件系统传输到 RDS for PostgreSQL 数据库实例。向数据库实例发送大量数据可能会延迟从客户端向数据库实例传输数据的时间。

**空闲客户端连接**  
当客户端以 `idle in transaction` 状态连接到 RDS for PostgreSQL 数据库实例时，数据库实例可能会等待客户端发送更多数据或发出命令。在这种状态下的连接可能会导致 `Client:ClientRead` 事件增加。

**用于连接池的 PgBouncer**  
PgBouncer 有一个名为 `pkt_buf` 的低级网络配置设置，预设情况下设置为 4096。如果工作负载通过 PgBouncer 发送大于 4096 字节的查询数据包，我们建议增加 `pkt_buf` 设置为 8192。如果新设置没有减少 `Client:ClientRead` 事件的数量，我们建议增加 `pkt_buf` 设置为较大的值，例如 16384 或 32768。如果查询文本很大，则较大的设置可能会特别有用。

## 操作
<a name="wait-event.clientread.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [将客户端放置在与实例相同的可用区和 VPC 子网中](#wait-event.clientread.actions.az-vpc-subnet)
+ [扩展客户端](#wait-event.clientread.actions.scale-client)
+ [使用当前一代实例](#wait-event.clientread.actions.db-instance-class)
+ [提高网络带宽](#wait-event.clientread.actions.increase-network-bandwidth)
+ [监控网络性能的最大值](#wait-event.clientread.actions.monitor-network-performance)
+ [监控处于“空闲事务”状态的事务](#wait-event.clientread.actions.check-idle-in-transaction)

### 将客户端放置在与实例相同的可用区和 VPC 子网中
<a name="wait-event.clientread.actions.az-vpc-subnet"></a>

为了减少网络延迟并提高网络吞吐量，请将客户端放在与 RDS for PostgreSQL 数据库实例相同的可用区和虚拟私有云（VPC）子网中。确保客户端在地理位置上尽可能靠近数据库实例。

### 扩展客户端
<a name="wait-event.clientread.actions.scale-client"></a>

使用 Amazon CloudWatch 或其他主机指标，确定您的客户端当前是受 CPU 或网络带宽的限制，还是受此两者的限制。如果客户端受到限制，请相应地扩展您的客户端。

### 使用当前一代实例
<a name="wait-event.clientread.actions.db-instance-class"></a>

在某些情况下，您可能没有使用支持巨型帧的数据库实例类。如果您在 Amazon EC2 上运行应用程序，请考虑为客户端使用当前一代实例。另外，在客户端操作系统上配置最大传输单位 (MTU)。这种技术可能会减少网络往返次数并提高网络吞吐量。有关更多信息，请参阅《Amazon EC2 用户指南》**中的[巨型帧（9001 MTU）](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/network_mtu.html#jumbo_frame_instances)。

有关数据库实例类的信息，请参阅 [数据库实例类](Concepts.DBInstanceClass.md)。要确定等同于 Amazon EC2 实例类型的数据库实例类，请将 `db.` 放置在 Amazon EC2 实例类型名称之前。例如，`r5.8xlarge` Amazon EC2 实例等同于 `db.r5.8xlarge` 数据库实例类。

### 提高网络带宽
<a name="wait-event.clientread.actions.increase-network-bandwidth"></a>

使用 `NetworkReceiveThroughput` 和 `NetworkTransmitThroughput` Amazon CloudWatch 指标监控数据库实例上的传入和传出网络流量。这些指标可以帮助您确定网络带宽是否足以满足您的工作负载。

如果您的网络带宽不够，请增加它。如果 AWS 客户端或您的数据库实例已达到网络带宽限制，增加带宽的唯一方法是增加数据库实例大小。有关更多信息，请参阅 [数据库实例类类型](Concepts.DBInstanceClass.Types.md)。

有关 CloudWatch 指标的更多信息，请参阅[Amazon RDS 的 Amazon CloudWatch 指标](rds-metrics.md)。

### 监控网络性能的最大值
<a name="wait-event.clientread.actions.monitor-network-performance"></a>

如果您使用的是 Amazon EC2 客户端，Amazon EC2 会提供网络性能指标的最大值，包括聚合入站和出站网络带宽。它还提供连接跟踪功能，以确保按预期返回数据包以及域名系统 (DNS) 等服务的链接本地服务访问。要监控这些最大值，请使用当前的增强型联网驱动程序并监控客户端的网络性能。

有关更多信息，请参阅《Amazon EC2 用户指南》**中的[监控您的 Amazon EC2 实例的网络性能](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html)，以及《Amazon EC2 用户指南》**中的[监控您的 Amazon EC2 实例的网络性能](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/monitoring-network-performance-ena.html)。

### 监控处于“空闲事务”状态的事务
<a name="wait-event.clientread.actions.check-idle-in-transaction"></a>

检查您是否有越来越多的 `idle in transaction` 连接。要做到这一点，请监控 `pg_stat_activity` 表中的 `state` 列。您可能能够通过运行类似于以下内容的查询来识别连接源。

```
select client_addr, state, count(1) from pg_stat_activity 
where state like 'idle in transaction%' 
group by 1,2 
order by 3 desc
```

# Client:ClientWrite
<a name="wait-event.clientwrite"></a>

当 RDS for PostgreSQL 等待将数据写入客户端时，会发生 `Client:ClientWrite` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.clientwrite.context.supported)
+ [上下文](#wait-event.clientwrite.context)
+ [等待次数增加的可能原因](#wait-event.clientwrite.causes)
+ [操作](#wait-event.clientwrite.actions)

## 支持的引擎版本
<a name="wait-event.clientwrite.context.supported"></a>

RDS for PostgreSQL 版本 10 及更高版本支持此等待事件信息。

## 上下文
<a name="wait-event.clientwrite.context"></a>

客户端进程必须先读取从 RDS for PostgreSQL 数据库集群接收的所有数据，然后集群才能发送更多数据。集群在将更多数据发送给客户端之前等待的时间为 `Client:ClientWrite` 事件。

RDS for PostgreSQL 数据库实例与客户端之间的网络吞吐量降低可能会导致此事件。客户端的 CPU 压力和网络饱和也可能导致此事件。*CPU 压力*是 CPU 被充分利用并且有任务等待 CPU 时间的时间。*网络饱和*是当数据库和客户端之间的网络传输的数据超出其处理能力之时。

## 等待次数增加的可能原因
<a name="wait-event.clientwrite.causes"></a>

`Client:ClientWrite` 显示在主要等待中的常见原因包括以下各项：

**网络延迟增加**  
RDS for PostgreSQL 数据库实例和客户端之间的网络延迟可能会增加。较高的网络延迟会增加客户端接收数据所需的时间。

**客户端负载增加**  
客户端上可能存在 CPU 压力或网络饱和。客户端负载的增加会延迟从 RDS for PostgreSQL 数据库实例接收数据的过程。

**发送到客户端的大量数据**  
RDS for PostgreSQL 数据库实例可能会向客户端发送大量数据。客户端可能无法像集群发送数据那样地快速接收数据。诸如大型表的副本之类的活动可能会导致 `Client:ClientWrite` 事件增加。

## 操作
<a name="wait-event.clientwrite.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [将客户端放置在与集群相同的可用区和 VPC 子网中。](#wait-event.clientwrite.actions.az-vpc-subnet)
+ [使用当前一代实例](#wait-event.clientwrite.actions.db-instance-class)
+ [减少发送到客户端的数据量](#wait-event.clientwrite.actions.reduce-data)
+ [扩展客户端](#wait-event.clientwrite.actions.scale-client)

### 将客户端放置在与集群相同的可用区和 VPC 子网中。
<a name="wait-event.clientwrite.actions.az-vpc-subnet"></a>

为了减少网络延迟并提高网络吞吐量，请将客户端放在与 RDS for PostgreSQL 数据库实例相同的可用区和虚拟私有云（VPC）子网中。

### 使用当前一代实例
<a name="wait-event.clientwrite.actions.db-instance-class"></a>

在某些情况下，您可能没有使用支持巨型帧的数据库实例类。如果您在 Amazon EC2 上运行应用程序，请考虑为客户端使用当前一代实例。另外，在客户端操作系统上配置最大传输单位 (MTU)。这种技术可能会减少网络往返次数并提高网络吞吐量。有关更多信息，请参阅《Amazon EC2 用户指南》**中的[巨型帧（9001 MTU）](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/network_mtu.html#jumbo_frame_instances)。

有关数据库实例类的信息，请参阅 [数据库实例类](Concepts.DBInstanceClass.md)。要确定等同于 Amazon EC2 实例类型的数据库实例类，请将 `db.` 放置在 Amazon EC2 实例类型名称之前。例如，`r5.8xlarge` Amazon EC2 实例等同于 `db.r5.8xlarge` 数据库实例类。

### 减少发送到客户端的数据量
<a name="wait-event.clientwrite.actions.reduce-data"></a>

如果可能，请调整应用程序以减少 RDS for PostgreSQL 数据库实例发送给客户端的数据量。进行这样的调整可以减轻客户端上的 CPU 和网络争用。

### 扩展客户端
<a name="wait-event.clientwrite.actions.scale-client"></a>

使用 Amazon CloudWatch 或其他主机指标，确定您的客户端当前是受 CPU 或网络带宽的限制，还是受此两者的限制。如果客户端受到限制，请相应地扩展您的客户端。

# CPU
<a name="wait-event.cpu"></a>

当线程在 CPU 中处于活动状态或正在等待 CPU 时，会发生此事件。

**Topics**
+ [支持的引擎版本](#wait-event.cpu.context.supported)
+ [上下文](#wait-event.cpu.context)
+ [等待次数增加的可能原因](#wait-event.cpu.causes)
+ [操作](#wait-event.cpu.actions)

## 支持的引擎版本
<a name="wait-event.cpu.context.supported"></a>

此等待事件信息与所有的 RDS for PostgreSQL 版本相关。

## 上下文
<a name="wait-event.cpu.context"></a>

*中央处理单元 (CPU)* 是运行指令的计算机的组件。例如，CPU 指令执行算术运算并在内存中交换数据。如果查询增加了通过数据库引擎执行的指令的数量，则运行查询所花费的时间将增加。*CPU 调度*正在为进程提供 CPU 时间。调度由操作系统的内核编排。

**Topics**
+ [如何判断此等待何时发生](#wait-event.cpu.when-it-occurs)
+ [DBLoadCPU 指标](#wait-event.cpu.context.dbloadcpu)
+ [os.cpuUtilization 指标](#wait-event.cpu.context.osmetrics)
+ [CPU 调度的可能原因](#wait-event.cpu.context.scheduling)

### 如何判断此等待何时发生
<a name="wait-event.cpu.when-it-occurs"></a>

该 `CPU` 等待事件表示后端进程在 CPU 中处于活动状态或正在等待 CPU。您知道，当查询显示以下信息时会发生这种情况：
+ The `pg_stat_activity.state` column has the value `active`。
+ `pg_stat_activity` 中的 `wait_event_type` 和 `wait_event` 列都是 `null`。

要查看正在使用或等待 CPU 的后端进程，请运行以下查询。

```
SELECT * 
FROM   pg_stat_activity
WHERE  state = 'active'
AND    wait_event_type IS NULL
AND    wait_event IS NULL;
```

### DBLoadCPU 指标
<a name="wait-event.cpu.context.dbloadcpu"></a>

CPU 的性能详情指标为 `DBLoadCPU`。`DBLoadCPU` 的值可能与 Amazon CloudWatch 指标 `CPUUtilization` 的值不同。后一个指标是从 Hypervisor 中收集的，用于数据库实例。

### os.cpuUtilization 指标
<a name="wait-event.cpu.context.osmetrics"></a>

性能详情操作系统指标提供有关 CPU 利用率的详细信息。例如，您可以显示以下指标：
+ `os.cpuUtilization.nice.avg`
+ `os.cpuUtilization.total.avg`
+ `os.cpuUtilization.wait.avg`
+ `os.cpuUtilization.idle.avg`

性能详情将数据库引擎的 CPU 使用情况报告为 `os.cpuUtilization.nice.avg`。

### CPU 调度的可能原因
<a name="wait-event.cpu.context.scheduling"></a>

 操作系统（OS）内核处理 CPU 的调度。当 CPU 处于*活动状态*时，进程可能需要等待才能获得调度。CPU 在执行计算时处于活动状态。当它有一个未运行的空闲线程（也即，一个等待内存输入/输出的空闲线程）时也处于活动状态。这种类型的 I/O 主导着典型的数据库工作负载。

满足以下条件时，进程可能会等待获得 CPU 调度：
+ CloudWatch `CPUUtilization` 指标接近 100%。
+ 平均负载大于 vCPU 的数量，表示负载过重。您可以在性能详情中的操作系统指标部分找到 `loadAverageMinute` 指标。

## 等待次数增加的可能原因
<a name="wait-event.cpu.causes"></a>

当此事件的发生率超过正常（可能表示性能问题）时，典型的原因包括以下几点。

**Topics**
+ [突然猛增的可能原因](#wait-event.cpu.causes.spikes)
+ [长期高频的可能原因](#wait-event.cpu.causes.long-term)
+ [极端状况](#wait-event.cpu.causes.corner-cases)

### 突然猛增的可能原因
<a name="wait-event.cpu.causes.spikes"></a>

突然猛增的最可能原因如下：
+ 您的应用程序打开了太多与数据库同时连接。这种情况被称为“连接风暴”。
+ 您的应用程序工作负载在以下任一方面发生变化：
  + 新查询
  + 数据集的大小增加
  + 索引维护或创建
  + 新函数
  + 新的运营商
  + 并行查询执行增加
+ 您的查询执行计划已更改。在某些情况下，更改可能会导致缓冲区增加。例如，查询之前使用索引，而现在正在使用顺序扫描。在这种情况下，查询需要更多的 CPU 才能实现同样的目标。

### 长期高频的可能原因
<a name="wait-event.cpu.causes.long-term"></a>

长期反复出现的事件的最可能原因：
+ CPU 上同时运行的后端进程太多。这些进程可以是并行工件。
+ 查询执行不佳，因为它们需要大量缓冲区。

### 极端状况
<a name="wait-event.cpu.causes.corner-cases"></a>

如果所有可能的原因都不是实际原因，则可能会发生以下情况：
+ CPU 正在换入和换出进程。
+ 如果关闭了*大页*功能，CPU 可能正在管理页表条目。原定设置情况下，微型、小型和中型数据库实例类以外的所有数据库实例类都会开启内存管理功能。有关更多信息，请参阅 [适用于 RDS for PostgreSQL 的大页](PostgreSQL.Concepts.General.FeatureSupport.HugePages.md)。

## 操作
<a name="wait-event.cpu.actions"></a>

如果 `CPU` 等待事件主导着数据库活动，它不一定表示性能问题。只在性能下降时应对此事件。

**Topics**
+ [调查数据库是否导致 CPU 增加](#wait-event.cpu.actions.db-CPU)
+ [确定连接数量是否增加](#wait-event.cpu.actions.connections)
+ [响应工作负载变化](#wait-event.cpu.actions.workload)

### 调查数据库是否导致 CPU 增加
<a name="wait-event.cpu.actions.db-CPU"></a>

检查性能详情中的 `os.cpuUtilization.nice.avg` 指标。如果此值远低于 CPU 使用率，则非数据库进程是 CPU 的主要贡献者。

### 确定连接数量是否增加
<a name="wait-event.cpu.actions.connections"></a>

检查 Amazon CloudWatch 中的 `DatabaseConnections` 指标。您的操作取决于 CPU 等待事件增加期间该数量是增加还是减少。

#### 连接增加
<a name="wait-event.cpu.actions.connections.increased"></a>

如果连接数量增加，请将消耗 CPU 的后端进程数与 vCPU 的数量进行比较。以下是可能的情况：
+ 消耗 CPU 的后端进程数量少于 vCPU 的数量。

  在这种情况下，连接数量不是问题。但是，您仍然可以尝试降低 CPU 利用率。
+ 消耗 CPU 的后端进程数量大于 vCPU 的数量。

  在这种情况下，需考虑以下选项：
  + 减少连接到数据库的后端进程的数量。例如，实施连接池解决方案，例如 RDS 代理。要了解更多信息，请参阅[Amazon RDS 代理](rds-proxy.md)。
  + 升级实例大小以获得更多 vCPU 数量。
  + 如果适用，将一些只读工作负载重新导向到读取器节点。

#### 连接未增加
<a name="wait-event.cpu.actions.connections.decreased"></a>

检查性能详情中的 `blks_hit` 指标。寻找 `blks_hit` 增加与 CPU 使用率之间的相关性。以下是可能的情况：
+ CPU 使用率和 `blks_hit` 具有相关性。

  在这种情况下，找到与 CPU 使用率相关联的主要 SQL 语句，然后查找计划更改。您可以使用下面的方法之一：
  + 手动解释计划并将其与预期的执行计划进行比较。
  + 寻找每秒数据块命中量和每秒局部数据块命中量的增加。在性能详情控制面板的**主要 SQL** 部分中，选择 **Preferences**（首选项）。
+ CPU 使用率和 `blks_hit` 无关。

  在这种情况下，请确定是否出现以下任一情况：
  + 应用程序正在快速连接到数据库并断开与数据库的连接。

    通过打开 `log_connections` 和 `log_disconnections`，然后分析 PostgreSQL 日志来诊断此行为。考虑使用 `pgbadger` 日志分析器。有关更多信息，请参阅 [https://github.com/darold/pgbadger](https://github.com/darold/pgbadger)。
  + 操作系统已超载。

    在这种情况下，性能详情显示，后端进程使用 CPU 的时间比平时更长。在性能详情 `os.cpuUtilization` 指标或 CloudWatch `CPUUtilization` 指标中寻找证据。如果操作系统过载，请查看增强监控指标以进一步诊断。具体来说，请查看进程列表以及每个进程占用的 CPU 百分比。
  + 主要 SQL 语句消耗的 CPU 太多。

    检查与 CPU 使用率相关联的语句，看看它们是否可以使用更少的 CPU。运行 `EXPLAIN` 命令，并将重点放在影响最大的计划节点上。考虑使用 PostgreSQL 执行计划可视化工具。要试用此工具，请参阅 [http://explain.dalibo.com/](http://explain.dalibo.com/)。

### 响应工作负载变化
<a name="wait-event.cpu.actions.workload"></a>

如果您的工作负载发生了变化，请查找以下类型的更改：

新查询  
检查是否预计会出现新的查询。如果是，请确保他们的执行计划和每秒执行次数符合预期。

数据集的大小增加  
确定分区（如果尚未实施）是否有帮助。此策略可能会减少查询需要检索的页数。

索引维护或创建  
检查维护时间表是否符合预期。最佳实践是在高峰活动之外安排维护活动。

新函数  
检查这些功能在测试期间是否按预期运行。具体来说，检查每秒执行次数是否符合预期。

新的运营商  
检查它们在测试期间是否按预期运行。

运行并行查询的增加  
确定是否出现以下任一情况：  
+ 所涉及的关系或索引的规模突然增长，因此它们与 `min_parallel_table_scan_size` 或 `min_parallel_index_scan_size` 显著不同。
+ 最近对 `parallel_setup_cost` 或 `parallel_tuple_cost` 进行了更改。
+ 最近对 `max_parallel_workers` 或 `max_parallel_workers_per_gather` 进行了更改。

# IO:BufFileRead 和 IO:BufFileWrite
<a name="wait-event.iobuffile"></a>

`IO:BufFileRead` 和 `IO:BufFileWrite` 事件发生在 RDS for PostgreSQL 创建临时文件时。当操作需要的内存超过当前定义的工作内存参数时，它们会将临时数据写入持久性存储。此操作有时被称为*溢出到磁盘*。有关临时文件及其用法的更多信息，请参阅[使用 PostgreSQL 管理临时文件](PostgreSQL.ManagingTempFiles.md)。

**Topics**
+ [支持的引擎版本](#wait-event.iobuffile.context.supported)
+ [上下文](#wait-event.iobuffile.context)
+ [等待次数增加的可能原因](#wait-event.iobuffile.causes)
+ [操作](#wait-event.iobuffile.actions)

## 支持的引擎版本
<a name="wait-event.iobuffile.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.iobuffile.context"></a>

`IO:BufFileRead` 和 `IO:BufFileWrite` 与工作内存区域和维护工作内存区域有关。有关这些本地内存区域的更多信息，请参阅 PostgreSQL 文档中的[资源消耗量](https://www.postgresql.org/docs/current/runtime-config-resource.html)。

`work_mem` 的原定设置值为 4MB。如果一个会话并行执行操作，则处理并行性的每个工件将使用 4MB 的内存。出于此原因，请仔细设置 `work_mem`。如果您将值增加的太大，则运行很多会话的数据库可能会占用太多内存。如果您将值设置得太低，RDS for PostgreSQL 会在本地存储中创建临时文件。这些临时文件的磁盘输入/输出可能会降低性能。

如果观察到以下事件顺序，则数据库可能正在生成临时文件：

1. 可用性突然急剧下降

1. 可用空间的快速恢复

您可能还会看到“chainsaw”模式。此模式可能表明您的数据库在不断创建小文件。

## 等待次数增加的可能原因
<a name="wait-event.iobuffile.causes"></a>

一般来说，这些等待事件是占用内存比 `work_mem` 或 `maintenance_work_mem` 参数分配的内存更多的操作造成。为了进行补偿，操作会写入临时文件。`IO:BufFileRead` 和 `IO:BufFileWrite` 事件的常见原因包括以下内容：

**需要比工作内存区域中存在的内存更多的查询**  
具有以下特征的查询使用工作内存区域：  
+ 哈希联接
+ `ORDER BY` 子句
+ `GROUP BY` 子句
+ `DISTINCT`
+ 窗口函数
+ `CREATE TABLE AS SELECT`
+ 具体化视图刷新

**需要比维护工作内存区域中存在的内存更多的语句**  
以下语句使用维护工作内存区域：  
+ `CREATE INDEX`
+ `CLUSTER`

## 操作
<a name="wait-event.iobuffile.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [识别问题](#wait-event.iobuffile.actions.problem)
+ [请检查您的联接查询](#wait-event.iobuffile.actions.joins)
+ [检查您的 ORDER BY 和 GROUP BY 查询](#wait-event.iobuffile.actions.order-by)
+ [避免使用 DISTINCT 操作](#wait-event.iobuffile.actions.distinct)
+ [考虑使用窗口函数而不是 GROUP BY 函数](#wait-event.iobuffile.actions.window)
+ [调查具体化视图和 CTAS 语句](#wait-event.iobuffile.actions.mv-refresh)
+ [在重新构建索引时使用 pg\$1repack](#wait-event.iobuffile.actions.pg_repack)
+ [聚集表时，增加 maintenance\$1work\$1mem](#wait-event.iobuffile.actions.cluster)
+ [优化内存以防止 IO:BufFileRead 和 IO:BufFileWrite](#wait-event.iobuffile.actions.tuning-memory)

### 识别问题
<a name="wait-event.iobuffile.actions.problem"></a>

您可以直接在性能详情中查看临时文件的使用情况。有关更多信息，请参阅 [使用性能详情查看临时文件使用情况](PostgreSQL.ManagingTempFiles.Example.md)。禁用性能详情后，您可能会注意到 `IO:BufFileRead` 和 `IO:BufFileWrite` 操作量有所增加。

要确定问题的根源，可以将 `log_temp_files` 参数设置为记录所生成的临时文件超过指定阈值 KB 的所有查询。原定设置情况下，`log_temp_files` 设置为 `-1`，这将关闭此日志记录功能。如果您将此参数设置为 `0`，RDS for PostgreSQL 会记录所有临时文件。如果值为 `1024`，RDS for PostgreSQL 会记录所生成的临时文件大于 1MB 的所有查询。有关 `log_temp_files` 更多信息，请参阅 PostgreSQL 文档中的[错误报告和日志记录](https://www.postgresql.org/docs/current/runtime-config-logging.html)。

### 请检查您的联接查询
<a name="wait-event.iobuffile.actions.joins"></a>

您的查询很可能使用联接。例如，以下查询将四个表联接到一起。

```
SELECT * 
       FROM "order" 
 INNER JOIN order_item 
       ON (order.id = order_item.order_id)
 INNER JOIN customer 
       ON (customer.id = order.customer_id)
 INNER JOIN customer_address 
       ON (customer_address.customer_id = customer.id AND 
           order.customer_address_id = customer_address.id)
 WHERE customer.id = 1234567890;
```

临时文件使用率激增的可能原因是查询本身存在问题。例如，中断的子句可能无法正确筛选联接。考虑以下示例中的第二个内联接。

```
SELECT * 
       FROM "order"
 INNER JOIN order_item 
       ON (order.id = order_item.order_id)
 INNER JOIN customer 
       ON (customer.id = customer.id)
 INNER JOIN customer_address 
       ON (customer_address.customer_id = customer.id AND 
           order.customer_address_id = customer_address.id)
 WHERE customer.id = 1234567890;
```

前面的查询错误地将 `customer.id` 与 `customer.id` 进行了联接，在每个客户和每个订单之间生成了笛卡尔积。这种类型的意外联接会生成大型临时文件。根据表的大小，笛卡尔查询甚至可以填满存储空间。满足以下条件时，您的应用程序可能会有笛卡尔联接：
+ 您可以看到存储可用性大幅下降，然后是快速恢复。
+ 现在没有创建任何索引。
+ 现在没有发布任何 `CREATE TABLE FROM SELECT` 语句。
+ 没有进行任何具体化视图的刷新。

要查看是否使用正确的键联接表，请检查查询和对象关系映射指令。请记住，应用程序的某些查询不会总是被调用，而且有些查询是动态生成的。

### 检查您的 ORDER BY 和 GROUP BY 查询
<a name="wait-event.iobuffile.actions.order-by"></a>

在某些情况下，`ORDER BY` 子句可能会导致过多的临时文件。请考虑以下准则：
+ 当需要对它们进行排序时，只包括 `ORDER BY` 子句中的列。本指南对于返回数千行并在 `ORDER BY` 子句中指定很多列的查询尤其重要。
+ 考虑创建索引以在 `ORDER BY` 子句与具有相同升序或降序的列匹配时对它们进行加速。部分索引更可取，因为它们较小。较小的索引可以更快地读取和遍历。
+ 如果为可以接受 null 值的列创建索引，请考虑是希望将 null 值存储在索引的末尾还是在索引的开头存储。

  如果可能，通过筛选结果集来减少需要排序的行数。如果您使用 `WITH` 子句语句或子查询，请记住，内部查询会生成一个结果集并会将其传递给外部查询。查询可以筛选出的行越多，查询需要进行的排序就越少。
+ 如果您不需要获取完整的结果集，请使用 `LIMIT` 子句。例如，如果您只想要前五行，则使用 `LIMIT` 子句的查询不会继续生成结果。这样，查询需要更少的内存和临时文件。

使用 `GROUP BY` 子句的查询也可能需要临时文件。`GROUP BY` 查询通过使用以下函数汇总值：
+ `COUNT`
+ `AVG`
+ `MIN`
+ `MAX`
+ `SUM`
+ `STDDEV`

要优化 `GROUP BY` 查询，请按照 `ORDER BY` 查询的建议。

### 避免使用 DISTINCT 操作
<a name="wait-event.iobuffile.actions.distinct"></a>

如果可能的话，避免使用 `DISTINCT` 操作来删除重复的行。查询返回的不必要和重复的行越多，`DISTINCT` 操作就会越昂贵。如果可能，请在 `WHERE` 子句中添加筛选条件，即使您对不同的表使用相同的筛选条件。筛选查询并正确联接可以提高性能并减少资源使用。它还可以防止错误的报告和结果。

如果您需要将 `DISTINCT` 用于同一个表的多行，请考虑创建复合索引。将索引中的多个列进行分组可以缩短评估不同行的时间。此外，如果您使用 RDS for PostgreSQL 版本 10 或更高版本，则可以使用 `CREATE STATISTICS` 命令在多个列之间关联统计数据。

### 考虑使用窗口函数而不是 GROUP BY 函数
<a name="wait-event.iobuffile.actions.window"></a>

使用 `GROUP BY`，您可以更改结果集，然后检索聚合的结果。使用窗口函数，可以在不更改结果集的情况下聚合数据。窗口函数使用 `OVER` 子句来跨查询定义的集执行计算，从而将一行与另一行关联。您可以使用窗口函数中的所有 `GROUP BY` 函数，但也可以使用以下函数：
+ `RANK`
+ `ARRAY_AGG`
+ `ROW_NUMBER`
+ `LAG`
+ `LEAD`

为了尽量减少窗口函数生成的临时文件的数量，请在需要两个不同的聚合时删除同一结果集的重复项。请考虑以下查询。

```
SELECT sum(salary) OVER (PARTITION BY dept ORDER BY salary DESC) as sum_salary
     , avg(salary) OVER (PARTITION BY dept ORDER BY salary ASC) as avg_salary
  FROM empsalary;
```

您可以使用如下 `WINDOW` 子句重新写入查询。

```
SELECT sum(salary) OVER w as sum_salary
         , avg(salary) OVER w as_avg_salary
    FROM empsalary
  WINDOW w AS (PARTITION BY dept ORDER BY salary DESC);
```

原定设置情况下，RDS for PostgreSQL 执行计划器会整合类似的节点，这样它就不会重复操作。但是，通过对窗口数据块使用显式声明，您可以更轻松地维护查询。您还可以通过防止重复来提高性能。

### 调查具体化视图和 CTAS 语句
<a name="wait-event.iobuffile.actions.mv-refresh"></a>

当具体化视图刷新时，它会运行查询。此查询可以包含 `GROUP BY`、`ORDER BY` 或 `DISTINCT` 之类的操作。刷新期间，您可能会观察到大量临时文件以及等待事件 `IO:BufFileWrite` 和 `IO:BufFileRead`。同样地，当您根据 `SELECT` 语句创建表时，`CREATE TABLE` 语句会运行查询。要减少所需的临时文件，请优化查询。

### 在重新构建索引时使用 pg\$1repack
<a name="wait-event.iobuffile.actions.pg_repack"></a>

创建索引时，引擎会对结果集进行排序。随着表的大小增加以及索引列中的值变得更加多样化，临时文件需要更多的空间。在大多数情况下，如果不修改维护工作内存区域，就无法阻止为大型表创建临时文件。有关 `maintenance_work_mem` 的更多信息，请参阅 PostgreSQL 文档中的[https://www.postgresql.org/docs/current/runtime-config-resource.html](https://www.postgresql.org/docs/current/runtime-config-resource.html)。

重新创建大型索引时可能的解决方法是使用 pg\$1repack 扩展。有关更多信息，请参阅 pg\$1repack 文档中的[用最少的锁定重新组织 PostgreSQL 数据库中的表](https://reorg.github.io/pg_repack/)。有关在 RDS for PostgreSQL 数据库实例中设置此扩展的信息，请参阅[使用 pg\$1repack 扩展减少表和索引的膨胀](Appendix.PostgreSQL.CommonDBATasks.pg_repack.md)。

### 聚集表时，增加 maintenance\$1work\$1mem
<a name="wait-event.iobuffile.actions.cluster"></a>

`CLUSTER` 命令基于 *index\$1name* 指定的现有索引聚集 *table\$1name* 指定的表。RDS for PostgreSQL 以物理方式重新创建表以匹配给定索引的顺序。

当磁性存储普遍存在时，集群很常见，因为存储吞吐量有限。由于基于 SSD 的存储已经很常见，因此集群不太受欢迎。但是，如果对表进行聚集，您仍然可以根据表大小、索引、查询等稍微提高性能。

如果您运行 `CLUSTER` 命令并观察到等待事件 `IO:BufFileWrite` 和 `IO:BufFileRead`，请优化 `maintenance_work_mem`。将内存大小增加到相当大的量。较高的值意味着引擎可以使用更多内存进行集群操作。

### 优化内存以防止 IO:BufFileRead 和 IO:BufFileWrite
<a name="wait-event.iobuffile.actions.tuning-memory"></a>

在某些情况下，您需要优化内存。您的目标是使用相应的参数平衡以下消耗区域间的内存，如下所示。
+ `work_mem` 值 
+ 折扣 `shared_buffers` 值后剩余的内存
+ 已打开和使用中的最大连接数，受限于 `max_connections`

有关优化内存的更多信息，请参阅 PostgreSQL 文档中的[资源消耗量](https://www.postgresql.org/docs/current/runtime-config-resource.html)。

#### 增加工作内存区域的大小
<a name="wait-event.iobuffile.actions.tuning-memory.work-mem"></a>

在某些情况下，唯一的选项是增加会话使用的内存。如果您的查询编写正确并且正在使用正确的键进行连接，请考虑增加 `work_mem` 值。

要了解查询生成了多少个临时文件，请将 `log_temp_files` 设置为 `0`。如果您将 `work_mem` 值增加为日志中标识的最大值，则可以防止查询生成临时文件。但是，`work_mem` 为每个连接或并行工件设置每个计划节点的最大值。如果数据库有 5000 个连接，并且每个连接使用 256MiB 内存，则引擎需要 1.2TiB 的 RAM。因此，您的实例可能会耗尽内存。

#### 为共享缓冲池预留足够的内存
<a name="wait-event.iobuffile.actions.tuning-memory.shared-pool"></a>

您的数据库使用很多内存区域，例如共享缓冲池，而不仅仅是工作内存区域。在增加 `work_mem` 之前考虑这些额外的内存区域的要求。

例如，假设您的 RDS for PostgreSQL 实例类为 db.r5.2xlarge。此实例类拥有 64GiB 的内存。原定设置情况下，将 25% 的内存预留为共享缓冲池。减去分配给共享内存区域的量后，仍然有 16384 MB。不要将剩余内存专门分配给工作内存区域，因为操作系统和引擎还需要内存。

您可以分配给 `work_mem` 的内存取决于实例类。如果您使用较大的实例类，则可用的内存更多。但是，在前面的示例中，您不能使用超过 16GiB 的内存。否则，当内存耗尽时，您的实例将变得不可用。要从不可用状态恢复实例，RDS for PostgreSQL 自动化服务会自动重新启动。

#### 管理连接数
<a name="wait-event.iobuffile.actions.tuning-memory.connections"></a>

假设您的数据库实例具有 5000 个同时连接。每个连接至少使用 4MiB 的 `work_mem` 连接的内存消耗过高可能会降低性能。作为响应，您可进行以下选择：
+ 升级到更大的实例类。
+ 使用连接代理或池程序减少同时数据库连接的数量。

对于代理，请考虑 Amazon RDS 代理、pgBouncer 或基于您的应用程序的连接池程序。此解决方案减轻了 CPU 负载。它还可以降低所有连接都需要工作内存区域时的风险。当数据库连接较少时，您可以增加 `work_mem` 的值。通过这种方式，您可以减少 `IO:BufFileRead` 和 `IO:BufFileWrite` 等待事件的发生率。此外，等待工作内存区域的查询显著加速。

# IO:DataFileRead
<a name="wait-event.iodatafileread"></a>

当由于分页在共享内存中不可用，连接等待后端进程从存储中读取所需分页时，会发生 `IO:DataFileRead` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.iodatafileread.context.supported)
+ [上下文](#wait-event.iodatafileread.context)
+ [等待次数增加的可能原因](#wait-event.iodatafileread.causes)
+ [操作](#wait-event.iodatafileread.actions)

## 支持的引擎版本
<a name="wait-event.iodatafileread.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.iodatafileread.context"></a>

所有查询和数据处理 (DML) 操作都会访问缓冲池中的页面。可以诱导读取的语句包括 `SELECT`、`UPDATE` 和 `DELETE`。例如，`UPDATE` 可以从表或索引中读取页面。如果请求或更新的页面不在共享缓冲池中，则此读取可能会导致 `IO:DataFileRead` 事件。

由于共享缓冲池是有限的，所以它可以填满。在这种情况下，对不在内存中的页面的请求会强制数据库从磁盘中读取数据块。如果 `IO:DataFileRead` 事件频繁发生，您的共享缓冲池可能太小，从而无法容纳您的工作负载。对于读取大量不适合缓冲池的行的 `SELECT` 查询，此问题很严重。有关缓冲区池的更多信息，请参阅 PostgreSQL 文档中的[资源消耗量](https://www.postgresql.org/docs/current/runtime-config-resource.html)。

## 等待次数增加的可能原因
<a name="wait-event.iodatafileread.causes"></a>

`IO:DataFileRead` 事件的常见原因包括以下各项：

**连接激增**  
您可能会发现多个连接生成相同数量的 IO:DataFileRead 等待事件。在这种情况下，`IO:DataFileRead` 事件可能会发生激增（突然大幅度增加）。

**执行顺序扫描的 SELECT 和 DML 语句**  
您的应用程序可能正在执行新的操作。或者，现有的操作可能会因为新的执行计划而发生变化。在这种情况下，请查找具有更大的 `seq_scan` 值的表格（特别是大型表格）。通过查询 `pg_stat_user_tables` 查找它们。要跟踪生成更多读取操作的查询，请使用扩展 `pg_stat_statements`。

**适用于大型数据集的 CTAS 和 CREATE INDEX**  
*CTAS* 是一个 `CREATE TABLE AS SELECT` 语句。如果您使用大型数据集作为源来运行 CTAS，或者在大型表上创建索引，则可能会发生 `IO:DataFileRead` 事件。创建索引时，数据库可能需要使用顺序扫描读取整个对象。当页面不在内存中时，CTAS 会生成 `IO:DataFile` 读取。

**多个 vacuum 工件同时运行**  
vacuum 工件可以手动或自动触发。我们建议采取积极的 vacuum 策略。但是，当表中有许多更新或删除的行时，`IO:DataFileRead` 等待增加。回收空间后，花在 `IO:DataFileRead` 上的 vacuum 时间减少。

**摄取大量数据**  
当您的应用程序提取大量数据时，`ANALYZE` 操作可能会更频繁地发生。`ANALYZE` 进程可以由 Autovacuum 启动程序触发，也可以手动调用。  
`ANALYZE` 操作可以读取表的子集。必须扫描的页数通过将 30 乘以 `default_statistics_target` 值进行计算。有关更多信息，请参阅 [PostgreSQL 文档](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-DEFAULT-STATISTICS-TARGET)。`default_statistics_target` 参数接受 1 到 10000 之间的值，其中原定设置值为 100。

**资源匮乏**  
如果消耗了实例网络带宽或 CPU，`IO:DataFileRead` 事件可能会更频繁地发生。

## 操作
<a name="wait-event.iodatafileread.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [检查谓词筛选条件是否存在生成等待的查询](#wait-event.iodatafileread.actions.filters)
+ [尽量减少维护操作的影响](#wait-event.iodatafileread.actions.maintenance)
+ [响应大量连接](#wait-event.iodatafileread.actions.connections)

### 检查谓词筛选条件是否存在生成等待的查询
<a name="wait-event.iodatafileread.actions.filters"></a>

假设您确定了正在生成 `IO:DataFileRead` 等待事件的特定查询。您可以使用以下方法识别它们：
+ 性能详情
+ 目录视图，例如扩展程序 `pg_stat_statements` 提供的视图
+ 目录视图 `pg_stat_all_tables`，如果它定期显示物理读取数量增加
+ `pg_statio_all_tables` 视图，如果它显示 `_read` 计数器正在增加

我们建议您确定这些查询的谓词（`WHERE` 子句）中使用了哪些筛选条件。请遵循以下准则：
+ 运行 `EXPLAIN` 命令。在输出中，确定使用的扫描类型。顺序扫描不一定表示存在问题。与使用筛选条件的查询相比，使用顺序扫描的查询自然会产生更多的 `IO:DataFileRead` 事件。

  了解 `WHERE` 子句中列出的列是否已编入索引。如果没有，请考虑为此列创建索引。这种方法避免了顺序扫描并减少了 `IO:DataFileRead` 事件。如果某个查询具有限制性筛选条件并且仍然生成顺序扫描，请评估是否使用了正确的索引。
+ 了解查询是否正在访问非常大的表。在某些情况下，对表进行分区可以提高性能，从而允许查询只读取必要的分区。
+ 检查联接操作的基数（总行数）。请注意您在筛选条件中为您的 `WHERE` 子句传递的值的限制性。如果可能，请优化查询以减少在计划的每个步骤中传递的行数。

### 尽量减少维护操作的影响
<a name="wait-event.iodatafileread.actions.maintenance"></a>

维护操作（例如 `VACUUM` 和 `ANALYZE`）非常重要。我们建议您不要将其关闭，因为您会找到与这些维护操作相关的 `IO:DataFileRead` 等待事件。以下方法可以最大限度地减少这些操作的影响：
+ 在非高峰时段手动运行维护操作。此方法可防止数据库达到自动操作的阈值。
+ 对于非常大的表，请考虑对表进行分区。这种方法减少了维护操作的开销。数据库只访问需要维护的分区。
+ 当您摄取大量数据时，请考虑禁用自动分析功能。

当以下公式为真时，系统会自动为表触发 Autovacuum 功能。

```
pg_stat_user_tables.n_dead_tup > (pg_class.reltuples x autovacuum_vacuum_scale_factor) + autovacuum_vacuum_threshold
```

视图 `pg_stat_user_tables` 和目录 `pg_class` 有多个行。一行可以对应于表中的一行。这个公式假设 `reltuples` 适用于特定的表。参数 `autovacuum_vacuum_scale_factor`（原定设置为 0.20）和 `autovacuum_vacuum_threshold`（原定设置为 50 个元组）通常在全局范围内为整个实例设置。但是，您可以为特定表设置不同的值。

**Topics**
+ [查找不必要地占用空间的表](#wait-event.iodatafileread.actions.maintenance.tables)
+ [查找不必要地占用空间的索引](#wait-event.iodatafileread.actions.maintenance.indexes)
+ [查找符合 Autovacuum 操作条件的表](#wait-event.iodatafileread.actions.maintenance.autovacuumed)

#### 查找不必要地占用空间的表
<a name="wait-event.iodatafileread.actions.maintenance.tables"></a>

要查找不必要地占用空间的表，可以使用 PostgreSQL `pgstattuple` 扩展中的函数。原定设置情况下，此扩展（模块）在所有 RDS for PostgreSQL 数据库实例上均可用，并且可以使用以下命令在实例上进行实例化。

```
CREATE EXTENSION pgstattuple;
```

有关此扩展的更多信息，请参阅 PostgreSQL 文档中的 [pgstattuple](https://www.postgresql.org/docs/current/pgstattuple.html)。

您可以在应用程序中检查表和索引膨胀。有关更多信息，请参[诊断表和索引膨胀](https://docs.aws.amazon.com//AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.diag-table-ind-bloat.html)。

#### 查找不必要地占用空间的索引
<a name="wait-event.iodatafileread.actions.maintenance.indexes"></a>

要查找臃肿的索引并估计在您具有读取权限的表上不必要地消耗的空间量，可以运行以下查询。

```
-- WARNING: rows with is_na = 't' are known to have bad statistics ("name" type is not supported).
-- This query is compatible with PostgreSQL 8.2 and later.

SELECT current_database(), nspname AS schemaname, tblname, idxname, bs*(relpages)::bigint AS real_size,
  bs*(relpages-est_pages)::bigint AS extra_size,
  100 * (relpages-est_pages)::float / relpages AS extra_ratio,
  fillfactor, bs*(relpages-est_pages_ff) AS bloat_size,
  100 * (relpages-est_pages_ff)::float / relpages AS bloat_ratio,
  is_na
  -- , 100-(sub.pst).avg_leaf_density, est_pages, index_tuple_hdr_bm, 
  -- maxalign, pagehdr, nulldatawidth, nulldatahdrwidth, sub.reltuples, sub.relpages 
  -- (DEBUG INFO)
FROM (
  SELECT coalesce(1 +
       ceil(reltuples/floor((bs-pageopqdata-pagehdr)/(4+nulldatahdrwidth)::float)), 0 
       -- ItemIdData size + computed avg size of a tuple (nulldatahdrwidth)
    ) AS est_pages,
    coalesce(1 +
       ceil(reltuples/floor((bs-pageopqdata-pagehdr)*fillfactor/(100*(4+nulldatahdrwidth)::float))), 0
    ) AS est_pages_ff,
    bs, nspname, table_oid, tblname, idxname, relpages, fillfactor, is_na
    -- , stattuple.pgstatindex(quote_ident(nspname)||'.'||quote_ident(idxname)) AS pst, 
    -- index_tuple_hdr_bm, maxalign, pagehdr, nulldatawidth, nulldatahdrwidth, reltuples 
    -- (DEBUG INFO)
  FROM (
    SELECT maxalign, bs, nspname, tblname, idxname, reltuples, relpages, relam, table_oid, fillfactor,
      ( index_tuple_hdr_bm +
          maxalign - CASE -- Add padding to the index tuple header to align on MAXALIGN
            WHEN index_tuple_hdr_bm%maxalign = 0 THEN maxalign
            ELSE index_tuple_hdr_bm%maxalign
          END
        + nulldatawidth + maxalign - CASE -- Add padding to the data to align on MAXALIGN
            WHEN nulldatawidth = 0 THEN 0
            WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign
            ELSE nulldatawidth::integer%maxalign
          END
      )::numeric AS nulldatahdrwidth, pagehdr, pageopqdata, is_na
      -- , index_tuple_hdr_bm, nulldatawidth -- (DEBUG INFO)
    FROM (
      SELECT
        i.nspname, i.tblname, i.idxname, i.reltuples, i.relpages, i.relam, a.attrelid AS table_oid,
        current_setting('block_size')::numeric AS bs, fillfactor,
        CASE -- MAXALIGN: 4 on 32bits, 8 on 64bits (and mingw32 ?)
          WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8
          ELSE 4
        END AS maxalign,
        /* per page header, fixed size: 20 for 7.X, 24 for others */
        24 AS pagehdr,
        /* per page btree opaque data */
        16 AS pageopqdata,
        /* per tuple header: add IndexAttributeBitMapData if some cols are null-able */
        CASE WHEN max(coalesce(s.null_frac,0)) = 0
          THEN 2 -- IndexTupleData size
          ELSE 2 + (( 32 + 8 - 1 ) / 8) 
          -- IndexTupleData size + IndexAttributeBitMapData size ( max num filed per index + 8 - 1 /8)
        END AS index_tuple_hdr_bm,
        /* data len: we remove null values save space using it fractionnal part from stats */
        sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024)) AS nulldatawidth,
        max( CASE WHEN a.atttypid = 'pg_catalog.name'::regtype THEN 1 ELSE 0 END ) > 0 AS is_na
      FROM pg_attribute AS a
        JOIN (
          SELECT nspname, tbl.relname AS tblname, idx.relname AS idxname, 
            idx.reltuples, idx.relpages, idx.relam,
            indrelid, indexrelid, indkey::smallint[] AS attnum,
            coalesce(substring(
              array_to_string(idx.reloptions, ' ')
               from 'fillfactor=([0-9]+)')::smallint, 90) AS fillfactor
          FROM pg_index
            JOIN pg_class idx ON idx.oid=pg_index.indexrelid
            JOIN pg_class tbl ON tbl.oid=pg_index.indrelid
            JOIN pg_namespace ON pg_namespace.oid = idx.relnamespace
          WHERE pg_index.indisvalid AND tbl.relkind = 'r' AND idx.relpages > 0
        ) AS i ON a.attrelid = i.indexrelid
        JOIN pg_stats AS s ON s.schemaname = i.nspname
          AND ((s.tablename = i.tblname AND s.attname = pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE)) 
          -- stats from tbl
          OR  (s.tablename = i.idxname AND s.attname = a.attname))
          -- stats from functional cols
        JOIN pg_type AS t ON a.atttypid = t.oid
      WHERE a.attnum > 0
      GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9
    ) AS s1
  ) AS s2
    JOIN pg_am am ON s2.relam = am.oid WHERE am.amname = 'btree'
) AS sub
-- WHERE NOT is_na
ORDER BY 2,3,4;
```

#### 查找符合 Autovacuum 操作条件的表
<a name="wait-event.iodatafileread.actions.maintenance.autovacuumed"></a>

要查找符合 Autovacuum 操作条件的表，请运行以下查询。

```
--This query shows tables that need vacuuming and are eligible candidates.
--The following query lists all tables that are due to be processed by autovacuum. 
-- During normal operation, this query should return very little.
WITH  vbt AS (SELECT setting AS autovacuum_vacuum_threshold 
              FROM pg_settings WHERE name = 'autovacuum_vacuum_threshold')
    , vsf AS (SELECT setting AS autovacuum_vacuum_scale_factor 
              FROM pg_settings WHERE name = 'autovacuum_vacuum_scale_factor')
    , fma AS (SELECT setting AS autovacuum_freeze_max_age 
              FROM pg_settings WHERE name = 'autovacuum_freeze_max_age')
    , sto AS (SELECT opt_oid, split_part(setting, '=', 1) as param, 
                split_part(setting, '=', 2) as value 
              FROM (SELECT oid opt_oid, unnest(reloptions) setting FROM pg_class) opt)
SELECT
    '"'||ns.nspname||'"."'||c.relname||'"' as relation
    , pg_size_pretty(pg_table_size(c.oid)) as table_size
    , age(relfrozenxid) as xid_age
    , coalesce(cfma.value::float, autovacuum_freeze_max_age::float) autovacuum_freeze_max_age
    , (coalesce(cvbt.value::float, autovacuum_vacuum_threshold::float) + 
         coalesce(cvsf.value::float,autovacuum_vacuum_scale_factor::float) * c.reltuples) 
         as autovacuum_vacuum_tuples
    , n_dead_tup as dead_tuples
FROM pg_class c 
JOIN pg_namespace ns ON ns.oid = c.relnamespace
JOIN pg_stat_all_tables stat ON stat.relid = c.oid
JOIN vbt on (1=1) 
JOIN vsf ON (1=1) 
JOIN fma on (1=1)
LEFT JOIN sto cvbt ON cvbt.param = 'autovacuum_vacuum_threshold' AND c.oid = cvbt.opt_oid
LEFT JOIN sto cvsf ON cvsf.param = 'autovacuum_vacuum_scale_factor' AND c.oid = cvsf.opt_oid
LEFT JOIN sto cfma ON cfma.param = 'autovacuum_freeze_max_age' AND c.oid = cfma.opt_oid
WHERE c.relkind = 'r' 
AND nspname <> 'pg_catalog'
AND (
    age(relfrozenxid) >= coalesce(cfma.value::float, autovacuum_freeze_max_age::float)
    or
    coalesce(cvbt.value::float, autovacuum_vacuum_threshold::float) + 
      coalesce(cvsf.value::float,autovacuum_vacuum_scale_factor::float) * c.reltuples <= n_dead_tup
    -- or 1 = 1
)
ORDER BY age(relfrozenxid) DESC;
```

### 响应大量连接
<a name="wait-event.iodatafileread.actions.connections"></a>

当您监控 Amazon CloudWatch 时，您可能会发现 `DatabaseConnections` 指标激增。这种增加表示与数据库的连接数量有所增加。我们建议采取以下方法：
+ 限制应用程序可与每个实例一起打开的连接数。如果您的应用程序具有嵌入式连接池功能，请设置合理数量的连接。根据实例中的 vCPU 可以有效并行处理的数量来确定数量。

  如果您的应用程序没有使用连接池功能，请考虑使用 Amazon RDS 代理或替代方案。这种方法允许您的应用程序打开与负载均衡器的多个连接。然后，均衡器可以打开与数据库的数量有限的连接。由于并行运行的连接减少，您的数据库实例在内核中执行的上下文切换会减少。查询的进度应该更快，从而导致等待事件减少。有关更多信息，请参阅 [Amazon RDS 代理](rds-proxy.md)。
+ 尽可能利用 RDS for PostgreSQL 的只读副本。当您的应用程序运行只读操作时，将这些请求发送到只读副本。此方法可减少主（写入器）节点上的输入/输出压力。
+ 请考虑纵向扩展数据库实例。更高容量的实例类可提供更多内存，这为 RDS for PostgreSQL 提供了一个更大的共享缓冲池来容纳页面。较大的大小还为数据库实例提供了更多的 vCPU 来处理连接。当生成 `IO:DataFileRead` 等待事件的操作为写入时，更多的 vCPU 会特别有用。

# IO:WALWrite
<a name="wait-event.iowalwrite"></a>



**Topics**
+ [支持的引擎版本](#wait-event.iowalwrite.context.supported)
+ [上下文](#wait-event.iowalwrite.context)
+ [等待次数增加的可能原因](#wait-event.iowalwrite.causes)
+ [操作](#wait-event.iowalwrite.actions)

## 支持的引擎版本
<a name="wait-event.iowalwrite.context.supported"></a>

RDS for PostgreSQL 10 及更高版本的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.iowalwrite.context"></a>

数据库中生成预写日志数据的活动首先填满 WAL 缓冲区，然后异步写入磁盘。当 SQL 会话等待 WAL 数据完成向磁盘写入以便它可以释放事务的 COMMIT 调用时，会生成等待事件 `IO:WALWrite`。

## 等待次数增加的可能原因
<a name="wait-event.iowalwrite.causes"></a>

如果这种等待事件经常发生，则应查看您的工作负载以及工作负载执行的更新类型及其频率。特别要查找以下类型的活动。

**繁重的 DML 活动**  
更改数据库表中的数据不会立即发生。对一个表的插入可能需要等待从另一客户端对同一个表进行插入或更新。用于更改数据值的数据操作语言（DML）语句（INSERT、UPDATE、DELETE、COMMIT、ROLLBACK TRANSATION）可能会导致争用，从而导致预写日志文件等待刷新缓冲区。以下 Amazon RDS 性能详情指标反映了这种情况，这些指标表明 DML 活动非常多。  
+  `tup_inserted`
+ `tup_updated`
+ `tup_deleted`
+ `xact_rollback`
+ `xact_commit`
有关这些指标的更多信息，请参阅 [适用于 Amazon RDS for PostgreSQL 的性能详情计数器](USER_PerfInsights_Counters.md#USER_PerfInsights_Counters.PostgreSQL)。

**频繁的检查点活动**  
过多的检查点会导致更多数量的 WAL 文件。在 RDS for PostgreSQL 中，整页写入始终处于“开启”状态。整页写入有助于防止数据丢失。但是，当检查点活动过于频繁时，系统可能会遇到整体性能问题。在 DML 活动非常多的系统上尤其如此。在某些情况下，您可能会在 `postgresql.log` 中发现错误消息，指出“检查点出现频率过高”。  
我们建议您在优化检查点时，谨慎平衡性能与出现异常关闭时进行恢复所需的预期时间。

## 操作
<a name="wait-event.iowalwrite.actions"></a>

我们建议通过执行以下操作来减少此等待事件的数量。

**Topics**
+ [减少提交的数量](#wait-event.iowalwrite.actions.problem)
+ [监控您的检查点](#wait-event.iowalwrite.actions.monitor)
+ [纵向扩展 IO](#wait-event.iowalwrite.actions.scale-io)
+ [专用日志卷（DLV）](#wait-event.iowalwrite.actions.dlv)

### 减少提交的数量
<a name="wait-event.iowalwrite.actions.problem"></a>

为了减少提交的数量，您可以将语句合并到事务数据块中。使用 Amazon RDS 性能详情来检查正在运行的查询类型。您也可以将大型维护操作移至非高峰时段。例如，在非生产时间创建索引或使用 `pg_repack` 操作。

### 监控您的检查点
<a name="wait-event.iowalwrite.actions.monitor"></a>

您可以监控两个参数，以查看 RDS for PostgreSQL 数据库实例对于检查点写入 WAL 文件的频率。
+ `log_checkpoints` –原定设置情况下，该参数为“on”。它会导致对于每个检查点向 PostgreSQL 日志发送一条消息。这些日志消息包括写入的缓冲区数量、写入缓冲区所花的时间以及针对给定检查点添加、删除或回收的 WAL 文件数。

  有关此参数的更多信息，请参阅 PostgreSQL 文档中的[错误报告和日志记录](https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-CHECKPOINTS)。
+ `checkpoint_warning` – 此参数为检查点频率设置阈值（以秒为单位），超过该阈值将生成警告。原定设置情况下，不在 RDS for PostgreSQL 中设置此参数。您可以设置此参数的值，以便在 RDS for PostgreSQL 数据库实例中的数据库更改以 WAL 文件大小无法处理的速率写入时收到警告。例如，假设您将参数设置为 30。如果您的 RDS for PostgreSQL 实例需要写入更改的频率超过每 30 秒，则会向 PostgreSQL 日志发送“检查点出现频率过高”的警告。这可能表明应增加您的 `max_wal_size` 值。

  有关更多信息，请参阅 PostgreSQL 文档中的[预写日志](https://www.postgresql.org/docs/current/runtime-config-wal.html#RUNTIME-CONFIG-WAL-CHECKPOINTS)。

### 纵向扩展 IO
<a name="wait-event.iowalwrite.actions.scale-io"></a>

这种类型的输入/输出（IO）等待事件可以通过扩展每秒进行读写操作的次数（IOPS）以提供更快的 IO 来进行修复。扩展 IO 比扩展 CPU 更可取，因为扩展 CPU 会导致更多的 IO 争用，原因在于增加的 CPU 可以处理更多的工作，从而使 IO 瓶颈进一步恶化。一般情况下，建议在执行扩展操作之前优化您的工作负载。

### 专用日志卷（DLV）
<a name="wait-event.iowalwrite.actions.dlv"></a>

您可以利用 Amazon RDS 控制台、AWS CLI 或 Amazon RDS API，将专用日志卷（DLV）用于使用预调配 IOPS（PIOPS）存储的数据库实例。DLV 将 PostgreSQL 数据库事务日志移动到与包含数据库表的卷不同的存储卷中。有关更多信息，请参阅 [专用日志卷（DLV）](CHAP_Storage.md#CHAP_Storage.dlv)。

# IPC:并行等待事件
<a name="rpg-ipc-parallel"></a>

以下 `IPC:parallel wait events` 表明会话正在等待与并行查询执行操作相关的进程间通信。
+ `IPC:BgWorkerStartup`：进程正在等待并行工作进程完成其启动序列。在初始化工作线程以执行并行查询时会发生这种情况。
+ `IPC:BgWorkerShutdown`：进程正在等待并行工作进程完成其关闭序列。这发生在并行查询执行的清理阶段。
+ `IPC:ExecuteGather`：在查询执行期间，进程正在等待从并行工作进程接收数据。当领导进程需要从其工作线程中收集结果时，会发生这种情况。
+ `IPC:ParallelFinish`：进程正在等待并行工作线程完成其执行并报告其最终结果。这种情况发生在并行查询执行的完成阶段。

**Topics**
+ [支持的引擎版本](#rpg-ipc-parallel-context-supported)
+ [上下文](#rpg-ipc-parallel-context)
+ [等待次数增加的可能原因](#rpg-ipc-parallel-causes)
+ [操作](#rpg-ipc-parallel-actions)

## 支持的引擎版本
<a name="rpg-ipc-parallel-context-supported"></a>

Aurora PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="rpg-ipc-parallel-context"></a>

PostgreSQL 中的并行查询执行涉及多个进程协同工作以处理单个查询。当确定查询适合并行化时，领导进程会根据 `max_parallel_workers_per_gather` 参数设置与一个或多个并行工作进程进行协调。领导进程在各工作线程之间划分工作，每个工作线程独立处理自己的那部分数据，然后将结果收集回领导进程。

**注意**  
每个并行工作线程均作为一个单独的进程运行，其资源需求类似于完整的用户会话。这意味着，与非并行查询相比，具有 4 个工作线程的并行查询消耗的资源（CPU、内存、I/O 带宽）最多可达 5 倍，因为领导进程和每个工作进程都保持自己的资源分配。例如，诸如 `work_mem` 的设置单独应用于每个工作线程，可能会使所有进程的总内存使用量成倍增加。

并行查询架构由三个主要部分组成：
+ 领导进程：启动并行操作、划分工作负载并与工作进程进行协调的主进程。
+ 工作进程：并行执行查询各部分的后台进程。
+ 收集/收集合并：将来自多个工作进程的结果合并回领导进程的操作

在并行执行期间，进程需要通过进程间通信（IPC）机制相互通信。这些 IPC 等待事件发生在不同的阶段：
+ 工作线程启动：初始化并行工作线程时
+ 数据交换：当工作线程处理数据并将结果发送给领导进程时
+ 工作线程关闭：当并行执行完成且终止工作线程时
+ 同步点：当进程需要协调或等待其它进程完成其任务时

了解这些等待事件对于诊断与并行查询执行相关的性能问题至关重要，尤其是在可能同时执行多个并行查询的高并发环境中。

## 等待次数增加的可能原因
<a name="rpg-ipc-parallel-causes"></a>

有几个因素可能导致与并行相关的 IPC 等待事件增加：

**并行查询的高并发性**  
当多个并行查询同时运行时，可能会导致资源争用和 IPC 操作的等待时间增加。这在事务量或分析工作负载较高的系统中尤其常见。

**并行查询计划不够理想**  
如果查询计划器选择效率低下的并行计划，则可能会导致不必要的并行化或工作线程间的工作分配不佳。这可能会导致 IPC 等待增加，尤其是对于 `IPC:ExecuteGather` 和 `IPC:ParallelFinish` 事件。这些规划问题通常源于过时的统计数据和表/索引膨胀。

**并行工作线程频繁启动和关闭**  
频繁启动和终止并行工作线程的短期查询可能会导致 `IPC:BgWorkerStartup` 和 `IPC:BgWorkerShutdown` 事件增加。这种情况经常出现在具有许多小型可并行查询的 OLTP 工作负载中。

**资源约束**  
有限的 CPU、内存或 I/O 容量可能会导致并行执行出现瓶颈，从而导致所有 IPC 事件的等待时间增加。例如，如果 CPU 饱和，则工作进程可能需要更长的时间才能启动或处理自己的那部分工作。

**复杂的查询结构**  
具有多个并行性级别的查询（例如，并行联接后跟并行聚合）可能会导致更复杂的 IPC 模式并可能增加等待时间，尤其是对于 `IPC:ExecuteGather` 事件。

**大型结果集**  
生成大型结果集的查询可能会导致 `IPC:ExecuteGather` 等待时间增加，因为领导进程将花费更多的时间来收集和处理来自工作进程的结果。

了解这些因素有助于诊断和解决与 Aurora PostgreSQL 中的并行查询执行相关的性能问题。

## 操作
<a name="rpg-ipc-parallel-actions"></a>

当您看到与并行查询相关的等待时，这通常表示后端进程正在协调或等待并行工作进程。在执行并行计划期间，这些等待很常见。您可以通过监控并行工作线程使用情况、查看参数设置以及调整查询执行和资源分配，来调查并缓解这些等待的影响。

**Topics**
+ [分析查询计划是否存在并行性效率低下问题](#rpg-ipc-parallel-analyze-plans)
+ [监控并行查询使用情况](#rpg-ipc-parallel-monitor)
+ [查看和调整并行查询设置](#rpg-ipc-parallel-adjust-settings)
+ [优化资源分配](#rpg-ipc-parallel-optimize-resources)
+ [调查连接管理](#rpg-ipc-parallel-connection-management)
+ [查看和优化维护操作](#rpg-ipc-parallel-maintenance)

### 分析查询计划是否存在并行性效率低下问题
<a name="rpg-ipc-parallel-analyze-plans"></a>

并行查询执行可能经常导致系统不稳定、CPU 峰值和不可预测的查询性能变化。彻底分析并行性是否确实可以改善特定工作负载至关重要。使用 EXPLAIN ANALYZE 查看并行查询执行计划。

在会话级别暂时禁用并行性以比较计划效率：

```
SET max_parallel_workers_per_gather = 0;
EXPLAIN ANALYZE <your_query>;
```

重新启用并行性并进行比较：

```
RESET max_parallel_workers_per_gather;
EXPLAIN ANALYZE <your_query>;
```

如果禁用并行性会产生更好或更一致的结果，则考虑使用 SET 命令在会话级别为特定查询禁用并行性。为了获得更广泛的影响，您可能需要通过调整数据库参数组中的相关参数，来在实例级别禁用并行性。有关更多信息，请参阅 [在 Amazon RDS 中修改数据库参数组中的参数](USER_WorkingWithParamGroups.Modifying.md)。

### 监控并行查询使用情况
<a name="rpg-ipc-parallel-monitor"></a>

使用以下查询来深入了解并行查询活动和容量：

检查活动的并行工作进程：

```
SELECT
    COUNT(*)
FROM
    pg_stat_activity
WHERE
    backend_type = 'parallel worker';
```

此查询显示活动的并行工作进程的数量。较高的值可能表示为“max\$1parallel\$1workers”配置了较高的值，您可能需要考虑将其降低。

检查并发并行查询：

```
SELECT
    COUNT(DISTINCT leader_pid)
FROM
    pg_stat_activity
WHERE
    leader_pid IS NOT NULL;
```

此查询返回已启动并行查询的不同领导进程的数量。此处的数字较高表示多个会话正在并发运行并行查询，这可能会增加对 CPU 和内存的需求。

### 查看和调整并行查询设置
<a name="rpg-ipc-parallel-adjust-settings"></a>

查看以下参数以确保它们与您的工作负载一致：
+ `max_parallel_workers`：所有会话中并行工作线程的总数。
+ `max_parallel_workers_per_gather`：每个查询的最大工作线程数。

对于 OLAP 工作负载，增加这些值可以提高性能。对于 OLTP 工作负载，通常首选较低的值。

```
SHOW max_parallel_workers;
SHOW max_parallel_workers_per_gather;
```

### 优化资源分配
<a name="rpg-ipc-parallel-optimize-resources"></a>

监控 CPU 利用率，如果 vCPU 的数量一直很高，并且应用程序从并行查询中受益，请考虑调整 vCPU 的数量。确保有足够的内存可用于并行操作。
+ 使用性能详情指标来确定系统是否受到 CPU 限制。
+ 每个并行工作线程都使用自己的 `work_mem`。确保总内存使用量在实例限制范围内。

并行查询可能比非并行查询消耗的资源要多得多，因为每个工作进程都是一个完全独立的进程，它对系统的影响与额外的用户会话大致相同。在为此设置选择值时，以及在配置其它用于控制资源利用率的设置（例如 `work_mem`）时，应考虑到这一点。有关更多信息，请参阅 [PostgreSQL 文档](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-WORK-MEM)。资源限制（例如 `work_mem`）单独应用于每个工作线程，这意味着跨所有进程的总利用率可能比针对任何单个进程的正常利用率高得多。

如果工作负载高度并行化，可以考虑增加 vCPU 数量或调整内存参数。

### 调查连接管理
<a name="rpg-ipc-parallel-connection-management"></a>

如果遇到连接耗尽的情况，请查看应用程序连接池策略。考虑在应用程序级别实现连接池（如果尚未使用）。

### 查看和优化维护操作
<a name="rpg-ipc-parallel-maintenance"></a>

协调索引创建和其它维护任务，以防止资源争用。考虑将这些操作安排在非高峰时段。避免在用户查询负载较高的时期安排繁重的维护工作（例如并行索引构建）。这些操作可能会消耗并行工作线程并影响常规查询的性能。

# IPC:ProcArrayGroupUpdate
<a name="apg-rpg-ipcprocarraygroup"></a>

当会话正等待组领导在操作结束时更新事务状态时，会发生 `IPC:ProcArrayGroupUpdate` 事件。虽然 PostgreSQL 通常将 IPC 类型的等待事件与并行查询操作相关联，但这个特定等待事件并非并行查询所特有。

**Topics**
+ [支持的引擎版本](#apg-rpg-ipcprocarraygroup.supported)
+ [上下文](#apg-rpg-ipcprocarraygroup.context)
+ [等待次数增加的可能原因](#apg-rpg-ipcprocarraygroup.causes)
+ [操作](#apg-rpg-ipcprocarraygroup.actions)

## 支持的引擎版本
<a name="apg-rpg-ipcprocarraygroup.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="apg-rpg-ipcprocarraygroup.context"></a>

**了解进程数组** – 进程（proc）数组是 PostgreSQL 中的共享内存结构。它包含有关所有正在运行的进程的信息，包括事务详细信息。在事务完成（`COMMIT` 或 `ROLLBACK`）期间，需要更新 ProcArray 以反映更改并从数组中清除 transactionID。尝试完成事务的会话必须在 ProcArray 上获得独占锁。这可以防止其他进程获得其共享或独占锁。

**组更新机制** – 在执行 COMMIT 或 ROLLBACK 时，如果后端进程无法在独占模式下获取 ProcArrayLock，它会更新一个名为 ProcArrayGroupMember 的特殊字段。这会将事务添加到打算结束的会话列表中。然后，此后端进程进入休眠状态，其休眠时间被记录为 ProcArrayGroupUpdate 等待事件。ProcArray 中具有 procArrayGroupMember 的第一个进程，称为领导进程，以独占模式获取 ProcArrayLock。然后，它会清除等待群组 transactionID 清除的进程列表。完成后，领导进程会释放 ProcArrayLock，然后唤醒此列表中的所有进程，通知它们其事务已完成。

## 等待次数增加的可能原因
<a name="apg-rpg-ipcprocarraygroup.causes"></a>

正在运行的进程越多，领导进程在独占模式下保持 procArrayLock 的时间就越长。因此，更多的写入事务会进入组更新场景，导致可能出现大量进程在 `ProcArrayGroupUpdate` 等待事件上排队等待。在数据库洞察的 Top SQL 视图中，您会看到 COMMIT 是该等待事件占比最多的语句。这是预期行为，但需要对正在运行的特定写入 SQL 进行更深入的调查，以确定要采取的适当措施。

## 操作
<a name="apg-rpg-ipcprocarraygroup.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。可以通过使用 Amazon RDS 性能详情或查询 PostgreSQL 系统视图 `pg_stat_activity` 来识别 `IPC:ProcArrayGroupUpdate` 事件。

**Topics**
+ [监控事务提交和回滚操作](#apg-rpg-ipcprocarraygroup.actions.monitor)
+ [减少并发性](#apg-rpg-ipcprocarraygroup.actions.concurrency)
+ [实施连接池](#apg-rpg-ipcprocarraygroup.actions.pooling)
+ [使用速度更快的存储](#apg-rpg-ipcprocarraygroup.actions.storage)

### 监控事务提交和回滚操作
<a name="apg-rpg-ipcprocarraygroup.actions.monitor"></a>

**监控提交和回滚** – 提交和回滚数量的增加可能会导致 ProcArray 承受的压力增加。例如，如果 SQL 语句由于重复键冲突增多而开始失败，您可能会看到回滚次数增加，这可能会增加 ProcArray 争用和表膨胀。

Amazon RDS 数据库洞察提供 PostgreSQL 指标 `xact_commit` 和 `xact_rollback` 以报告每秒的提交和回滚次数。

### 减少并发性
<a name="apg-rpg-ipcprocarraygroup.actions.concurrency"></a>

**批处理事务** – 尽可能在单个事务中进行批量操作，以减少提交/回滚操作。

**限制并发性** - 减少并发活跃事务的数量，以缓解 ProcArray 上的锁争用。虽然需要进行一些测试，但减少并发连接总数可以减少争用并维持吞吐量。

### 实施连接池
<a name="apg-rpg-ipcprocarraygroup.actions.pooling"></a>

**连接池解决方案** - 使用连接池来有效地管理数据库连接，从而减少后端的总数，进而减少 ProcArray 上的工作负载。虽然需要进行一些测试，但减少并发连接总数可以减少争用并维持吞吐量。

**减少连接风暴** – 同样，频繁创建和终止连接的模式会给 ProcArray 带来额外的压力。通过减少这种模式，可以减少总体争用。

### 使用速度更快的存储
<a name="apg-rpg-ipcprocarraygroup.actions.storage"></a>

**专用日志卷** – 如果 `IPC:ProcArrayGroupUpdate` 等待事件伴随着很多 `IO:WALWrite` 等待事件，则设置专用日志卷可以减少写入 WAL 的瓶颈。反过来，这会提高提交的性能。

有关更多信息，请参阅[专用日志卷](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PIOPS.dlv.html)。

# Lock:advisory
<a name="wait-event.lockadvisory"></a>

当 PostgreSQL 应用程序使用锁定来协调多个会话之间的活动时，会发生 `Lock:advisory` 事件。

**Topics**
+ [相关引擎版本](#wait-event.lockadvisory.context.supported)
+ [上下文](#wait-event.lockadvisory.context)
+ [原因](#wait-event.lockadvisory.causes)
+ [操作](#wait-event.lockadvisory.actions)

## 相关引擎版本
<a name="wait-event.lockadvisory.context.supported"></a>

此等待事件信息与 RDS for PostgreSQL 版本 9.6 及更高版本相关。

## 上下文
<a name="wait-event.lockadvisory.context"></a>

PostgreSQL 咨询锁是由用户的应用程序代码显式锁定和解锁的应用程序级别的合作锁。应用程序可以使用 PostgreSQL 咨询锁来协调多个会话之间的活动时。与常规锁、对象级锁或行级锁不同的是，应用程序对锁的生命周期拥有完全控制权。有关更多信息，请参阅 PostgreSQL 文档中的[咨询锁](https://www.postgresql.org/docs/12/explicit-locking.html#ADVISORY-LOCKS)。

咨询锁可以在事务结束之前释放，也可以在事务中由会话持有。对于隐式的、系统强制的锁，例如 `CREATE INDEX` 语句获取的对表的互斥访问锁，情况并非如此。

有关用于获取（锁定）和释放（解锁）咨询锁的函数的说明，请参阅 PostgreSQL 文档中的[咨询锁函数](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS)。

咨询锁是在常规的 PostgreSQL 锁定系统之上实施的，并且在 `pg_locks` 系统视图中可见。

## 原因
<a name="wait-event.lockadvisory.causes"></a>

这种锁类型由显式使用它的应用程序完全控制。作为查询的一部分为每个行获取的咨询锁可能会导致锁激增或长期积累。

当以获得比查询返回的行更多的锁的方式运行查询时，会发生这些影响。应用程序最终必须释放每个锁，但是如果在未返回的行上获取锁，则应用程序无法找到所有锁。

以下示例来自 PostgreSQL 文档中的[咨询锁](https://www.postgresql.org/docs/12/explicit-locking.html#ADVISORY-LOCKS)。

```
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100;
```

在此示例中，`LIMIT` 子句只能在内部选择行并锁定其 ID 值后停止查询的输出。当数据量不断增长导致计划人员选择在开发过程中未测试的其他执行计划时，可能会突然发生这种情况。在这种情况下，由于应用程序会为锁定的每个 ID 值调用 `pg_advisory_unlock`，会发生累积。但是，在这种情况下，它找不到在未返回的行上获取的锁集合。由于锁是在会话级别获取的，因此它们不会在事务结束时自动释放。

阻止锁定尝试激增的另一个可能原因是意外的冲突。在这些冲突中，应用程序的不相关部分错误地共享了相同的锁 ID 空间。

## 操作
<a name="wait-event.lockadvisory.actions"></a>

查看应用程序对咨询锁的使用情况，并详细说明在应用程序流中获取和释放每种类型的咨询锁的位置和时间。

确定会话是获取太多锁定还是长时间运行的会话没有尽早释放锁，从而导致锁缓慢累积。您可以通过使用 `pg_terminate_backend(pid)` 结束会话来纠正会话级别锁定的缓慢累积。

正在等待咨询锁的客户端显示在带有 `wait_event_type=Lock` 和 `wait_event=advisory` 的 `pg_stat_activity` 中。您可以通过查询相同 `pid` 的 `pg_locks` 系统视图来获取特定的锁定值，以寻找 `locktype=advisory` 和 `granted=f`。

然后您可以通过查询具有 `granted=t` 的相同咨询锁的 `pg_locks` 来识别阻止的会话，如以下示例所示。

```
SELECT blocked_locks.pid AS blocked_pid,
         blocking_locks.pid AS blocking_pid,
         blocked_activity.usename AS blocked_user,
         blocking_activity.usename AS blocking_user,
         now() - blocked_activity.xact_start AS blocked_transaction_duration,
         now() - blocking_activity.xact_start AS blocking_transaction_duration,
         concat(blocked_activity.wait_event_type,':',blocked_activity.wait_event) AS blocked_wait_event,
         concat(blocking_activity.wait_event_type,':',blocking_activity.wait_event) AS blocking_wait_event,
         blocked_activity.state AS blocked_state,
         blocking_activity.state AS blocking_state,
         blocked_locks.locktype AS blocked_locktype,
         blocking_locks.locktype AS blocking_locktype,
         blocked_activity.query AS blocked_statement,
         blocking_activity.query AS blocking_statement
    FROM pg_catalog.pg_locks blocked_locks
    JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
    JOIN pg_catalog.pg_locks blocking_locks
        ON blocking_locks.locktype = blocked_locks.locktype
        AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
        AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
        AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
        AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
        AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
        AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
        AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
        AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
        AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
        AND blocking_locks.pid != blocked_locks.pid
    JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
    WHERE NOT blocked_locks.GRANTED;
```

所有的咨询锁 API 函数都有两组参数，一个 `bigint` 参数或两个 `integer` 参数：
+ 对于具有一个 `bigint` 参数的 API 函数，上面的 32 位在 `pg_locks.classid` 中，下面的 32 位在 `pg_locks.objid` 中。
+ 对于具有两个 `integer` 参数的 API 函数，第一个参数是 `pg_locks.classid`，第二个参数是 `pg_locks.objid`。

`pg_locks.objsubid` 值表示使用了哪个 API 表单：`1` 表示一个 `bigint` 参数；`2` 表示两个 `integer` 参数。

# Lock:extend
<a name="wait-event.lockextend"></a>

当后端进程正在等待锁定关系以对其进行扩展，而另一个进程出于同样目的锁定该关系时，会发生 `Lock:extend` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.lockextend.context.supported)
+ [上下文](#wait-event.lockextend.context)
+ [等待次数增加的可能原因](#wait-event.lockextend.causes)
+ [操作](#wait-event.lockextend.actions)

## 支持的引擎版本
<a name="wait-event.lockextend.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.lockextend.context"></a>

事件 `Lock:extend` 表示后端进程正在等待扩展另一个后端进程在扩展该关系时保持锁定的关系。由于每次只有一个进程可以扩展关系，因此系统会生成 `Lock:extend` 等待事件。`INSERT`、`COPY` 和 `UPDATE` 操作可以生成此事件。

## 等待次数增加的可能原因
<a name="wait-event.lockextend.causes"></a>

当 `Lock:extend` 事件的发生率超过正常（可能表示性能问题）时，典型原因包括以下几点：

**对同一表的并发插入或更新激增 **  
插入或更新同一表的查询的并发会话数可能会增加。

**网络带宽不足**  
数据库实例上的网络带宽可能不足以满足当前工作负载的存储通信需求。这可能会导致存储延迟，从而导致 `Lock:extend` 事件增加。

## 操作
<a name="wait-event.lockextend.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [减少同一关系的并发插入和更新](#wait-event.lockextend.actions.action1)
+ [提高网络带宽](#wait-event.lockextend.actions.increase-network-bandwidth)

### 减少同一关系的并发插入和更新
<a name="wait-event.lockextend.actions.action1"></a>

首先，确定 `tup_inserted` 和 `tup_updated` 指标是否有增加，以及此等待事件是否伴随增加。如果是这样，请检查哪些关系在插入和更新操作中处于高争用状态。要确定这一点，请查询 `pg_stat_all_tables` 视图，以了解 `n_tup_ins` 和 `n_tup_upd` 字段中的值。有关 `pg_stat_all_tables` 视图的信息，请参阅 PostgreSQL 文档中的 [pg\$1stat\$1all\$1tables](https://www.postgresql.org/docs/13/monitoring-stats.html#MONITORING-PG-STAT-ALL-TABLES-VIEW)。

要获取有关正在阻止和已阻止的查询的更多信息，请如以下示例所示查询 `pg_stat_activity`：

```
SELECT
    blocked.pid,
    blocked.usename,
    blocked.query,
    blocking.pid AS blocking_id,
    blocking.query AS blocking_query,
    blocking.wait_event AS blocking_wait_event,
    blocking.wait_event_type AS blocking_wait_event_type
FROM pg_stat_activity AS blocked
JOIN pg_stat_activity AS blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
where
blocked.wait_event = 'extend'
and blocked.wait_event_type = 'Lock';
 
   pid  | usename  |            query             | blocking_id |                         blocking_query                           | blocking_wait_event | blocking_wait_event_type
  ------+----------+------------------------------+-------------+------------------------------------------------------------------+---------------------+--------------------------
   7143 |  myuser  | insert into tab1 values (1); |        4600 | INSERT INTO tab1 (a) SELECT s FROM generate_series(1,1000000) s; | DataFileExtend      | IO
```

在您确定有助于增加 `Lock:extend` 事件的关系后，请使用以下方法来减少争用：
+ 查明是否可以使用分区来减少同一个表的争用。将插入或更新的元组分成不同的分区可以减少争用。有关分区的信息，请参阅 [使用 pg\$1partman 扩展管理 PostgreSQL 分区](PostgreSQL_Partitions.md)。
+ 如果等待事件主要是由于更新活动造成的，请考虑减少关系的 fillfactor 值。这可以减少更新期间对新数据块的请求。fillfactor 是表的存储参数，用于确定打包表页面的最大空间量。它表示为页面总空间的百分比。有关 fillfactor 参数的更多信息，请参阅 PostgreSQL 文档中的 [CREATE TABLE](https://www.postgresql.org/docs/13/sql-createtable.html)。
**重要**  
我们强烈建议您在更改 fillfactor 时测试系统，因为更改此值可能会对性能产生负面影响，这具体取决于您的工作负载。

### 提高网络带宽
<a name="wait-event.lockextend.actions.increase-network-bandwidth"></a>

要查看写入延迟是否增加，请检查 CloudWatch 中的 `WriteLatency` 指标。如果有，请使用 `WriteThroughput` 和 `ReadThroughput` Amazon CloudWatch 指标监控数据库实例上与存储相关的流量。这些指标可以帮助您确定网络带宽是否足以满足您的工作负载的存储活动。

如果您的网络带宽不够，请增加它。如果您的数据库实例已达到网络带宽限制，增加带宽的唯一方法是增加数据库实例大小。

有关 CloudWatch 指标的更多信息，请参阅[Amazon RDS 的 Amazon CloudWatch 实例级指标](rds-metrics.md#rds-cw-metrics-instance)。有关每个数据库实例类的网络性能的信息，请参阅 [Amazon RDS 的 Amazon CloudWatch 实例级指标](rds-metrics.md#rds-cw-metrics-instance)。

# Lock:Relation
<a name="wait-event.lockrelation"></a>

当查询等待获取当前被另一个事务锁定的表或视图（关系）上的锁定时，会发生 `Lock:Relation` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.lockrelation.context.supported)
+ [上下文](#wait-event.lockrelation.context)
+ [等待次数增加的可能原因](#wait-event.lockrelation.causes)
+ [操作](#wait-event.lockrelation.actions)

## 支持的引擎版本
<a name="wait-event.lockrelation.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.lockrelation.context"></a>

大多数 PostgreSQL 命令隐式使用锁来控制对表中数据的并发访问。您还可以通过 `LOCK` 命令在应用程序代码中显式使用这些锁。许多锁定模式彼此不兼容，它们可以在尝试访问同一对象时阻止事务。发生这种情况时，RDS for PostgreSQL 会生成一个 `Lock:Relation` 事件。以下是一些常见的示例：
+ `ACCESS EXCLUSIVE` 之类的独占锁可以阻止所有并发访问。数据定义语言 (DDL) 操作（例如 `DROP TABLE`、`TRUNCATE`、`VACUUM FULL` 和 `CLUSTER`）隐式获取 `ACCESS EXCLUSIVE` 锁定。`ACCESS EXCLUSIVE` 也是用于未显式指定模式的 `LOCK TABLE` 语句的原定设置锁定模式。
+ 在表上使用 `CREATE INDEX (without CONCURRENT)` 与数据操作语言 (DML) 语句 `UPDATE`、`DELETE` 和 `INSERT` 有冲突，这些语句可获取 `ROW EXCLUSIVE` 锁定。

有关表级锁和冲突锁模式的更多信息，请参阅 PostgreSQL 文档中的[显式锁定](https://www.postgresql.org/docs/13/explicit-locking.html)。

阻止查询和事务通常通过以下方式之一解锁阻止：
+ 阻止查询 – 应用程序可以取消查询或者用户可以结束该过程。引擎还可以因会话的语句超时或死锁检测机制而强制结束查询。
+ 阻止事务 – 事务在运行 `ROLLBACK` 或 `COMMIT` 语句时停止阻止。当会话被客户端或网络问题断开连接或结束时，也会自动发生回滚。当数据库引擎关闭、系统内存不足等时，可以结束会话。

## 等待次数增加的可能原因
<a name="wait-event.lockrelation.causes"></a>

当 `Lock:Relation` 事件的发生频率高于正常值时，可能表明存在性能问题。典型的原因包括：

**增加与表锁冲突的并发会话**  
用冲突锁定模式锁定相同表格的查询的并发会话数可能会增加。

**维护操作**  
`VACUUM` 和 `ANALYZE` 之类的运行状况维护操作可以显著增加冲突锁的数量。`VACUUM FULL` 获取 `ACCESS EXCLUSIVE` 锁，`ANALYSE` 获取 `SHARE UPDATE EXCLUSIVE`锁。这两种类型的锁都可能导致 `Lock:Relation` 等待事件。应用程序数据维护操作（例如刷新具体化视图）也可以增加阻止的查询和事务。

**读取器实例的锁定**  
写入器和读取器持有的关系锁之间可能存在冲突。目前，仅 `ACCESS EXCLUSIVE` 关系锁复制到读取器实例。但是，`ACCESS EXCLUSIVE` 关系锁将与读取器持有的任何 `ACCESS SHARE` 关系锁冲突。这可能会导致读取器上的锁定关系等待事件增加。

## 操作
<a name="wait-event.lockrelation.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [减少阻止 SQL 语句的影响](#wait-event.lockrelation.actions.reduce-blocks)
+ [尽量减少维护操作的影响](#wait-event.lockrelation.actions.maintenance)

### 减少阻止 SQL 语句的影响
<a name="wait-event.lockrelation.actions.reduce-blocks"></a>

为了减少阻止 SQL 语句的影响，请尽可能修改应用程序代码。以下是减少数据块的两种常见方法：
+ 使用 `NOWAIT` 选项 – 一些 SQL 命令，例如 `SELECT` 和 `LOCK` 语句，支持此选项。如果无法立即获取锁定，则 `NOWAIT` 指令将取消请求锁定的查询。这种方法可以帮助防止阻止会话导致其后面堆积阻止的会话。

  例如：假设事务 A 正在等待事务 B 所持有的锁定。现在，如果 B 请求对被事务 C 锁定的表进行锁，那么事务 A 可能会被阻止，直到事务 C 完成。但是，如果事务 B 在请求对 C 进行锁定时使用 `NOWAIT`，它可能会很快失败，并确保事务 A 不必无限期等待。
+ 使用 `SET lock_timeout` – 设置 `lock_timeout` 值，以限制 SQL 语句等待获取关系锁的时间。如果锁未在指定的超时内获取，则请求锁定的事务将被取消。在会话级别设置此值。

### 尽量减少维护操作的影响
<a name="wait-event.lockrelation.actions.maintenance"></a>

维护操作（例如 `VACUUM` 和 `ANALYZE`）非常重要。我们建议您不要将其关闭，因为您会找到与这些维护操作相关的 `Lock:Relation` 等待事件。以下方法可以最大限度地减少这些操作的影响：
+ 在非高峰时段手动运行维护操作。
+ 要减少由 Autovacuum 任务导致的 `Lock:Relation` 等待，执行任何需要的 Autovacuum 优化。有关优化 Autovacuum 的信息，请参阅《*Amazon RDS 用户指南*》中的[在 Amazon RDS 上使用 PostgreSQL Autovacuum](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.Autovacuum.html)。

# Lock:transactionid
<a name="wait-event.locktransactionid"></a>

当事务正在等待行级锁定时，会发生 `Lock:transactionid` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.locktransactionid.context.supported)
+ [上下文](#wait-event.locktransactionid.context)
+ [等待次数增加的可能原因](#wait-event.locktransactionid.causes)
+ [操作](#wait-event.locktransactionid.actions)

## 支持的引擎版本
<a name="wait-event.locktransactionid.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.locktransactionid.context"></a>

当事务试图获取已被授予同时运行的事务的行级锁时，会发生事件 `Lock:transactionid`。显示 `Lock:transactionid` 等待事件的会话因此锁定被阻止。当阻止事务在 `COMMIT` 或 `ROLLBACK` 语句中结束后，被阻止的事务可以继续进行。

RDS for PostgreSQL 的多版本并发控制语义保证读取器不会阻止写入器，写入器也不会阻止读取器。为了发生行级冲突，正在阻止和已阻止的事务必须发出以下类型的冲突语句：
+ `UPDATE`
+ `SELECT … FOR UPDATE`
+ `SELECT … FOR KEY SHARE`

语句 `SELECT … FOR KEY SHARE` 是一种特殊情况。数据库使用子句 `FOR KEY SHARE` 以优化参照完整性的性能。一行上的行级锁定可以组织引用该行的其他表上的 `INSERT`、`UPDATE` 和 `DELETE` 命令。

## 等待次数增加的可能原因
<a name="wait-event.locktransactionid.causes"></a>

当此事件出现频率超过正常时，原因通常是 `UPDATE`、`SELECT … FOR UPDATE` 或 `SELECT … FOR KEY SHARE` 语句结合以下条件。

**Topics**
+ [高并发性](#wait-event.locktransactionid.concurrency)
+ [空闲事务](#wait-event.locktransactionid.idle)
+ [长时间运行的事务](#wait-event.locktransactionid.long-running)

### 高并发性
<a name="wait-event.locktransactionid.concurrency"></a>

RDS for PostgreSQL 可以使用细粒度的行级锁定语义。满足以下条件时，行级冲突的可能性会增加：
+ 高度并发的工作负载争用相同的行。
+ 并发性增加。

### 空闲事务
<a name="wait-event.locktransactionid.idle"></a>

有时，`pg_stat_activity.state` 列会显示值 `idle in transaction`。对于已开始事务但尚未发布 `COMMIT` 或 `ROLLBACK` 的会话，会显示此值。如果 `pg_stat_activity.state` 值不为 `active`，`pg_stat_activity` 中显示的查询是完成运行的最新版本。阻止会话没有主动处理查询，因为未完成的事务持有锁定。

如果空闲事务获得了行级锁，则可能会阻止其他会话获取它。这种情况导致等待事件 `Lock:transactionid` 频繁发生。要诊断问题，请检查来自的 `pg_stat_activity` 和 `pg_locks` 的输出。

### 长时间运行的事务
<a name="wait-event.locktransactionid.long-running"></a>

长时间运行的事务会获得很长一段时间的锁定。这些长期保留的锁定可以阻止其他事务运行。

## 操作
<a name="wait-event.locktransactionid.actions"></a>

行锁定是 `UPDATE`、`SELECT … FOR UPDATE` 或 `SELECT … FOR KEY SHARE` 语句之间发生的冲突。在尝试解决方案之前，请先了解这些语句何时在同一行上运行。使用此信息选择以下各部分所述的策略。

**Topics**
+ [响应高并发](#wait-event.locktransactionid.actions.problem)
+ [回应空闲事务](#wait-event.locktransactionid.actions.find-blocker)
+ [响应长期运行的事务](#wait-event.locktransactionid.actions.concurrency)

### 响应高并发
<a name="wait-event.locktransactionid.actions.problem"></a>

如果并发性是问题，请尝试以下方法之一：
+ 降低应用程序中的并发率。例如，减少活动会话的数量。
+ 实施连接池。要了解如何使用 RDS 代理进行池连接，请参阅 [Amazon RDS 代理](rds-proxy.md)。
+ 设计应用程序或数据模型以避免争用 `UPDATE` 和 `SELECT … FOR UPDATE` 语句。您还可以减少 `SELECT … FOR KEY SHARE` 语句访问的外键的数量。

### 回应空闲事务
<a name="wait-event.locktransactionid.actions.find-blocker"></a>

如果 `pg_stat_activity.state` 显示 `idle in transaction`，请使用以下策略：
+ 尽可能开启自动提交。这种方法可防止事务在等待 `COMMIT` 或 `ROLLBACK` 时阻止其他事务。
+ 搜索缺失 `COMMIT`、`ROLLBACK` 或 `END` 的代码路径。
+ 确保应用程序中的异常处理逻辑始终具有通向有效 `end of transaction` 的路径。
+ 确保您的应用程序在结束与`COMMIT` 或 `ROLLBACK` 的事务后处理查询结果。

### 响应长期运行的事务
<a name="wait-event.locktransactionid.actions.concurrency"></a>

如果长时间运行的事务导致频繁发生 `Lock:transactionid`，请尝试以下策略：
+ 在长时间运行的事务中保持行锁定。
+ 尽可能通过实现自动提交来限制查询的长度。

# Lock:tuple
<a name="wait-event.locktuple"></a>

在后端进程等待获取元组锁定时，会发生 `Lock:tuple` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.locktuple.context.supported)
+ [上下文](#wait-event.locktuple.context)
+ [等待次数增加的可能原因](#wait-event.locktuple.causes)
+ [操作](#wait-event.locktuple.actions)

## 支持的引擎版本
<a name="wait-event.locktuple.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.locktuple.context"></a>

事件 `Lock:tuple` 表示一个后端正在等待获取元组上的锁定，而另一个后端在同一个元组上保持冲突锁定。下表说明了会话生成 `Lock:tuple` 事件的场景。


|  时间  |  会话 1  |  会话 2  |  会话 3  | 
| --- | --- | --- | --- | 
|  t1  |  开始事务。  |    |    | 
|  t2  |  更新第 1 行。  |    |    | 
|  t3  |    |  更新第 1 行。会话获取元组上的独占锁定，然后等待会话 1 通过提交或回滚来释放锁。  |    | 
|  t4  |    |    |  更新第 1 行。会话等待会话 2 才能释放元组上的独占锁定。  | 

或者您可以使用基准测试工具 `pgbench` 来模拟此等待事件。配置大量并发会话以使用自定义 SQL 文件更新表中的同一行。

要了解冲突锁模式的更多信息，请参阅 PostgreSQL 文档中的[显式锁定](https://www.postgresql.org/docs/current/explicit-locking.html)。要了解有关 `pgbench` 的更多信息，请参阅 PostgreSQL 文档中的 [pgbench](https://www.postgresql.org/docs/current/pgbench.html)。

## 等待次数增加的可能原因
<a name="wait-event.locktuple.causes"></a>

当此事件的发生率超过正常（可能表示性能问题）时，典型原因包括以下几点：
+ 大量并发会话试图通过运行 `UPDATE` 或 `DELETE` 语句获取相同元组的冲突锁定。
+ 高度并发的会话正在使用 `FOR UPDATE` 或 `FOR NO KEY UPDATE` 锁定模式运行 `SELECT` 语句。
+ 各种因素促使应用程序或连接池打开更多会话以执行相同的操作。由于新会话正在尝试修改相同的行，数据库负载可能会激增，`Lock:tuple` 可以出现。

有关更多信息，请参阅 PostgreSQL 文档中的[行级锁定](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS)。

## 操作
<a name="wait-event.locktuple.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [调查应用程序逻辑](#wait-event.locktuple.actions.problem)
+ [查找阻止器会话](#wait-event.locktuple.actions.find-blocker)
+ [在并发性高时降低并发性](#wait-event.locktuple.actions.concurrency)
+ [排查瓶颈](#wait-event.locktuple.actions.bottlenecks)

### 调查应用程序逻辑
<a name="wait-event.locktuple.actions.problem"></a>

了解阻止器会话是否已经处于 `idle in transaction` 状态很长一段时间。如果是这样，请考虑结束阻止器会话，作为短期解决方案。您可以使用 `pg_terminate_backend` 函数。有关此函数的更多信息，请参阅 PostgreSQL 文档中的[服务器信号函数](https://www.postgresql.org/docs/13/functions-admin.html#FUNCTIONS-ADMIN-SIGNAL)。

要获得长期解决方案，请执行以下操作：
+ 调整应用程序逻辑。
+ 使用 `idle_in_transaction_session_timeout` 参数。此参数可结束空闲时间超过指定时间的已打开事务的任何会话。有关更多信息，请参阅 PostgreSQL 文档中的[客户端连接原定设置](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT)。
+ 尽可能多地使用自动提交。有关更多信息，请参阅 PostgreSQL 文档中的 [SET AUTOCOMMIT](https://www.postgresql.org/docs/current/ecpg-sql-set-autocommit.html)。

### 查找阻止器会话
<a name="wait-event.locktuple.actions.find-blocker"></a>

在 `Lock:tuple` 等待事件发生时，通过找出哪些锁相互依赖来识别阻止器和已阻止的会话。有关更多信息，请参阅 PostgreSQL wiki 中的[锁定依赖项信息](https://wiki.postgresql.org/wiki/Lock_dependency_information)。

以下示例查询所有会话，并对 `tuple` 进行筛选，通过 `wait_time` 进行排序。

```
SELECT blocked_locks.pid AS blocked_pid,
         blocking_locks.pid AS blocking_pid,
         blocked_activity.usename AS blocked_user,
         blocking_activity.usename AS blocking_user,
         now() - blocked_activity.xact_start AS blocked_transaction_duration,
         now() - blocking_activity.xact_start AS blocking_transaction_duration,
         concat(blocked_activity.wait_event_type,':',blocked_activity.wait_event) AS blocked_wait_event,
         concat(blocking_activity.wait_event_type,':',blocking_activity.wait_event) AS blocking_wait_event,
         blocked_activity.state AS blocked_state,
         blocking_activity.state AS blocking_state,
         blocked_locks.locktype AS blocked_locktype,
         blocking_locks.locktype AS blocking_locktype,
         blocked_activity.query AS blocked_statement,
         blocking_activity.query AS blocking_statement
    FROM pg_catalog.pg_locks blocked_locks
    JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
    JOIN pg_catalog.pg_locks blocking_locks
        ON blocking_locks.locktype = blocked_locks.locktype
        AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
        AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
        AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
        AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
        AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
        AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
        AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
        AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
        AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
        AND blocking_locks.pid != blocked_locks.pid
    JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
    WHERE NOT blocked_locks.GRANTED;
```

### 在并发性高时降低并发性
<a name="wait-event.locktuple.actions.concurrency"></a>

`Lock:tuple` 事件可能会不断发生，特别是在繁忙的工作负载时间。在这种情况下，考虑降低非常繁忙的行的高并发率。通常，只有几个行控制队列或布尔逻辑，这使得这些行非常繁忙。

您可以根据业务需求、应用程序逻辑和工作负载类型使用不同的方法来降低并发性。例如，您可以执行以下操作：
+ 重新设计表和数据逻辑以降低高并发性。
+ 更改应用程序逻辑以降低行级别的高并发性。
+ 使用行级锁定利用和重新设计查询。
+ 使用具有重试操作的 `NOWAIT` 子句。
+ 考虑使用乐观和混合锁定逻辑并发控制。
+ 考虑更改数据库隔离级别。

### 排查瓶颈
<a name="wait-event.locktuple.actions.bottlenecks"></a>

当出现诸如 CPU 匮乏或 Amazon EBS 带宽的最大使用率的瓶颈时，可能会发生 `Lock:tuple`。要减少瓶颈，请考虑以下方法：
+ 纵向扩展您的实例类类型。
+ 优化资源密集型查询。
+ 更改应用程序逻辑。
+ 存档很少访问的数据。

# LWLock:BufferMapping (LWLock:buffer\$1mapping)
<a name="wait-event.lwl-buffer-mapping"></a>

当会话正在等待将数据块与共享缓冲池中的缓冲区关联起来时，会发生此事件。

**注意**  
对于 RDS for PostgreSQL 版本 13 及更高版本，此事件命名为 `LWLock:BufferMapping`。对于 RDS for PostgreSQL 版本 12 及更旧版本，此事件命名为 `LWLock:buffer_mapping`。

**Topics**
+ [支持的引擎版本](#wait-event.lwl-buffer-mapping.context.supported)
+ [上下文](#wait-event.lwl-buffer-mapping.context)
+ [原因](#wait-event.lwl-buffer-mapping.causes)
+ [操作](#wait-event.lwl-buffer-mapping.actions)

## 支持的引擎版本
<a name="wait-event.lwl-buffer-mapping.context.supported"></a>

此等待事件信息与 RDS for PostgreSQL 版本 9.6 及更高版本相关。

## 上下文
<a name="wait-event.lwl-buffer-mapping.context"></a>

*共享缓冲池*是一个 PostgreSQL 内存区域，它包含进程现在正在使用或过去使用的所有页面。当进程需要页面时，它会将页面读入共享缓冲池中。`shared_buffers` 参数会设置共享缓冲区大小并保留一个内存区域来存储表和索引页。如果更改此参数，请确保重新启动数据库。

以下情况下回发生 `LWLock:buffer_mapping` 等待事件：
+ 进程在缓冲区表中搜索页面并获取共享缓冲区映射锁。
+ 进程将页面加载到缓冲池中并获取独占缓冲区映射锁。
+ 进程从缓冲池中删除页面并获取独占缓冲区映射锁。

## 原因
<a name="wait-event.lwl-buffer-mapping.causes"></a>

当此事件发生超过正常时（可能表示性能问题），数据库正在共享缓冲池中移入和移出分页。典型的原因包括：
+ 大型查询
+ 臃肿的索引和表
+ 完整的表扫描
+ 小于工作集的共享池大小

## 操作
<a name="wait-event.lwl-buffer-mapping.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。

**Topics**
+ [监控缓冲区相关的指标](#wait-event.lwl-buffer-mapping.actions.monitor-metrics)
+ [评估您的索引策略](#wait-event.lwl-buffer-mapping.actions.indexes)
+ [减少必须快速分配的缓冲区数量](#wait-event.lwl-buffer-mapping.actions.buffers)

### 监控缓冲区相关的指标
<a name="wait-event.lwl-buffer-mapping.actions.monitor-metrics"></a>

当 `LWLock:buffer_mapping` 等待激增时，调查缓冲区命中率。您可以使用这些指标更好地了解缓冲区缓存中发生的情况。检查以下指标：

`blks_hit`  
此性能详情计数器指标表示从共享缓冲池中检索的数据块的数量。当 `LWLock:buffer_mapping` 等待事件出现后，您可能会观察到 `blks_hit` 激增。

`blks_read`  
此性能详情计数器指标表示需要将输入/输出读入共享缓冲池的数据块的数量。您可能会在 `LWLock:buffer_mapping` 等待事件的前面观察到 `blks_read` 激增。

### 评估您的索引策略
<a name="wait-event.lwl-buffer-mapping.actions.indexes"></a>

要确认您的索引策略不会降低性能，请检查以下各项：

索引膨胀  
确保索引和表膨胀不会导致不必要的分页被读入共享缓冲区。如果表中包含未使用的行，请考虑存档数据并从表中删除这些行。然后，您可以为调整大小的表重建索引。

常用查询的索引  
要确定您是否拥有最佳索引，请在性能详情中监控数据库引擎指标。`tup_returned` 指标显示读取的行数。`tup_fetched` 指标显示返回到客户端的行数量。如果 `tup_returned` 明显大于 `tup_fetched`，可能无法正确编制数据索引。此外，您的表统计数据可能不是最新的。

### 减少必须快速分配的缓冲区数量
<a name="wait-event.lwl-buffer-mapping.actions.buffers"></a>

要减少 `LWLock:buffer_mapping` 等待事件，请尝试减少必须快速分配的缓冲区数量。一种策略是执行较小的批处理操作。通过对表进行分区，也许能够实现较小的批处理。

# LWLock:BufferIO (IPC:BufferIO)
<a name="wait-event.lwlockbufferio"></a>

当 RDS for PostgreSQL 正在等待其他进程在同时尝试访问页面的情况下完成输入/输出（I/O）操作时，会发生 `LWLock:BufferIO` 事件。它的目的是将同一个分页读入共享缓冲区中。

**Topics**
+ [相关引擎版本](#wait-event.lwlockbufferio.context.supported)
+ [上下文](#wait-event.lwlockbufferio.context)
+ [原因](#wait-event.lwlockbufferio.causes)
+ [操作](#wait-event.lwlockbufferio.actions)

## 相关引擎版本
<a name="wait-event.lwlockbufferio.context.supported"></a>

此等待事件信息与所有 RDS for PostgreSQL 版本相关。对于 RDS for PostgreSQL 12 及更早版本，此等待事件命名为 lwlock:buffer\$1io，而在 RDS for PostgreSQL 13 版本中，它命名为 lwlock:bufferio。从 RDS for PostgreSQL 14 版本开始，BufferIO 等待事件从 `LWLock` 移到 `IPC` 等待事件类型（IPC:BufferIO）。

## 上下文
<a name="wait-event.lwlockbufferio.context"></a>

每个共享缓冲区都有一个与 `LWLock:BufferIO` 等待事件相关的输入/输出锁，每次必须在共享缓冲池外检索数据块（或分页）。

此锁定用于处理多个会话，而这些会话都需要访问同一个数据块。必须从共享缓冲池外部读取此数据库块，该缓冲池由 `shared_buffers` 参数定义。

一旦在共享缓冲池内读取分页，`LWLock:BufferIO` 锁即被释放。

**注意**  
`LWLock:BufferIO` 等待事件发生在 [IO:DataFileRead](wait-event.iodatafileread.md) 等待事件之前。`IO:DataFileRead` 事件在从存储中读取数据时发生。

有关轻量级锁定的更多信息，请参阅[锁定概览](https://github.com/postgres/postgres/blob/65dc30ced64cd17f3800ff1b73ab1d358e92efd8/src/backend/storage/lmgr/README#L20)。

## 原因
<a name="wait-event.lwlockbufferio.causes"></a>

`LWLock:BufferIO` 显示在主要等待中的常见原因包括以下各项：
+ 多个后端或连接试图访问同样在等待输入/输出操作的同一页面
+ 共享缓冲池大小之间的比率（由 `shared_buffers` 参数定义）以及当前工作负载所需的缓冲区数量
+ 共享缓冲池的大小与其他操作移出的分页数量没有很好地平衡
+ 需要引擎在共享缓冲池中读取更多页面的臃肿的大索引
+ 缺乏强制数据库引擎从表中读取更多页面的索引
+ 检查点发生太频繁或需要刷新太多修改过的页面
+ 试图在同一页面上执行操作的数据库连接突增

## 操作
<a name="wait-event.lwlockbufferio.actions"></a>

根据等待事件的原因，我们建议采取不同的操作：
+ 观察 Amazon CloudWatch 指标，了解 `BufferCacheHitRatio` 突然减少和 `LWLock:BufferIO` 等待事件之间的关系。此影响可能意味着您有一个较小的共享缓冲区设置。您可能需要增加数据库实例类或对其进行纵向扩展。您可以将工作负载拆分为更多的读取器节点。
+ 如果您发现 `LWLock:BufferIO` 与 `BufferCacheHitRatio` 指标降低相一致，请根据您的工作负载峰值时间优化 `max_wal_size` 和 `checkpoint_timeout`。然后确定哪个查询可能会导致发生此情况。
+ 验证是否有未使用的索引，然后将其删除。
+ 使用分区表（也具有分区索引）。这样做有助于保持较低的指数重新排序并降低其影响。
+ 避免对列进行不必要的索引编制。
+ 使用连接池防止数据库连接突增。
+ 作为最佳实践，限制与数据库的最大连接数。

# LWLock:buffer\$1content (BufferContent)
<a name="wait-event.lwlockbuffercontent"></a>

当某个会话等待读取或写入内存中的某个数据页面，而另一个会话正锁定该页面以进行写入时，会发生 `LWLock:buffer_content` 事件。在 RDS for PostgreSQL 13 及更高版本中，此等待事件称为 `BufferContent`。

**Topics**
+ [支持的引擎版本](#wait-event.lwlockbuffercontent.context.supported)
+ [上下文](#wait-event.lwlockbuffercontent.context)
+ [等待次数增加的可能原因](#wait-event.lwlockbuffercontent.causes)
+ [操作](#wait-event.lwlockbuffercontent.actions)

## 支持的引擎版本
<a name="wait-event.lwlockbuffercontent.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.lwlockbuffercontent.context"></a>

要读取或操作数据，PostgreSQL 会通过共享内存缓冲区访问数据。要从缓冲区读取，进程会在共享模式下获取缓冲区内容的轻量级锁 (LWLock)。要写入缓冲区，它会在独占模式下获得该锁。共享锁允许其他进程同时获取对该内容的共享锁。独占锁可防止其他进程获取对该内容的任何类型的锁定。

`LWLock:buffer_content` (`BufferContent`) 事件表示多个进程试图获取对特定缓冲区的内容的锁定。

## 等待次数增加的可能原因
<a name="wait-event.lwlockbuffercontent.causes"></a>

当 `LWLock:buffer_content` (`BufferContent`) 事件的发生率超过正常（可能表示性能问题）时，典型原因包括以下几点：

**增加了对同一数据的并发更新**  
更新相同缓冲区内容的查询的并发会话数可能会增加。在具有大量索引的表中，这种争用可能更加明显。

**工作负载数据不在内存中**  
当活动工作负载正在处理的数据不在内存中时，这些等待事件可能会增加。这种影响是因为持有锁的进程可以在执行磁盘输入/输出操作时保持更长时间。

**过度使用外键约束**  
外键约束可能会增加进程在缓冲区内容锁上保留的时间。这种影响是因为读取操作需要在更新引用的键时对该键进行共享缓冲区内容锁定。

## 操作
<a name="wait-event.lwlockbuffercontent.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。您可以通过使用 Amazon RDS 性能详情或查询视图 `pg_stat_activity` 来识别 `LWLock:buffer_content` (`BufferContent`) 事件。

**Topics**
+ [提高内存中的效率](#wait-event.lwlockbuffercontent.actions.in-memory)
+ [减少对外键约束的使用](#wait-event.lwlockbuffercontent.actions.foreignkey)
+ [删除未使用的索引](#wait-event.lwlockbuffercontent.actions.indexes)
+ [使用序列时增加缓存大小](#wait-event.lwlockbuffercontent.actions.sequences)

### 提高内存中的效率
<a name="wait-event.lwlockbuffercontent.actions.in-memory"></a>

为了增加活动工作负载数据在内存中的可能性，请对表进行分区或纵向扩展您的实例类。有关数据库实例类的信息，请参阅 [数据库实例类](Concepts.DBInstanceClass.md)。

### 减少对外键约束的使用
<a name="wait-event.lwlockbuffercontent.actions.foreignkey"></a>

调查在使用外键约束时遇到大量`LWLock:buffer_content` (`BufferContent`) 等待事件的工作负载。删除不必要的外键约束。

### 删除未使用的索引
<a name="wait-event.lwlockbuffercontent.actions.indexes"></a>

对于遇到大量 `LWLock:buffer_content`(`BufferContent`) 等待事件的工作负载，识别未使用的索引并删除它们。

### 使用序列时增加缓存大小
<a name="wait-event.lwlockbuffercontent.actions.sequences"></a>

如果您的表使用序列，请增加缓存大小以消除序列页和索引页上的争用。每个序列都是共享内存中的单个页面。预定义缓存是对应于每个连接的。当许多并发会话获取序列值时，这可能不足以处理工作负载。

# LWLock:lock\$1manager (LWLock:lockmanager)
<a name="wait-event.lw-lock-manager"></a>

当 RDS for PostgreSQL 引擎维护共享锁的内存区域以便在无法使用快速路径锁时分配、检查和取消分配锁时，会发生此事件。

**Topics**
+ [支持的引擎版本](#wait-event.lw-lock-manager.context.supported)
+ [上下文](#wait-event.lw-lock-manager.context)
+ [等待次数增加的可能原因](#wait-event.lw-lock-manager.causes)
+ [操作](#wait-event.lw-lock-manager.actions)

## 支持的引擎版本
<a name="wait-event.lw-lock-manager.context.supported"></a>

此等待事件信息与 RDS for PostgreSQL 版本 9.6 及更高版本相关。对于早于版本 13 的 RDS for PostgreSQL 发行版，此等待事件的名称为 `LWLock:lock_manager`。对于 RDS for PostgreSQL 版本 13 及更高版本，此等待事件的名称为 `LWLock:lockmanager`。

## 上下文
<a name="wait-event.lw-lock-manager.context"></a>

发布 SQL 语句时，RDS for PostgreSQL 会记录锁，以在并发操作期间保护数据库的结构、数据和完整性。引擎可以使用快速路径锁或不快的路径锁来实现这个目标。不快的路径锁定更昂贵，并且比快速路径锁定造成的开销更大。

### 快速路径锁定
<a name="wait-event.lw-lock-manager.context.fast-path"></a>

为了减少频繁获取和释放但很少发生冲突的锁的开销，后端进程可以使用快速路径锁定。数据库对满足以下条件的锁定使用此机制：
+ 他们使用 DEFAULT 锁定方法。
+ 它们代表数据库关系的锁，而不是共享关系。
+ 它们是不太可能发生冲突的弱锁。
+ 引擎可以快速确认不可能存在发生冲突的锁。

在以下任一条件为真时，引擎无法使用快速路径锁定：
+ 锁不满足上述标准。
+ 没有更多的插槽可用于后端进程。

要优化查询以实现快速路径锁，可以使用以下查询。

```
SELECT count(*), pid, mode, fastpath
  FROM pg_locks
 WHERE fastpath IS NOT NULL
 GROUP BY 4,3,2
 ORDER BY pid, mode;
 count | pid  |      mode       | fastpath
-------+------+-----------------+----------
16 | 9185 | AccessShareLock | t
336 | 9185 | AccessShareLock | f
1 | 9185 | ExclusiveLock   | t
```

以下查询仅显示数据库中的总数。

```
SELECT count(*), mode, fastpath
  FROM pg_locks
 WHERE fastpath IS NOT NULL
 GROUP BY 3,2
 ORDER BY mode,1;
count |      mode       | fastpath
-------+-----------------+----------
16 | AccessShareLock | t
337 | AccessShareLock | f
1 | ExclusiveLock   | t
(3 rows)
```

有关快速路径锁定的更多信息，请参阅 PostgreSQL 锁管理器 README 中的[快速路径](https://github.com/postgres/postgres/blob/master/src/backend/storage/lmgr/README#L70-L76)和 PostgreSQL 文档中的 [pg-locks](https://www.postgresql.org/docs/9.3/view-pg-locks.html#AEN98195)。

### 锁管理器的扩缩问题示例
<a name="wait-event.lw-lock-manager.context.lock-manager"></a>

在此示例中，名为 `purchases` 的表存储五年的数据，按天进行分区。每个分区有两个索引。将发生以下一系列事件：

1. 您查询了许多天的数据，这需要数据库读取许多分区。

1. 数据库为每个分区创建一个锁条目。如果分区索引是优化程序访问路径的一部分，则数据库也会为它们创建一个锁条目。

1. 当同一后端进程请求的锁条目数大于 16 时（这是 `FP_LOCK_SLOTS_PER_BACKEND` 的值），锁管理器会使用非快速路径锁定方法。

现代应用程序可能有数百个会话。如果并发会话在没有适当的分区修剪的情况下查询父级数据库，则数据库可能会创建数百甚至数千个非快速路径锁。通常，当此并发性高于 vCPU 的数量时，会出现 `LWLock:lock_manager` 等待事件。

**注意**  
`LWLock:lock_manager` 等待事件与数据库架构中的分区或索引数量无关。相反，它与数据库必须控制的非快速路径锁的数量有关。

## 等待次数增加的可能原因
<a name="wait-event.lw-lock-manager.causes"></a>

当 `LWLock:lock_manager` 等待事件发生率超出正常（可能表明性能问题）时，突增的最可能原因如下：
+ 并发活动会话正在运行不使用快速路径锁的查询。这些会话还超出了最大 vCPU。
+ 大量并发活动会话正在访问严重分区的表。每个分区都有多个索引。
+ 数据库正在经历连接风暴。预设情况下，当数据库缓慢时，某些应用程序和连接池软件会创建更多连接。这种做法使问题变得更糟。优化连接池软件，以免发生连接风暴。
+ 大量会话在不修剪分区的情况下查询父级表。
+ 数据定义语言 (DDL)、数据操作语言 (DML) 或维护命令专门锁定经常访问或修改的繁忙关系或元组。

## 操作
<a name="wait-event.lw-lock-manager.actions"></a>

如果 `CPU` 等待事件发生，它不一定表示性能问题。仅当性能下降并且此等待事件主导了数据库负载时才响应此事件。

**Topics**
+ [使用分区修剪](#wait-event.lw-lock-manager.actions.pruning)
+ [删除不必要的索引](#wait-event.lw-lock-manager.actions.indexes)
+ [优化查询以实现快速路径锁定](#wait-event.lw-lock-manager.actions.tuning)
+ [优化其他等待事件](#wait-event.lw-lock-manager.actions.other-waits)
+ [减少硬件瓶颈](#wait-event.lw-lock-manager.actions.hw-bottlenecks)
+ [使用连接池程序。](#wait-event.lw-lock-manager.actions.pooler)
+ [升级您的 RDS for PostgreSQL 版本](#wait-event.lw-lock-manager.actions.pg-version)

### 使用分区修剪
<a name="wait-event.lw-lock-manager.actions.pruning"></a>

*分区修剪*是一种适用于声明性分区表的查询优化策略，它从表扫描中排除不需要的分区，从而提高性能。预设情况下，分区修剪处于开启状态。如果它已关闭，请按如下方式将其打开。

```
SET enable_partition_pruning = on;
```

查询可以在其 `WHERE` 子句包含用于分区的列时利用分区修建。有关更多信息，请参阅 PostgreSQL 文档中的[分区修建](https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITION-PRUNING)。

### 删除不必要的索引
<a name="wait-event.lw-lock-manager.actions.indexes"></a>

数据库可能包含未使用或很少使用的索引。如果是，请考虑删除它们。请执行以下任一操作：
+ 通过阅读 PostgreSQL wiki 中的[未使用索引](https://wiki.postgresql.org/wiki/Index_Maintenance#Unused_Indexes)了解如何查找不必要的索引。
+ 运行 PG 收集器。此 SQL 脚本会收集数据库信息并将其显示在整合的 HTML 报告中。检查“未使用的索引”部分。有关更多信息，请参阅 AWS Labs GitHub 存储库中的 [pg-collector](https://github.com/awslabs/pg-collector)。

### 优化查询以实现快速路径锁定
<a name="wait-event.lw-lock-manager.actions.tuning"></a>

要了解您的查询是否使用快速路径锁定，请查询 `pg_locks` 表中的 `fastpath` 列。如果您的查询没有使用快速路径锁定，请尝试将每个查询的关系数减少到 16 个以下。

### 优化其他等待事件
<a name="wait-event.lw-lock-manager.actions.other-waits"></a>

如果 `LWLock:lock_manager` 排在主要等待列表中的第一个或第二个，请检查列表中是否也显示了以下等待事件：
+ `Lock:Relation`
+ `Lock:transactionid`
+ `Lock:tuple`

如果前面的事件显示在列表的前面，请考虑首先优化这些等待事件。这些事件可以是 `LWLock:lock_manager` 的驱动因素。

### 减少硬件瓶颈
<a name="wait-event.lw-lock-manager.actions.hw-bottlenecks"></a>

您可能存在硬件瓶颈，例如 CPU 匮乏或 Amazon EBS 带宽的最大使用率。在这样的情况下，需考虑减少硬件瓶颈。请考虑以下操作：
+ 纵向扩展您的实例类。
+ 优化占用大量 CPU 和内存的查询。
+ 更改应用程序逻辑。
+ 将您的数据存档。

有关 CPU、内存和 EBS 网络带宽的更多信息，请参阅 [Amazon RDS 实例类型](https://aws.amazon.com/rds/instance-types/)。

### 使用连接池程序。
<a name="wait-event.lw-lock-manager.actions.pooler"></a>

如果您的活动连接总数超过最大 vCPU，则需要 CPU 的操作系统进程超过实例类型所能支持的数量。在这种情况下，需考虑使用或优化连接池。有关您的实例类型 vCPU 数量的更多信息，请参阅 [Amazon RDS 实例类型](https://aws.amazon.com/rds/instance-types/)。

有关为连接池的更多信息，请参阅以下资源：
+ [Amazon RDS 代理](rds-proxy.md)
+ [pgbouncer](http://www.pgbouncer.org/usage.html)
+ *PostgreSQL 文档*中的[连接池和数据源](https://www.postgresql.org/docs/7.4/jdbc-datasource.html)

### 升级您的 RDS for PostgreSQL 版本
<a name="wait-event.lw-lock-manager.actions.pg-version"></a>

如果您当前的 RDS for PostgreSQL 版本低于 12，请升级到版本 12 或更高版本。PostgreSQL 版本 12 及更高版本具有改进的分区机制。有关版本 12 的更多信息，请参阅 [PostgreSQL 12.0 发布说明]( https://www.postgresql.org/docs/release/12.0/)。有关升级 RDS for PostgreSQL 的更多信息，请参阅 [升级 RDS for PostgreSQL 数据库引擎](USER_UpgradeDBInstance.PostgreSQL.md)。

# LWLock:pg\$1stat\$1statements
<a name="apg-rpg-lwlockpgstat"></a>

当 `pg_stat_statements` 扩展对跟踪 SQL 语句的哈希表进行独占锁定时，就会发生 LWLock:pg\$1stat\$1statements 等待事件。如果存在以下情形，可能会发生这种情况：
+ 当跟踪的语句数量达到配置的 `pg_stat_statements.max` 参数值，并且需要为更多条目腾出空间时，此扩展会对调用次数执行排序，移除 5% 的执行次数最少的语句，然后用剩余的条目重新填充哈希表。
+ 当 `pg_stat_statements` 对磁盘上的 `pgss_query_texts.stat` 文件执行 `garbage collection` 操作并重新写入该文件时。

**Topics**
+ [支持的引擎版本](#apg-rpg-lwlockpgstat.supported)
+ [上下文](#apg-rpg-lwlockpgstat.context)
+ [等待次数增加的可能原因](#apg-rpg-lwlockpgstat.causes)
+ [操作](#apg-rpg-lwlockpgstat.actions)

## 支持的引擎版本
<a name="apg-rpg-lwlockpgstat.supported"></a>

 RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="apg-rpg-lwlockpgstat.context"></a>

**了解 pg\$1stat\$1statements 扩展**：pg\$1stat\$1statements 扩展在哈希表中跟踪 SQL 语句执行统计数据。该扩展可跟踪 SQL 语句，直至达到由 `pg_stat_statements.max` 参数定义的限制。此参数确定可以跟踪的最大语句数，该数量对应于 pg\$1stat\$1statements 视图中的最大行数。

**语句统计数据持久性**：该扩展通过以下方式，在实例重新启动时持久保留语句统计数据：
+ 将数据写入名为 pg\$1stat\$1statements.stat 的文件
+ 使用 pg\$1stat\$1statements.save 参数来控制持久行为

当 pg\$1stat\$1statements.save 设为以下设置时：
+ 开启（默认）：关闭时保存统计数据，并在服务器启动时重新加载它们
+ 关闭：既不会在服务器关闭时保存统计数据，也不会在服务器启动时重新加载它们

**查询文本存储**：该扩展将所跟踪的查询的文本存储在一个名为 `pgss_query_texts.stat` 的文件中。在进行垃圾回收之前，该文件可以增长到所有跟踪的 SQL 语句平均大小的两倍。该扩展需要在清理操作和重新写入 `pgss_query_texts.stat` 文件期间对哈希表实施独占锁定。

**语句取消分配流程**：当所跟踪语句的数量达到 `pg_stat_statements.max` 限制并且需要跟踪新的语句时，此扩展会：
+ 对哈希表实施独占锁定（LWLock:pg\$1stat\$1statements）。
+ 将现有数据加载到本地内存中。
+ 根据调用次数执行快速排序。
+ 移除调用次数最少的语句（底部 5%）。
+ 用剩余的条目重新填充哈希表。

**监控语句取消分配**：在 PostgreSQL 14 及更高版本中，可以使用 pg\$1stat\$1statements\$1info 视图来监控语句取消分配。此视图包括一个 dealloc 列，该列显示对语句取消分配来为新语句腾出空间的次数

如果频繁地对语句取消分配，则会导致更频繁地对磁盘上的 `pgss_query_texts.stat` 文件进行垃圾回收。

## 等待次数增加的可能原因
<a name="apg-rpg-lwlockpgstat.causes"></a>

`LWLock:pg_stat_statements` 等待次数增加的典型原因包括：
+ 应用程序使用的唯一查询的数量有所增加。
+ 与正在使用的唯一查询的数量相比，`pg_stat_statements.max` 参数值较小。

## 操作
<a name="apg-rpg-lwlockpgstat.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。可以通过使用 Amazon RDS 性能详情或查询视图 `pg_stat_activity` 来识别 `LWLock:pg_stat_statements` 事件。

调整以下 `pg_stat_statements` 参数，以控制跟踪行为并减少 LWLock:pg\$1stat\$1 statements 等待事件。

**Topics**
+ [禁用 pg\$1stat\$1statements.track 参数](#apg-rpg-lwlockpgstat.actions.disabletrack)
+ [提高 pg\$1stat\$1statements.max 参数的值](#apg-rpg-lwlockpgstat.actions.increasemax)
+ [禁用 pg\$1stat\$1statements.track\$1utility 参数](#apg-rpg-lwlockpgstat.actions.disableutility)

### 禁用 pg\$1stat\$1statements.track 参数
<a name="apg-rpg-lwlockpgstat.actions.disabletrack"></a>

如果 LWLock:pg\$1stat\$1statements 等待事件对数据库性能产生不利影响，并且在进一步分析 `pg_stat_statements` 视图以确定根本原因之前需要快速的解决方案，则可以通过将 `pg_stat_statements.track` 参数设置为 `none` 来禁用该参数。这将禁用对语句统计数据的回收。

### 提高 pg\$1stat\$1statements.max 参数的值
<a name="apg-rpg-lwlockpgstat.actions.increasemax"></a>

要减少取消分配并最大限度地减少磁盘上 `pgss_query_texts.stat` 文件的垃圾回收，请提高 `pg_stat_statements.max` 参数的值。默认值为 `5,000`。

**注意**  
`pg_stat_statements.max` 参数为静态参数。必须重新启动数据库实例，才能应用对此参数的任何更改。

### 禁用 pg\$1stat\$1statements.track\$1utility 参数
<a name="apg-rpg-lwlockpgstat.actions.disableutility"></a>

您可以分析 pg\$1stat\$1statements 视图，以确定哪些实用程序命令正在消耗由 `pg_stat_statements` 跟踪的大部分资源。

`pg_stat_statements.track_utility` 参数控制模块是否跟踪实用程序命令，包括除 SELECT、INSERT、UPDATE、DELETE 和 MERGE 之外的所有命令。默认情况下，此参数设置为 `on`。

例如，当应用程序使用许多本质上为唯一的保存点查询时，它可能会增加语句取消分配。要解决此问题，您可以禁用 `pg_stat_statements.track_utility` 参数以停止 `pg_stat_statements` 跟踪保存点查询。

**注意**  
`pg_stat_statements.track_utility` 参数是一个动态参数。无需重新启动数据库实例即可更改其值。

**Example pg\$1stat\$1statements 中的唯一保存点查询示例**  <a name="savepoint-queries"></a>

```
                     query                       |       queryid       
-------------------------------------------------+---------------------
 SAVEPOINT JDBC_SAVEPOINT_495701                 | -7249565344517699703
 SAVEPOINT JDBC_SAVEPOINT_1320                   | -1572997038849006629
 SAVEPOINT JDBC_SAVEPOINT_26739                  |  54791337410474486
 SAVEPOINT JDBC_SAVEPOINT_1294466                |  8170064357463507593
 ROLLBACK TO SAVEPOINT JDBC_SAVEPOINT_65016      | -33608214779996400
 SAVEPOINT JDBC_SAVEPOINT_14185                  | -2175035613806809562
 SAVEPOINT JDBC_SAVEPOINT_45837                  | -6201592986750645383
 SAVEPOINT JDBC_SAVEPOINT_1324                   |  6388797791882029332
```

PostgreSQL 17 引入了多项增强功能来进行实用程序命令跟踪：
+ 保存点名称现在显示为常量。
+ 现在，两阶段提交命令的全局事务 ID（GID）显示为常量。
+ DEALLOCATE 语句的名称显示为常量。
+ CALL 参数现在显示为常量。

# LWLock:SubtransSLRU (LWLock:SubtransControlLock)
<a name="wait-event.lwlocksubtransslru"></a>

`LWLock:SubtransSLRU` 和 `LWLock:SubtransBuffer` 等待事件表示某个会话正在等待访问用于子事务信息的简单最近最少使用（SLRU）缓存。在确定事务可见性和父子关系时会发生这种情况。
+ `LWLock:SubtransSLRU`：某个进程正在等待访问子事务的最近使用最少的（SLRU）简单缓存。在 RDS for PostgreSQL 版本 13 之前，此等待事件称为 `SubtransControlLock`。
+ `LWLock:SubtransBuffer`：某个进程正在等待子事务的最近使用最少的（SLRU）简单缓冲区上的输入/输出。在 RDS for PostgreSQL 版本 13 之前，此等待事件称为 `subtrans`。

**Topics**
+ [支持的引擎版本](#wait-event.lwlocksubtransslru.supported)
+ [上下文](#wait-event.lwlocksubtransslru.context)
+ [等待次数增加的可能原因](#wait-event.lwlocksubtransslru.causes)
+ [操作](#wait-event.lwlocksubtransslru.actions)

## 支持的引擎版本
<a name="wait-event.lwlocksubtransslru.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.lwlocksubtransslru.context"></a>

**了解子事务** – 在 PostgreSQL 中，子事务是事务中的事务。它也称为嵌套事务。

子事务通常是在您使用以下内容时创建的：
+ `SAVEPOINT` 命令
+ 异常块（`BEGIN/EXCEPTION/END`）

子事务允许您在不影响整个事务的情况下回滚部分事务。这样就对事务管理提供了精细控制。

**实现细节** – PostgreSQL 将子事务实现为主事务中的嵌套结构。每个子事务都有自己的事务 ID。

关键实施方面：
+ 事务 ID 在 `pg_xact` 中进行跟踪
+ 父子关系存储在 `PGDATA` 下的 `pg_subtrans` 子目录中
+ 每个数据库会话最多可以维护 `64` 个处于活跃状态的子事务
+ 超过此限制会导致子事务溢出，从而需要访问用于子事务信息的简单最近最少使用（SLRU）缓存

## 等待次数增加的可能原因
<a name="wait-event.lwlocksubtransslru.causes"></a>

子事务 SLRU 争用的常见原因包括：
+ **过度使用 SAVEPOINT 和 EXCEPTION 处理** – 无论是否发生异常，带有 `EXCEPTION` 处理程序的 PL/pgSQL 过程都会自动创建隐式保存点。每个 `SAVEPOINT` 都会启动一个新的子事务。当单个事务累积超过 64 个子事务时，它会触发子事务 SLRU 溢出。
+ **驱动程序和 ORM 配置** – `SAVEPOINT` 使用方式可以在应用程序代码中显式指定，也可以通过驱动程序配置隐式生效。许多常用的 ORM 工具和应用程序框架本身都支持嵌套事务。下面是一些常见的示例：
  + 如果将 JDBC 驱动程序参数 `autosave` 设置为 `always` 或 `conservative`，则会在每次查询之前生成保存点。
  + 当设置为 `propagation_nested` 时，为 Spring Framework 事务定义。
  + 设置 `requires_new: true` 时为 Rails。
  + 使用 `session.begin_nested` 时为 SQLAlchemy。
  + 使用嵌套 `atomic()` 块时为 Django。
  + 使用 `Savepoint` 时为 GORM。
  + 当回滚级别设置设为语句级回滚（例如 `PROTOCOL=7.4-2`）时，为 psqlODBC。
+ **具有长时间运行的事务和子事务的高并发工作负载** – 当在高并发工作负载以及长时间运行的事务和子事务期间发生子事务 SLRU 溢出时，PostgreSQL 会遇到更多的争用。这表现为 `LWLock:SubtransBuffer` 和 `LWLock:SubtransSLRU` 锁的等待事件时间较长。

## 操作
<a name="wait-event.lwlocksubtransslru.actions"></a>

根据等待事件的原因，我们建议采取不同的操作。有些操作可以立即提供缓解措施，而另一些则需要调查和长期纠正。

**Topics**
+ [监控子事务使用情况](#wait-event.lwlocksubtransslru.actions.monitor)
+ [配置内存参数](#wait-event.lwlocksubtransslru.actions.memory)
+ [长期操作](#wait-event.lwlocksubtransslru.actions.longterm)

### 监控子事务使用情况
<a name="wait-event.lwlocksubtransslru.actions.monitor"></a>

对于 PostgreSQL 版本 16.1 及更高版本，使用以下查询来监控每个后端的子事务计数和溢出状态。此查询将后端统计数据与活动信息联接起来，以显示哪些进程正在使用子事务：

```
SELECT a.pid, usename, query, state, wait_event_type,
       wait_event, subxact_count, subxact_overflowed
FROM (SELECT id, pg_stat_get_backend_pid(id) pid, subxact_count, subxact_overflowed
      FROM pg_stat_get_backend_idset() id
           JOIN LATERAL pg_stat_get_backend_subxact(id) AS s ON true
     ) a
JOIN pg_stat_activity b ON a.pid = b.pid;
```

对于 PostgreSQL 版本 13.3 及更高版本，请监控 `pg_stat_slru` 视图中的子事务缓存压力。以下 SQL 会查询检索 Subtrans 组件的 SLRU 缓存统计信息：

```
SELECT * FROM pg_stat_slru WHERE name = 'Subtrans';
```

`blks_read` 值持续增加表示未缓存的子事务经常访问磁盘，这表明潜在的 SLRU 缓存压力很大。

### 配置内存参数
<a name="wait-event.lwlocksubtransslru.actions.memory"></a>

对于 PostgreSQL 17.1 及更高版本，您可以使用 `subtransaction_buffers` 参数配置子事务 SLRU 缓存大小。以下配置示例演示如何设置子事务缓冲区参数：

```
subtransaction_buffers = 128
```

此参数指定用于缓存子事务内容（`pg_subtrans`）的共享内存量。如果不带单位指定，则该值表示 `BLCKSZ` 字节的块，通常每个块 8KB。例如，将该值设置为 128 会为子事务缓存分配 1MB（128 \$1 8KB）的内存。

**注意**  
可以在集群级别设置此参数，以使所有实例保持一致。测试和调整该值，使其最适合您的特定工作负载要求和实例类。必须重启写入器实例才能使参数更改生效。

### 长期操作
<a name="wait-event.lwlocksubtransslru.actions.longterm"></a>
+ **检查应用程序代码和配置** - 查看您的应用程序代码和数据库驱动程序配置，了解显式和隐式 `SAVEPOINT` 用法以及子事务的总体使用情况。识别可能生成超过 64 个子事务的事务。
+ **减少保存点使用量** – 尽量在事务中少用保存点：
  + 检查带有 EXCEPTION 块的 PL/pgSQL 过程和函数。EXCEPTION 块会自动创建隐式保存点，这可能会导致子事务溢出。每个 EXCEPTION 子句都会创建一个子事务，无论在执行过程中是否实际发生异常。  
**Example**  

    示例 1：EXCEPTION 块使用有问题

    以下代码示例演示了创建多个子事务的有问题 EXCEPTION 块用法：

    ```
    CREATE OR REPLACE FUNCTION process_user_data()
    RETURNS void AS $$
    DECLARE
        user_record RECORD;
    BEGIN
        FOR user_record IN SELECT * FROM users LOOP
            BEGIN
                -- This creates a subtransaction for each iteration
                INSERT INTO user_audit (user_id, action, timestamp)
                VALUES (user_record.id, 'processed', NOW());
                
                UPDATE users 
                SET last_processed = NOW() 
                WHERE id = user_record.id;
                
            EXCEPTION
                WHEN unique_violation THEN
                    -- Handle duplicate audit entries
                    UPDATE user_audit 
                    SET timestamp = NOW() 
                    WHERE user_id = user_record.id AND action = 'processed';
            END;
        END LOOP;
    END;
    $$ LANGUAGE plpgsql;
    ```

    以下经过改进的代码示例通过使用 UPSERT 代替异常处理来减少子事务的使用量：

    ```
    CREATE OR REPLACE FUNCTION process_user_data()
    RETURNS void AS $$
    DECLARE
        user_record RECORD;
    BEGIN
        FOR user_record IN SELECT * FROM users LOOP
            -- Use UPSERT to avoid exception handling
            INSERT INTO user_audit (user_id, action, timestamp)
            VALUES (user_record.id, 'processed', NOW())
            ON CONFLICT (user_id, action) 
            DO UPDATE SET timestamp = NOW();
            
            UPDATE users 
            SET last_processed = NOW() 
            WHERE id = user_record.id;
        END LOOP;
    END;
    $$ LANGUAGE plpgsql;
    ```  
**Example**  

    示例 2：STRICT 异常处理程序

    以下代码示例演示了使用 NO\$1DATA\$1FOUND 时存在问题的 EXCEPTION 处理方式：

    ```
    CREATE OR REPLACE FUNCTION get_user_email(p_user_id INTEGER)
    RETURNS TEXT AS $$
    DECLARE
        user_email TEXT;
    BEGIN
        BEGIN
            -- STRICT causes an exception if no rows or multiple rows found
            SELECT email INTO STRICT user_email 
            FROM users 
            WHERE id = p_user_id;
            
            RETURN user_email;
            
        EXCEPTION
            WHEN NO_DATA_FOUND THEN
                RETURN 'Email not found';
        END;
    END;
    $$ LANGUAGE plpgsql;
    ```

    以下经过改进的代码示例通过使用 IF NOT FOUND 代替异常处理来避免子事务：

    ```
    CREATE OR REPLACE FUNCTION get_user_email(p_user_id INTEGER)
    RETURNS TEXT AS $$
    DECLARE
        user_email TEXT;
    BEGIN
         SELECT email INTO user_email 
         FROM users 
         WHERE id = p_user_id;
            
         IF NOT FOUND THEN
             RETURN 'Email not found';
         ELSE
             RETURN user_email;
         END IF;
    END;
    $$ LANGUAGE plpgsql;
    ```
  + JDBC 驱动程序 - 如果 `autosave` 参数设置为 `always` 或 `conservative`，则会在每次查询之前生成保存点。评估您的应用程序是否可以接受 `never` 设置。
  + PostgreSQL ODBC 驱动程序（psqlODBC）– 回滚级别设置（用于语句级回滚）创建隐式保存点以启用语句回滚功能。评估您的应用程序是否可以接受事务级别的回滚或不进行回滚。
  + 检查 ORM 事务配置
  + 考虑不需要保存点的替代错误处理策略
+ **优化事务设计** – 重组事务以避免过度嵌套，并降低出现子事务溢出情况的可能性。
+ **减少长时间运行的事务** – 长时间运行的事务会因为更长时间地保留子事务信息而加剧子事务问题。监控性能详情指标并将 `idle_in_transaction_session_timeout` 参数配置为自动终止空闲事务。
+ 监控性能详情指标 – 跟踪包括 `idle_in_transaction_count`（处于空闲事务状态的会话数）和 `idle_in_transaction_max_time`（运行时间最长的空闲事务持续时间）在内的指标，以检测长时间运行的事务。
+ 配置 `idle_in_transaction_session_timeout` - 在参数组中设置此参数，以便在指定持续时间后自动终止空闲事务。
+ 主动监控 – 监控 `LWLock:SubtransBuffer` 和 `LWLock:SubtransSLRU` 等待事件的高发生率，以在与子事务相关的争用变得严重之前将其检测出来。

# Timeout:PgSleep
<a name="wait-event.timeoutpgsleep"></a>

当服务器进程调用 `pg_sleep` 函数并且等待睡眠超时过期时，会发生 `Timeout:PgSleep` 事件。

**Topics**
+ [支持的引擎版本](#wait-event.timeoutpgsleep.context.supported)
+ [等待次数增加的可能原因](#wait-event.timeoutpgsleep.causes)
+ [操作](#wait-event.timeoutpgsleep.actions)

## 支持的引擎版本
<a name="wait-event.timeoutpgsleep.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 等待次数增加的可能原因
<a name="wait-event.timeoutpgsleep.causes"></a>

当应用程序、存储函数或用户发出调用以下函数之一的 SQL 语句时，会发生此等待事件：
+ `pg_sleep`
+ `pg_sleep_for`
+ `pg_sleep_until`

前面的函数会延迟执行，直到经过指定的秒数为止。例如，`SELECT pg_sleep(1)` 暂停 1 秒。有关更多信息，请参阅 PostgreSQL 文档中的[延迟执行](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-DELAY)。

## 操作
<a name="wait-event.timeoutpgsleep.actions"></a>

确定正在运行 `pg_sleep` 函数的语句。确定使用该功能是否合适。

# Timeout:VacuumDelay
<a name="wait-event.timeoutvacuumdelay"></a>

`Timeout:VacuumDelay` 事件表明已超过 vacuum 输入输出的成本限制，vacuum 进程已进入休眠状态。vacuum 操作在相应的成本延迟参数中指定的持续时间内停止，然后恢复工作。对于手动 vacuum 命令，延迟在 `vacuum_cost_delay` 参数中指定。对于 autovacuum 进程守护程序，延迟在 `autovacuum_vacuum_cost_delay parameter.` 中指定。

**Topics**
+ [支持的引擎版本](#wait-event.timeoutvacuumdelay.context.supported)
+ [上下文](#wait-event.timeoutvacuumdelay.context)
+ [等待次数增加的可能原因](#wait-event.timeoutvacuumdelay.causes)
+ [操作](#wait-event.timeoutvacuumdelay.actions)

## 支持的引擎版本
<a name="wait-event.timeoutvacuumdelay.context.supported"></a>

RDS for PostgreSQL 的所有版本均支持此等待事件信息。

## 上下文
<a name="wait-event.timeoutvacuumdelay.context"></a>

PostgreSQL 既有 autovacuum 进程守护程序，也有手动 vacuum 命令。RDS for PostgreSQL 数据库实例的 autovacuum 进程原定设置为“on”。手动 vacuum 命令可根据需要使用，例如，用于清除不活动元组表或生成新的统计数据。

在执行 vacuum 操作时，PostgreSQL 在系统执行各种输入输出操作时使用内部计数器来跟踪估计成本。当计数器达到由成本限制参数指定的值时，执行操作的进程将在成本延迟参数中指定的短暂持续时间内休眠。然后，它重置计数器并继续操作。

vacuum 进程具有可用于调节资源消耗的参数。autovacuum 和手动 vacuum 命令有自己的参数来设置成本限制值。它们也有自己的参数来指定成本延迟，即达到极限时让 vacuum 进入休眠状态的时间量。通过这种方式，成本延迟参数可以作为资源消耗的限制机制。在下面的列表中，您可以找到这些参数的说明。

**影响 autovacuum 进程守护程序限制的参数**
+ `[autovacuum\$1vacuum\$1cost\$1limit](https://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html#GUC-AUTOVACUUM-VACUUM-COST-LIMIT)` – 指定要在自动 vacuum 操作中使用的成本限制值。增加此参数的设置可使 Vacuum 进程使用更多资源并减少 `Timeout:VacuumDelay` 等待事件。
+ `[autovacuum\$1vacuum\$1cost\$1delay](https://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html#GUC-AUTOVACUUM-VACUUM-COST-DELAY)` – 指定要在自动 vacuum 操作中使用的成本延迟值。原定设置值为 2 毫秒。将延迟参数设置为 0 会关闭限制机制，因此，不会出现 `Timeout:VacuumDelay` 等待事件。

有关更多信息，请参阅 PostgreSQL 文档中的 [Automatic Vacuuming](https://www.postgresql.org/docs/current/runtime-config-autovacuum.html#GUC-AUTOVACUUM-VACUUM-COST-DELAY)。

**影响限制手动 vacuum 进程的参数**
+ `vacuum_cost_limit` – vacuum 进程进入休眠状态的阈值。原定设置情况下，此限制为 200。此数字表示各种资源所需的额外输入输出的累计成本估计值。增加此值会减少 `Timeout:VacuumDelay` 等待事件的数量。
+ `vacuum_cost_delay` – 达到 vacuum 成本限制时 vacuum 进程休眠的时间量。原定设置为 0，这意味着此功能关闭。您可以将其设置为整数值以指定开启此功能的毫秒数，但我们建议您将其保留为原定设置。

有关 `vacuum_cost_delay` 参数的更多信息，请参阅 PostgreSQL 文档中的[资源消耗量](https://www.postgresql.org/docs/current/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-VACUUM-COST)。

要了解有关如何在 RDS for PostgreSQL 中配置和使用 autovacuum 的更多信息，请参阅 [在 Amazon RDS for PostgreSQL 上使用 PostgreSQL autovacuum](Appendix.PostgreSQL.CommonDBATasks.Autovacuum.md)。

## 等待次数增加的可能原因
<a name="wait-event.timeoutvacuumdelay.causes"></a>

`Timeout:VacuumDelay` 受成本限制参数设置（`vacuum_cost_limit`、`autovacuum_vacuum_cost_limit`）与控制 vacuum 的休眠持续时间的成本延迟参数（`vacuum_cost_delay`、`autovacuum_vacuum_cost_delay`）之间平衡的影响。提高成本限制参数值允许 vacuum 在进入休眠状态之前使用更多资源。这样可以减少 `Timeout:VacuumDelay` 等待事件。增加任一延迟参数都会使 `Timeout:VacuumDelay` 等待事件发生的频率更高且持续的时间更长。

`autovacuum_max_workers` 参数设置还可以增加 `Timeout:VacuumDelay` 的数量。每个额外的 autovacuum 工作进程都有助于内部计数器机制，因此，与单个 autovacuum 工作进程相比，可以更快地达到限制。随着更快地达到成本限制，成本延迟会更频繁地生效，从而导致更多的 `Timeout:VacuumDelay` 等待事件。有关更多信息，请参阅 PostgreSQL 文档中的 [autovacuum\$1max\$1workers](https://www.postgresql.org/docs/current/runtime-config-autovacuum.html#GUC-AUTOVACUUM-MAX-WORKERS)。

大型对象（如 500GB 或更大）也会引发此等待事件，因为 vacuum 可能需要一些时间才能完成处理大型对象。

## 操作
<a name="wait-event.timeoutvacuumdelay.actions"></a>

如果 vacuum 操作按预期完成，则无需补救。换句话说，此等待事件不一定表示有问题。它表示 vacuum 在延迟参数中指定的时间段内进入休眠状态，以便可以将资源应用于需要完成的其他进程。

如果您希望更快地完成 vacuum 操作，可以降低延迟参数。这可缩短 vacuum 休眠的时间。