

# Lock:Relation
<a name="apg-waits.lockrelation"></a>

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

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

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

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

## 上下文
<a name="apg-waits.lockrelation.context"></a>

大多数 PostgreSQL 命令隐式使用锁来控制对表中数据的并发访问。您还可以通过 `LOCK` 命令在应用程序代码中显式使用这些锁。许多锁定模式彼此不兼容，它们可以在尝试访问同一对象时阻止事务。发生这种情况时，Aurora 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="apg-waits.lockrelation.causes"></a>

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

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

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

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

## 操作
<a name="apg-waits.lockrelation.actions"></a>

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

**Topics**
+ [减少阻止 SQL 语句的影响](#apg-waits.lockrelation.actions.reduce-blocks)
+ [尽量减少维护操作的影响](#apg-waits.lockrelation.actions.maintenance)
+ [检查读取器锁](#apg-waits.lockrelation.actions.readerlocks)

### 减少阻止 SQL 语句的影响
<a name="apg-waits.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="apg-waits.lockrelation.actions.maintenance"></a>

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

### 检查读取器锁
<a name="apg-waits.lockrelation.actions.readerlocks"></a>

您可以看到写入器和读取器的并发会话如何持有互相阻止的锁。执行此操作的一种方法是运行返回锁定类型和关系的查询。在该表中，可以找到对两个此类并发会话（一个写入器会话和一个读取器会话）的一系列查询。

重播过程会在取消读取器查询之前等待持续时间 `max_standby_streaming_delay`。如示例中所示，100 毫秒的锁定超时远低于 `max_standby_streaming_delay` 默认值 30 秒。锁定在出现问题之前已超时。


| 序列事件 | Session | 命令或输出 | 
| --- | --- | --- | 
|  使用指定的值设置名为 READER 的环境变量，并尝试使用此端点连接到数据库实例。  |  读取器会话  |  CLI 命令： <pre>export READER=aurorapg2.12345678910.us-west-1.rds.amazonaws.com<br /><br />psql -h $READER</pre> 输出： 

```
psql (15devel, server 10.14)
Type "help" for help.
```  | 
|  设置名为 WRITER 的环境变量，并尝试使用此端点连接到数据库实例。  |  写入器会话  |  CLI 命令： <pre>export WRITER=aurorapg1.12345678910.us-west-1.rds.amazonaws.com<br />psql -h $WRITER</pre> 输出： 

```
psql (15devel, server 10.14) 
Type "help" for help.
```  | 
|  写入器会话在写入器实例上创建表 t1。  |  写入器会话  |  PostgreSQL 查询： <pre>postgres=> CREATE TABLE t1(b integer);<br />CREATE TABLE</pre>  | 
|  如果写入器上没有冲突的查询，则会立即在写入器上获取 ACCESS EXCLUSIVE 锁定。  |  写入器会话  |  `ACCESS EXCLUSIVE` 锁定已启用  | 
|  读取器会话将锁定超时间隔设置为 100 毫秒。  |  读取器会话  |  PostgreSQL 查询： <pre>postgres=> SET lock_timeout=100;<br />SET</pre>  | 
|  读取器会话尝试从读取器实例上的表 t1 中读取数据。  |  读取器会话  |  PostgreSQL 查询： <pre>postgres=> SELECT * FROM t1;</pre> 示例输出： 

```
b
---
(0 rows)
```  | 
|  写入器会话删除 t1。  |  写入器会话  |  PostgreSQL 查询： <pre>postgres=> BEGIN;<br />BEGIN<br />postgres=> DROP TABLE t1;<br />DROP TABLE<br />postgres=></pre>  | 
|  查询超时并在读取器上被取消。  |  读取器会话  |  PostgreSQL 查询： <pre>postgres=> SELECT * FROM t1;</pre> 示例输出： 

```
ERROR:  canceling statement due to lock timeout
LINE 1: SELECT * FROM t1;
                      ^
```  | 
|  为确定错误的原因，读取器会话查询 `pg_locks` 和 `pg_stat_activity`。  |  读取器会话  |  PostgreSQL 查询： <pre>postgres=> SELECT locktype, relation, mode, backend_type<br />postgres=> FROM pg_locks l, pg_stat_activity t1<br />postgres=> WHERE l.pid=t1.pid AND relation = 't1'::regclass::oid;</pre>  | 
|  结果表明，`aurora wal replay` 进程对表 t1 持有 `ACCESS EXCLUSIVE` 锁定。  |  读取器会话  |  查询结果： <pre> locktype | relation |        mode         |   backend_type<br />----------+----------+---------------------+-------------------<br /> relation | 68628525 | AccessExclusiveLock | aurora wal replay<br />(1 row)</pre>  | 