LWLock:SubtransSLRU - Amazon Aurora

LWLock:SubtransSLRU

LWLock:SubtransSLRULWLock:SubtransBuffer 等待事件表示某个会话正在等待访问用于子事务信息的简单最近最少使用(SLRU)缓存。在确定事务可见性和父子关系时会发生这种情况。

  • LWLock:SubtransSLRU:某个进程正在等待访问子事务的最近使用最少的(SLRU)简单缓存。在 Aurora PostgreSQL 12.22 及更早版本中,此等待事件称为 SubtransControlLock

  • LWLock:SubtransBuffer:某个进程正在等待子事务的最近使用最少的(SLRU)简单缓冲区上的输入/输出。在 Aurora PostgreSQL 12.22 及更早版本中,此等待事件称为 subtrans

支持的引擎版本

Aurora 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 设置为 alwaysconservative,则会在每次查询之前生成保存点。

    • 当设置为 propagation_nested 时,为 Spring Framework 事务定义。

    • 设置 requires_new: true 时为 Rails。

    • 使用 session.begin_nested 时为 SQLAlchemy。

    • 使用嵌套 atomic() 块时为 Django。

    • 使用 Savepoint 时为 GORM。

    • 当回滚级别设置设为语句级回滚(例如 PROTOCOL=7.4-2)时,为 psqlODBC。

  • 具有长时间运行的事务和子事务的高并发工作负载 – 当在高并发工作负载以及长时间运行的事务和子事务期间发生子事务 SLRU 溢出时,PostgreSQL 会遇到更多的争用。这表现为 LWLock:SubtransBufferLWLock:SubtransSLRU 锁的等待事件时间较长。

操作

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

监控子事务使用情况

对于 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 * 8KB)的内存。

注意

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

长期操作

  • 检查应用程序代码和配置 - 查看您的应用程序代码和数据库驱动程序配置,了解显式和隐式 SAVEPOINT 用法以及子事务的总体使用情况。识别可能生成超过 64 个子事务的事务。

  • 减少保存点使用量 – 尽量在事务中少用保存点:

    • 检查带有 EXCEPTION 块的 PL/pgSQL 过程和函数。EXCEPTION 块会自动创建隐式保存点,这可能会导致子事务溢出。每个 EXCEPTION 子句都会创建一个子事务,无论在执行过程中是否实际发生异常。

      示例 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;

      示例 2:STRICT 异常处理程序

      以下代码示例演示了使用 NO_DATA_FOUND 时存在问题的 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 参数设置为 alwaysconservative,则会在每次查询之前生成保存点。评估您的应用程序是否可以接受 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:SubtransBufferLWLock:SubtransSLRU 等待事件的高发生率,以在与子事务相关的争用变得严重之前将其检测出来。