

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

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

`LWLock:SubtransSLRU` 和 `LWLock:SubtransBuffer`等待事件表示工作階段正在等待存取簡單的最近最少使用 (SLRU) 快取以取得子交易資訊。這會在判斷交易可見性和父子關係時發生。
+ `LWLock:SubtransSLRU`：程序正在等待為子交易存取簡單的最近最少使用 (SLRU) 快取。在版本 13 之前的 RDS for PostgreSQL 中，此等待事件稱為 `SubtransControlLock`。
+ `LWLock:SubtransBuffer`：程序正在等待簡單最近最少使用 (SLRU) 緩衝區上的輸入/輸出進行子交易。在版本 13 之前的 RDS for PostgreSQL 中，此等待事件稱為 `subtrans`。

**Topics**
+ [支援的引擎版本](#wait-event.lwlocksubtransslru.supported)
+ [Context](#wait-event.lwlocksubtransslru.context)
+ [等待時間增加的可能原因](#wait-event.lwlocksubtransslru.causes)
+ [動作](#wait-event.lwlocksubtransslru.actions)

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

所有 RDS for PostgreSQL 版本都支援此等待事件資訊。

## Context
<a name="wait-event.lwlocksubtransslru.context"></a>

**了解子交易** – 子交易是 PostgreSQL 中交易內的交易。它也稱為巢狀交易。

子交易通常會在您使用 時建立：
+ `SAVEPOINT` 命令
+ 例外狀況區塊 (`BEGIN/EXCEPTION/END`)

子交易可讓您復原部分交易，而不會影響整個交易。這可讓您精細控制交易管理。

**實作詳細資訊** – PostgreSQL 在主要交易中實作子交易做為巢狀結構。每個子交易都會取得自己的交易 ID。

關鍵實作層面：
+ 交易 IDs會在 中追蹤 `pg_xact`
+ 父子關係存放在 下的`pg_subtrans`子目錄中 `PGDATA`
+ 每個資料庫工作階段最多可以維持 個`64`作用中的子交易
+ 超過此限制會導致子交易溢位，這需要存取簡單的最近最少使用 (SLRU) 快取以取得子交易資訊

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

子交易 SLRU 爭用的常見原因包括：
+ **過度使用 SAVEPOINT 和 EXCEPTION 處理** – PL/pgSQL 程序搭配`EXCEPTION`處理常式會自動建立隱含儲存點，無論是否發生例外狀況。每個 都會`SAVEPOINT`啟動新的子交易。當單一交易累積超過 64 個子交易時，會觸發子交易 SLRU 溢位。
+ **驅動程式和 ORM 組態** – `SAVEPOINT` 用量可以在應用程式程式碼中明確，或透過驅動程式組態隱含。許多常用的 ORM 工具和應用程式架構原生支援巢狀交易。以下是一些常見的範例：
  + 如果 JDBC 驅動程式參數 `autosave`設定為 `always`或 `conservative`，則 會在每次查詢之前產生儲存點。
  + 設定為 時的 Spring Framework 交易定義`propagation_nested`。
  + `requires_new: true` 已設定 時的 Rails。
  + 使用 `session.begin_nested` 時的 SQLAlchemy。
  + 使用巢狀`atomic()`區塊時的 Django。
  + 使用 `Savepoint` 時的 GORM。
  + psqlODBC 將回復層級設定設為陳述式層級回復時 （例如，`PROTOCOL=7.4-2`)。
+ **具有長時間執行交易和子交易的高並行工作負載** – 當子交易 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 處理的問題：

    ```
    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`等待事件以偵測與交易相關的爭用，然後再變得至關重要。