

# LWLock:SubtransSLRU (LWLock:SubtransControlLock)


`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)

## 支持的引擎版本


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

## 上下文


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

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

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

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

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

## 等待次数增加的可能原因


子事务 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` 锁的等待事件时间较长。

## 操作


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

**Topics**
+ [

### 监控子事务使用情况
](#wait-event.lwlocksubtransslru.actions.monitor)
+ [

### 配置内存参数
](#wait-event.lwlocksubtransslru.actions.memory)
+ [

### 长期操作
](#wait-event.lwlocksubtransslru.actions.longterm)

### 监控子事务使用情况


对于 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 缓存压力很大。

### 配置内存参数


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

```
subtransaction_buffers = 128
```

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

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

### 长期操作

+ **检查应用程序代码和配置** - 查看您的应用程序代码和数据库驱动程序配置，了解显式和隐式 `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` 等待事件的高发生率，以在与子事务相关的争用变得严重之前将其检测出来。