

# 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`。要减少瓶颈，请考虑以下方法：
+ 纵向扩展您的实例类类型。
+ 优化资源密集型查询。
+ 更改应用程序逻辑。
+ 存档很少访问的数据。