PostgreSQL笔记32:死锁的诊断、处理与加锁原则

INSERT INTO实战指南:从原理、避坑到金融级数据迁移 INSERT INTO是SQL中最基础却最易引发生产事故的数据写入操作,其本质是事务性数据投递,涉及WAL日志、共享缓冲区、锁机制与执行计划优化。理解单行插入与批量插入的I/O路径差异,能避免90%的性能瓶颈;掌握ON CONFLICT、CTAS、INSERT...SELECT DISTINCT等模式的适用边界,可显著提升去重、迁移与初始化效率。在金融、电商等高一致性场景中,INSERT常因隐式锁升级、编码错乱、内存溢出或连接泄漏导致服务中断。本文聚焦真实生产环境下的INSERT调优策略、分批控制、错误收敛 阅读详情

纲要

  • 死锁(Deadlock)的定义与形成条件
  • PostgreSQL 的死锁检测机制:deadlock_timeout 参数
  • 死锁的预防、检测与恢复策略
  • 单机环境下的死锁复现:行级锁与事务交错
  • 死锁检测的牺牲者选择逻辑:常规事务与 autovacuum 的特殊处理
  • 流复制(Streaming Replication)场景下的死锁:备库 startup 进程与用户查询的冲突
  • UPDATE 语句的锁获取顺序与死锁:顺序扫描 vs 索引扫描
  • 加锁原则:够用即可、越快越好、递增获取、相同顺序

死锁的定义与形成条件

死锁(Deadlock)是并发系统中一个经典的问题。在 PostgreSQL 的语境下,死锁指的是两个或多个事务(Transaction)各自持有对方所需要的资源(如行锁、表锁),并且在等待对方释放资源的过程中形成循环依赖,导致所有事务都无法继续执行。

从锁的视角来看,死锁的形成需要满足四个必要条件:互斥条件、持有并等待条件、不可抢占条件以及循环等待条件。在 PostgreSQL 中,当两个事务以交错的方式获取同一组资源上的锁时,循环等待便可能发生。

死锁的预防、检测与恢复

PostgreSQL 对死锁的处理采用了一套“预防—检测—恢复”的完整机制。

死锁预防是一种事前手段,通过设计上的约束来保证系统不会进入死锁状态。例如,强制所有事务以相同的顺序获取锁。但预防措施往往伴随着性能开销(如排队等待),在锁冲突不严重的场景下可能造成不必要的资源浪费。

死锁检测是 PostgreSQL 的核心机制。由于死锁检测的开销相对较大,服务器并不会在每次等待锁时都执行检测。相反,PostgreSQL 会乐观地假设生产环境中死锁并不常见,在等待锁一段时间后才进行检查。这个等待时间由 deadlock_timeout 参数控制:

-- 查看当前 deadlock_timeout 设置(默认值为 1s)
SHOW deadlock_timeout;

-- 设置 deadlock_timeout(单位:毫秒)
SET deadlock_timeout = '2s';

deadlock_timeout 的默认值为 1 秒(1s),这通常是实践中最小的推荐值。在负载较高的服务器上,可以考虑适当增大该值。理想情况下,该设置应超过典型的事务执行时间,以提高锁在被等待者决定检查死锁之前得到释放的概率。当 log_lock_waits 参数启用时,deadlock_timeout 还决定了记录锁等待日志消息的阈值时间。

死锁恢复是检测到死锁后的处理动作。PostgreSQL 会选择一个事务作为牺牲者(Victim)予以终止(Abort),并回滚其所有操作,从而打破循环等待,让其他事务得以继续执行。

单机环境下的死锁复现

以下示例演示了在一个单机 PostgreSQL 实例中,两个事务因交错更新同一张表的不同行而引发死锁。

准备测试数据

DROP TABLE IF EXISTS t_lock_test;
CREATE TABLE t_lock_test (id INT PRIMARY KEY);
INSERT INTO t_lock_test VALUES (1), (2);

事务一(Session 1)

BEGIN;
UPDATE t_lock_test SET id = 99 WHERE id = 1;
-- 持有 id=1 的行锁,暂不提交

事务二(Session 2)

BEGIN;
UPDATE t_lock_test SET id = 100 WHERE id = 2;
-- 持有 id=2 的行锁,暂不提交

此时,事务一持有行 id=1 的锁,事务二持有行 id=2 的锁。接下来,两个事务尝试获取对方已持有的锁:

事务一(Session 1)继续执行

UPDATE t_lock_test SET id = 101 WHERE id = 2;
-- 等待事务二释放 id=2 的行锁

事务二(Session 2)继续执行

UPDATE t_lock_test SET id = 102 WHERE id = 1;
-- 等待事务一释放 id=1 的行锁

此时,事务一等待事务二,事务二等待事务一,形成了循环等待。PostgreSQL 的死锁检测器在 deadlock_timeout 超时后被唤醒,检测到死锁并终止其中一个事务。服务器日志中会出现类似以下的提示:

ERROR: deadlock detected
DETAIL: Process 12345 waits for ShareLock on transaction 67890; blocked by process 12346.
Process 12346 waits for ShareLock on transaction 67891; blocked by process 12345.
HINT: See server log for query details.
STATEMENT: UPDATE t_lock_test SET id = 102 WHERE id = 1;

死锁检测的牺牲者选择

大多数情况下,发起死锁检测的事务(即触发检测的那个事务)会被选为牺牲者并予以中断。但存在一个重要的特例:如果死锁循环链路中包含 autovacuum 进程,并且该进程正在执行防止事务 ID 回卷(Transaction ID Wraparound)的冻结操作(即 VACUUM FREEZE),PostgreSQL 会优先终止其他事务,以保全 autovacuum 进程。这是为了避免因事务 ID 回卷导致数据库只读或宕机的严重后果。

流复制场景下的死锁

死锁不仅会发生在单机环境中,在流复制(Streaming Replication)架构下同样可能出现。

在流复制中,备库(Standby)通过 startup 进程持续回放(Replay)从主库接收到的 WAL 日志。当主库执行需要获取 ACCESS EXCLUSIVE 锁的 DDL 操作(如 ALTER TABLE ADD COLUMN)时,备库的 startup 进程也会尝试获取同样的锁以回放该操作。

考虑以下场景:

  1. 主库对表 items 执行 ALTER TABLE ADD COLUMN,获取 ACCESS EXCLUSIVE 锁。
  2. 备库上,用户执行 SELECT * FROM items,获取 ACCESS SHARE 锁。
  3. 备库的 startup 进程尝试回放主库的 ALTER TABLE 操作,需要获取 ACCESS EXCLUSIVE 锁,但被用户的 SELECT 查询阻塞。
  4. 此时,如果用户在备库上对另一张表执行查询,且该查询与 startup 进程正在等待的锁形成交叉依赖,死锁便可能发生。

这种场景下的死锁,本质上是备库的回放进程(startup)与用户查询进程之间,因锁的获取顺序不一致而产生的循环等待。

UPDATE 语句的锁获取顺序与死锁

UPDATE 语句在更新多行时,其行锁的获取是逐行进行的,而非一次性锁定所有目标行。这意味着,如果两个 UPDATE 语句以不同的顺序扫描并锁定同一组行,就可能产生死锁。

-- 创建测试表并插入数据
CREATE TABLE t_update_test (id INT PRIMARY KEY, val INT);
INSERT INTO t_update_test SELECT generate_series(1, 10000), 0;

-- 创建倒序索引(用于模拟不同的扫描顺序)
CREATE INDEX idx_t_update_test_id_desc ON t_update_test (id DESC);

事务一:顺序扫描(从前往后)

BEGIN;
UPDATE t_update_test SET val = 1 WHERE id BETWEEN 1 AND 10000;
-- 从 id=1 开始依次获取行锁

事务二:利用倒序索引扫描(从后往前)

BEGIN;
SET enable_seqscan = OFF;  -- 强制走索引扫描
UPDATE t_update_test SET val = 2 WHERE id BETWEEN 1 AND 10000;
-- 从 id=10000 开始依次获取行锁

两个事务以相反的顺序获取行锁,当它们交错执行时,便可能形成死锁。这个例子说明,即使是一个单独的 UPDATE 语句,如果其执行计划(顺序扫描 vs 索引扫描)导致锁获取顺序不同,也可能在并发环境下引发死锁。因此,不能简单地认为单个 SQL 语句是“原子”的,而需要关注其锁获取的具体行为。

加锁原则

基于对锁机制和死锁的理解,可以总结出以下加锁原则,以指导应用程序的设计:

够用即可

使用满足条件的最弱锁模式。例如:

  • 仅需读取数据时,使用普通的 SELECT(获取 ACCESS SHARE 锁),而非 SELECT FOR UPDATE 或 SELECT FOR SHARE。
  • 创建索引时,优先考虑 CREATE INDEX CONCURRENTLY(获取 SHARE UPDATE EXCLUSIVE 锁),而非普通的 CREATE INDEX(获取 SHARE 锁)。
-- 推荐:并发创建索引,允许并发读写
CREATE INDEX CONCURRENTLY idx_name ON table_name (column);

-- 不推荐:普通创建索引,会阻塞写操作
CREATE INDEX idx_name ON table_name (column);

越快越好

用“长时间的弱锁 + 短时间的强锁”替代“长时间的强锁”。例如,pg_repack 工具的核心原理就是通过短暂持有 ACCESS EXCLUSIVE 锁来完成表的重建,其余时间仅持有弱锁,从而降低对业务的冲击。

递增获取

避免使用激进的锁模式。仅在必要时才升级锁级别,不需要时尽早释放。

相同顺序

在多个事务中,以一致的顺序获取锁资源。从前面的死锁复现示例可以看出,事务交错运行(即锁获取顺序不一致)是死锁的根本原因。无论是单机环境下的行级锁,还是流复制环境下的表级锁,遵循相同的锁获取顺序都是预防死锁最有效的手段。

API 速览

deadlock_timeout

  • 所属:PostgreSQL 服务器配置参数(GUC)
  • 类型:integer(毫秒)
  • 默认值:1000(1秒)
  • 作用域:全局 / 会话 / 事务
  • 权限:仅超级用户和具有适当 SET 权限的用户可更改
  • 说明:等待锁之后、检查死锁条件之前的等待时间。死锁检查开销较大,因此服务器不会在每次等待锁时都执行。
-- 查看当前值
SHOW deadlock_timeout;

-- 会话级设置
SET deadlock_timeout = '2s';

-- 全局设置(需重启或 reload)
ALTER SYSTEM SET deadlock_timeout = '2s';
SELECT pg_reload_conf();

log_lock_waits

  • 所属:PostgreSQL 服务器配置参数(GUC)
  • 类型:boolean
  • 默认值:off
  • 说明:当设置为 on 时,任何等待锁超过 deadlock_timeout 时间的操作都会被记录到日志中。
-- 启用锁等待日志
SET log_lock_waits = on;

pg_locks 系统视图

  • 所属:PostgreSQL 系统目录
  • 说明:提供当前数据库服务器中所有未完成锁的列表。可用于监控锁等待和死锁诊断。
-- 查看当前所有未授予的锁(即等待中的锁)
SELECT * FROM pg_locks WHERE NOT granted;

-- 查看锁等待关系
SELECT blocked.pid AS blocked_pid,
       blocking.pid AS blocking_pid,
       blocked.query AS blocked_query
FROM pg_locks blocked
JOIN pg_locks blocking ON blocked.locktype = blocking.locktype
  AND blocked.relation = blocking.relation
  AND blocked.page = blocking.page
  AND blocked.tuple = blocking.tuple
  AND blocked.virtualxid = blocking.virtualxid
  AND blocked.transactionid = blocking.transactionid
  AND blocked.classid = blocking.classid
  AND blocked.objid = blocking.objid
  AND blocked.objsubid = blocking.objsubid
WHERE NOT blocked.granted
  AND blocking.granted;

LOCK 命令

  • 所属:PostgreSQL SQL 命令
  • 语法:LOCK [TABLE] name [, ...] [IN lockmode MODE] [NOWAIT]
  • 说明:显式获取表级锁。支持 ACCESS SHARE、ROW SHARE、ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE、ACCESS EXCLUSIVE 等模式。
-- 以 ACCESS EXCLUSIVE 模式锁定表,若无法立即获取则报错而非等待
LOCK TABLE my_table IN ACCESS EXCLUSIVE MODE NOWAIT;

Demo 简单示例

以下是一个使用 Node.js 和 pg 驱动演示死锁检测与处理的完整示例。

运行说明

  1. 确保本地 PostgreSQL 实例运行中,且已创建测试数据库。
  2. 安装依赖:npm install pg
  3. 执行脚本:node deadlock_demo.js

代码说明

该示例模拟了两个并发事务以相反的顺序更新同一张表的两行数据,从而触发死锁。代码展示了:

  • 使用 pg 连接池管理数据库连接
  • 并发执行两个事务
  • 捕获并处理 deadlock detected 错误
  • 事务失败后的回滚与重试逻辑

技术点总结

  • PostgreSQL 行级锁的获取与释放机制
  • deadlock_timeout 对死锁检测时机的影响
  • 死锁错误的捕获与业务层重试策略
  • 事务隔离级别与锁的交互
import { Pool } from 'pg';

const pool = new Pool({
  host: 'localhost',
  port: 5432,
  database: 'testdb',
  user: 'postgres',
  password: 'postgres',
  max: 10,
});

async function setup() {
  const client = await pool.connect();
  await client.query(`
    DROP TABLE IF EXISTS t_demo;
    CREATE TABLE t_demo (id INT PRIMARY KEY, val INT);
    INSERT INTO t_demo VALUES (1, 0), (2, 0);
  `);
  client.release();
}

async function runTransaction(
  client: any,
  updateId: number,
  waitId: number,
  txName: string
): Promise<void> {
  try {
    await client.query('BEGIN');

    // 更新当前行,持有行锁
    await client.query('UPDATE t_demo SET val = val + 1 WHERE id = $1', [updateId]);
    console.log(`[${txName}] 已更新 id=${updateId},持有行锁`);

    // 模拟业务处理延迟,增加死锁概率
    await new Promise((resolve) => setTimeout(resolve, 500));

    // 尝试更新另一行,可能被阻塞
    console.log(`[${txName}] 尝试更新 id=${waitId},等待行锁...`);
    await client.query('UPDATE t_demo SET val = val + 1 WHERE id = $1', [waitId]);
    console.log(`[${txName}] 成功更新 id=${waitId}`);

    await client.query('COMMIT');
    console.log(`[${txName}] 事务提交成功`);
  } catch (err: any) {
    await client.query('ROLLBACK');
    if (err.code === '40P01') {
      // 40P01 是 PostgreSQL 的死锁错误代码
      console.log(`[${txName}] 检测到死锁,事务已回滚: ${err.message}`);
    } else {
      console.log(`[${txName}] 事务失败: ${err.message}`);
    }
    throw err;
  }
}

async function main() {
  await setup();

  const client1 = await pool.connect();
  const client2 = await pool.connect();

  // 事务一:先更新 id=1,再尝试更新 id=2
  // 事务二:先更新 id=2,再尝试更新 id=1
  // 两个事务以相反的顺序获取锁,必然产生死锁
  const p1 = runTransaction(client1, 1, 2, 'Tx1');
  const p2 = runTransaction(client2, 2, 1, 'Tx2');

  await Promise.allSettled([p1, p2]);

  client1.release();
  client2.release();
  await pool.end();
}

main().catch(console.error);

对应的 PostgreSQL 原生 SQL 操作(供参考):

-- Session 1
BEGIN;
UPDATE t_demo SET val = val + 1 WHERE id = 1;
-- 等待 500ms
UPDATE t_demo SET val = val + 1 WHERE id = 2;
COMMIT;

-- Session 2
BEGIN;
UPDATE t_demo SET val = val + 1 WHERE id = 2;
-- 等待 500ms
UPDATE t_demo SET val = val + 1 WHERE id = 1;
COMMIT;

多语言示例

Go 示例

使用 lib/pq 驱动(或 pgx)演示死锁检测与重试。

package main

import (
    "database/sql"
    "fmt"
    "log"
    "sync"
    "time"

    _ "github.com/lib/pq"
)

const connStr = "postgres://postgres:postgres@localhost/testdb?sslmode=disable"

func setup(db *sql.DB) error {
    _, err := db.Exec(`
        DROP TABLE IF EXISTS t_demo;
        CREATE TABLE t_demo (id INT PRIMARY KEY, val INT);
        INSERT INTO t_demo VALUES (1, 0), (2, 0);
    `)
    return err
}

func runTransaction(db *sql.DB, updateId, waitId int, txName string, wg *sync.WaitGroup) {
    defer wg.Done()

    tx, err := db.Begin()
    if err != nil {
        log.Printf("[%s] 开启事务失败: %v", txName, err)
        return
    }
    defer tx.Rollback()

    // 更新当前行,持有行锁
    _, err = tx.Exec("UPDATE t_demo SET val = val + 1 WHERE id = $1", updateId)
    if err != nil {
        log.Printf("[%s] 更新 id=%d 失败: %v", txName, updateId, err)
        return
    }
    log.Printf("[%s] 已更新 id=%d,持有行锁", txName, updateId)

    // 模拟业务延迟
    time.Sleep(500 * time.Millisecond)

    // 尝试更新另一行
    log.Printf("[%s] 尝试更新 id=%d,等待行锁...", txName, waitId)
    _, err = tx.Exec("UPDATE t_demo SET val = val + 1 WHERE id = $1", waitId)
    if err != nil {
        // 检查是否为死锁错误(PostgreSQL 错误码 40P01)
        if pgErr, ok := err.(*pq.Error); ok && pgErr.Code == "40P01" {
            log.Printf("[%s] 检测到死锁,事务已回滚: %v", txName, err)
        } else {
            log.Printf("[%s] 更新失败: %v", txName, err)
        }
        return
    }
    log.Printf("[%s] 成功更新 id=%d", txName, waitId)

    if err = tx.Commit(); err != nil {
        log.Printf("[%s] 提交事务失败: %v", txName, err)
        return
    }
    log.Printf("[%s] 事务提交成功", txName)
}

func main() {
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    if err := setup(db); err != nil {
        log.Fatal(err)
    }

    var wg sync.WaitGroup
    wg.Add(2)

    // 事务一:先更新 id=1,再尝试 id=2
    go runTransaction(db, 1, 2, "Tx1", &wg)
    // 事务二:先更新 id=2,再尝试 id=1
    go runTransaction(db, 2, 1, "Tx2", &wg)

    wg.Wait()
    log.Println("所有事务执行完毕")
}

运行说明:

  • 安装依赖:go get github.com/lib/pq
  • 运行:go run deadlock_demo.go

Python 示例

使用 psycopg2 驱动。

import psycopg2
import psycopg2.extensions
import time
import threading

conn_str = "dbname=testdb user=postgres password=postgres host=localhost"

def setup():
    conn = psycopg2.connect(conn_str)
    conn.autocommit = True
    cur = conn.cursor()
    cur.execute("""
        DROP TABLE IF EXISTS t_demo;
        CREATE TABLE t_demo (id INT PRIMARY KEY, val INT);
        INSERT INTO t_demo VALUES (1, 0), (2, 0);
    """)
    cur.close()
    conn.close()

def run_transaction(update_id, wait_id, tx_name):
    conn = psycopg2.connect(conn_str)
    conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)  # 实际手动管理事务
    conn.autocommit = False
    cur = conn.cursor()
    try:
        cur.execute("BEGIN;")
        cur.execute("UPDATE t_demo SET val = val + 1 WHERE id = %s;", (update_id,))
        print(f"[{tx_name}] 已更新 id={update_id},持有行锁")
        time.sleep(0.5)
        print(f"[{tx_name}] 尝试更新 id={wait_id},等待行锁...")
        cur.execute("UPDATE t_demo SET val = val + 1 WHERE id = %s;", (wait_id,))
        print(f"[{tx_name}] 成功更新 id={wait_id}")
        conn.commit()
        print(f"[{tx_name}] 事务提交成功")
    except psycopg2.errors.DeadlockDetected as e:
        conn.rollback()
        print(f"[{tx_name}] 检测到死锁,事务已回滚: {e}")
    except Exception as e:
        conn.rollback()
        print(f"[{tx_name}] 事务失败: {e}")
    finally:
        cur.close()
        conn.close()

if __name__ == "__main__":
    setup()
    t1 = threading.Thread(target=run_transaction, args=(1, 2, "Tx1"))
    t2 = threading.Thread(target=run_transaction, args=(2, 1, "Tx2"))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("所有事务执行完毕")

运行说明:

  • 安装依赖:pip install psycopg2-binary
  • 运行:python deadlock_demo.py

Java 示例

使用 JDBC 和 PostgreSQL 驱动。

import java.sql.*;
import java.util.concurrent.CountDownLatch;

public class DeadlockDemo {
    private static final String URL = "jdbc:postgresql://localhost/testdb";
    private static final String USER = "postgres";
    private static final String PASSWORD = "postgres";

    public static void setup() throws SQLException {
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             Statement stmt = conn.createStatement()) {
            stmt.execute("DROP TABLE IF EXISTS t_demo");
            stmt.execute("CREATE TABLE t_demo (id INT PRIMARY KEY, val INT)");
            stmt.execute("INSERT INTO t_demo VALUES (1, 0), (2, 0)");
        }
    }

    public static void runTransaction(int updateId, int waitId, String txName, CountDownLatch latch) {
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {
            conn.setAutoCommit(false);
            try (PreparedStatement pstmt1 = conn.prepareStatement("UPDATE t_demo SET val = val + 1 WHERE id = ?")) {
                pstmt1.setInt(1, updateId);
                pstmt1.executeUpdate();
                System.out.println("[" + txName + "] 已更新 id=" + updateId + ",持有行锁");

                Thread.sleep(500);

                System.out.println("[" + txName + "] 尝试更新 id=" + waitId + ",等待行锁...");
                try (PreparedStatement pstmt2 = conn.prepareStatement("UPDATE t_demo SET val = val + 1 WHERE id = ?")) {
                    pstmt2.setInt(1, waitId);
                    pstmt2.executeUpdate();
                    System.out.println("[" + txName + "] 成功更新 id=" + waitId);
                }

                conn.commit();
                System.out.println("[" + txName + "] 事务提交成功");
            } catch (SQLException e) {
                conn.rollback();
                // PostgreSQL 死锁错误码为 40P01
                if ("40P01".equals(e.getSQLState())) {
                    System.out.println("[" + txName + "] 检测到死锁,事务已回滚: " + e.getMessage());
                } else {
                    System.out.println("[" + txName + "] 事务失败: " + e.getMessage());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                conn.rollback();
                System.out.println("[" + txName + "] 事务被中断");
            }
        } catch (SQLException e) {
            System.out.println("[" + txName + "] 数据库连接失败: " + e.getMessage());
        } finally {
            latch.countDown();
        }
    }

    public static void main(String[] args) throws Exception {
        setup();

        CountDownLatch latch = new CountDownLatch(2);
        Thread t1 = new Thread(() -> runTransaction(1, 2, "Tx1", latch));
        Thread t2 = new Thread(() -> runTransaction(2, 1, "Tx2", latch));
        t1.start();
        t2.start();

        latch.await();
        System.out.println("所有事务执行完毕");
    }
}

运行说明:

  • 添加 PostgreSQL JDBC 驱动依赖(如 Maven: org.postgresql:postgresql:42.6.0)
  • 编译:javac DeadlockDemo.java
  • 运行:java DeadlockDemo

多语言对比表格

特性Node.js (pg)Go (lib/pq)Python (psycopg2)Java (JDBC)
驱动/库pggithub.com/lib/pqpsycopg2PostgreSQL JDBC Driver
连接池内置 Pool需自行实现或使用 pgx 连接池使用 psycopg2.pool 或第三方可使用 HikariCP 等
事务管理BEGIN/COMMIT/ROLLBACKBegin/Commit/Rollbackconn.autocommit=False 手动提交conn.setAutoCommit(false)
错误码判断err.code === '40P01'类型断言为 *pq.Error,检查 Code捕获 psycopg2.errors.DeadlockDetected检查 SQLException.getSQLState() 是否为 "40P01"
异步/并发async/await + Promise.allGoroutine + sync.WaitGroupthreading.ThreadThread + CountDownLatch
延迟模拟setTimeout + Promisetime.Sleeptime.sleepThread.sleep
错误处理try/catch错误返回或 logtry/excepttry/catch
依赖安装npm install pggo get github.com/lib/pqpip install psycopg2-binary通过 Maven/Gradle 添加
典型适用场景Node.js 后端服务高性能微服务、云原生数据科学、自动化脚本企业级应用、Spring 生态

所有示例均演示了相同的死锁触发逻辑(两个事务以相反顺序更新两行数据),并捕获了 PostgreSQL 的死锁错误码(40P01)进行相应处理。实际生产中,建议结合重试机制(如指数退避)和更完善的日志监控。

项目难点与解决方案

核心难点

在生产环境中,死锁的发生往往是偶发且难以复现的。由于并发事务的交错执行受到多种因素(如网络延迟、应用负载、查询执行计划变化等)的影响,死锁的复现和诊断具有较高的复杂度。

解决方案

  1. 启用 log_lock_waits:将 log_lock_waits 设置为 on,并配合合理的 deadlock_timeout 值,可以在日志中捕获锁等待和死锁的详细信息,便于事后分析。
  2. 监控 pg_locks 与 pg_stat_activity:定期查询锁等待关系,及时发现潜在的死锁风险。
  3. 应用层重试机制:在业务代码中捕获 40P01(死锁)错误码,实施有限次数的重试。
  4. 规范锁获取顺序:在应用设计层面,确保所有事务以相同的顺序访问数据库资源。

广度

死锁问题涉及 PostgreSQL 的锁管理器、事务管理器、WAL 日志、流复制等多个子系统,需要从单机到分布式架构的全局视角进行考量。

深度

死锁的诊断需要深入理解 PostgreSQL 的锁模式兼容性矩阵、行级锁与表级锁的交互、deadlock_timeout 与 log_lock_waits 的协同工作机制,以及 autovacuum 等后台进程的特殊处理逻辑。

复杂度

死锁问题的复杂度随着并发事务数量、涉及的表数量、锁模式多样性以及流复制架构的引入而呈指数级增长。在生产环境中,死锁的根因分析往往需要结合应用日志、数据库日志、系统视图快照等多维度数据进行综合判断。

官方文档

参考链接

总结

本文围绕 PostgreSQL 的死锁问题,从定义、检测机制、恢复策略到具体的复现案例进行了系统性的梳理。死锁的本质是事务间因资源竞争形成的循环等待,PostgreSQL 通过 deadlock_timeout 参数控制检测频率,并通过牺牲者选择机制打破死锁。

单机环境下的行级锁交错、流复制场景中备库回放进程与用户查询的冲突、以及 UPDATE 语句因执行计划不同导致的锁获取顺序差异,都是生产环境中常见的死锁诱因。遵循“够用即可、越快越好、递增获取、相同顺序”的加锁原则,结合 log_lock_waits 日志监控与应用程序层的重试机制,是应对死锁问题的有效实践。

Lease机制:云原生多实例任务协调的轻量级解决方案 在分布式系统中,多实例环境下资源归属权的确定是保障任务正确执行的基础前提。Lease作为一种带TTL的‘工作许可证’机制,通过乐观授权与悲观失效的设计,规避了传统锁、心跳和临时节点方案在动态伸缩、网络抖动与时钟漂移场景下的固有缺陷。其核心原理在于由协调服务(如Consul)签发具备明确过期时间的租约凭证,由实例主动续期,超时即自动释放,从而实现低延迟、高可用的任务接管控制。该机制尤其适用于PostgreSQL只读副本定时任务调度、Redis分布式锁优化及Consul服务网格中的健康协同等典型云原生场景,是构 阅读详情

相关推荐

Python运行时真相:字节码、GIL与内存的硬核解剖

Python常被笼统归类为‘解释型动态语言’,但其真实执行模型是源码编译为字节码后由Python虚拟机(PVM)解释执行——这决定了性能瓶颈不在语法层,而在CPython运行时开销。GIL并非线程安全的保障,而是一个保护字节码原子执行的互斥锁,对用户数据、C扩展及外部资源完全无约束;内存管理也远非‘全自动’,引用计数、分代回收与pymalloc内存池共同构成易被误判的复杂系统。理解字节码指令(如LOAD_FAST vs LOAD_GLOBAL)、GIL在pthread_mutex_t层面的物理存在,以及Py

dfu65065的博客 412

PostgreSQL诊断系列(2/6):锁问题排查全攻略——揪出“阻塞元凶”

PostgreSQL诊断系列(2/6):锁问题排查全攻略——揪出“阻塞元凶”

SmartDB开源项目作者:https://github.com/wenb1n-dev/SmartDB_MCP 1244

从CMU数据库神课到实战:构建数据库系统的完整学习路径与核心原理剖析

数据库系统是现代软件架构的基石,其核心在于高效、可靠地管理数据。理解其工作原理,需要从存储引擎、索引、查询优化等基础概念入手。数据库通过缓冲池管理内存与磁盘的交互,利用B+树等索引结构加速数据检索,并基于火山模型执行查询计划。这些技术的价值在于,它们共同保障了数据的一致性、持久性与高并发访问能力,支撑着从在线交易到数据分析的各类应用场景。本文围绕CMU 15-445/645这门经典课程,结合B+树索引实现与VSCode开发实践,为你梳理出一条从理论到实战的清晰学习路径,帮助你深入掌握数据库内核的设计精髓与工

weixin_33797791的博客 454

pgsql与oracle查询死锁和处理死锁

今天工作用postgresql数据库,遇到了死锁情况,干脆写个博客记录下查询和处理死锁的语句,以便自己后期工作使用。

m0_71559454的博客 2265

计算机核心课程体系化复习:从数据库索引到网络协议,构建底层知识图谱

在计算机科学领域,理解核心系统原理是构建扎实技术根基的关键。数据库系统通过索引(如B+树)、事务(ACID)和查询优化等机制,在数据持久化、一致性与性能之间进行精妙权衡。操作系统作为资源管理者,通过进程调度、虚拟内存和文件系统抽象硬件,确保系统高效稳定运行。计算机网络则依赖分层协议栈(如TCP/IP),在应用层(HTTP/3)、传输层(可靠传输)和网络层(路由寻址)协同工作,实现端到端通信。这些基础概念共同构成了计算机系统的骨架,其原理广泛应用于后端开发、分布式系统和高性能计算等场景。本文以数据库B+树索引

weixin_34025151的博客 632

MySQL实战进阶:从安装配置到性能优化的数据库工程师心法

数据库作为现代应用的核心数据存储与管理系统,其核心价值在于高效、安全、可靠地管理结构化数据。其工作原理基于客户端-服务器模型,通过SQL语言进行交互,并利用存储引擎(如InnoDB)实现数据的持久化与事务处理。掌握数据库技术对于构建高性能、可扩展的应用系统至关重要,广泛应用于电商、金融、内容管理等各类在线业务场景。本文聚焦于MySQL这一主流关系型数据库,深入探讨其生产环境部署的核心要点。其中,**my.cnf配置文件**的调优是性能基石,涉及内存缓冲池、日志刷写策略等关键参数设置;而**主从复制(Repl

weixin_30527323的博客 480

Wan2GP 低显存 AI 视频生成部署指南:6GB 显卡跑通全流程

Wan2GP 是一款免费开源、带 Web 界面的 AI 视频生成工具,集成 Wan 2.1/2.2、LTX-2、Hunyuan Video、Qwen Image、Flux 等主流模型,支持 LoRA 自定义和批量队列,部分模型 6GB 显存即可运行。这份指南按"先跑通 → 再提速 → 出错了查"的顺序展开,环境准备直接嵌入到对应步骤里,跟着做就能在老显卡上跑出生成流程。 ## 先对硬件:你的机器

gitblog_00302的博客 379

Linux进程管理三剑客:ps、kill、nice原理与实战

进程管理是操作系统核心能力之一,本质是内核对任务调度、资源分配和信号交互的统一控制。其底层依赖/proc文件系统快照读取、信号机制(如SIGTERM/SIGKILL)及CFS调度器的vruntime权重算法。技术价值在于实现资源可见性、可控终止与动态优先级调节,支撑高可用服务巡检、CPU/MEM异常定位、容器化环境治理等关键场景。本文聚焦ps命令参数组合优化、kill信号语义辨析与nice值对CPU时间片的实际影响,结合生产级故障案例,揭示Linux进程管理的真实逻辑。

adiking520110的博客 452

MySQL实战进阶:从安装配置到性能优化的系统化学习路径

你是不是也遇到过这样的场景:刚接手一个项目,数据库里一堆表,想查个数据却不知道从何下手;或者面试时被问到“MySQL的索引是怎么工作的”,只能含糊其辞地说“B+树”,再追问细节就卡壳了。又或者,照着网上的教程装好了MySQL,但一遇到“ERROR 1045 (28000): Access denied for user”这样的报错,就感觉像面对一堵墙,完全不知道下一步该往哪里走。 这太正常了。MySQL作为世界上最流行的开源关系型数据库,它的“入门”门槛看似很低——下载、安装、敲几个 SELECT 、 IN

weixin_30239339的博客 316

AutoGen生产级改造:Stateful Agent与结构化工具总线实战

多智能体系统(MAS)是构建企业级AI工作流的核心范式,其本质是通过Agent间协同完成复杂任务编排。AutoGen作为轻量级多Agent框架,原生设计聚焦研究可复现性,但在真实业务场景中面临状态丢失、工具返回非结构化、流程终止不可控等典型问题。本文基于金融、制造、生物医药等7个落地项目经验,深入解析Stateful Agent内存持久化机制与Structured Tool Bus标准化协议,揭示如何将LLM驱动的Agent升级为可审计、可回滚、可集成Jira/Confluence/ERP的生产级工作流节点

weixin_30292843的博客 417

C++实现高并发网盘后端:从Epoll到SQLite的完整架构与核心代码

网络编程与高并发服务器开发是构建现代分布式系统的基石,其核心在于高效处理海量连接与数据I/O。从原理上讲,这通常涉及I/O多路复用、非阻塞I/O、事件驱动等关键技术,旨在最大化单机资源利用率,支撑高吞吐、低延迟的服务。在工程实践中,选择C++这类系统级语言,能够实现对内存、线程和文件操作的精细控制,从而在磁盘I/O和网络I/O密集型场景(如网盘、对象存储)中压榨硬件极限性能。具体到应用场景,一个典型的网盘后端需要解决多用户并发上传下载、大文件分片传输、元数据高效管理等挑战。本文聚焦于**C++网盘后端**的

weixin_34109408的博客 653

真实可落地的AI Agent系统架构与工程实践

AI Agent并非泛化的智能体概念,而是具备执行、协调、记忆与反思能力的可工程化系统。其核心原理在于分层解耦设计:执行层保障幂等与容错,协调层专注流程编排而非业务逻辑,记忆层采用结构化优先的混合策略,反思层以小模型守门降低风险。技术价值体现在稳定性提升、成本优化与可观测性增强;典型应用场景覆盖电商售后、医疗科研辅助与工业远程诊断等强规则、高可靠需求领域。本文基于三个行业真实落地案例,详解Agent四层架构、可观测性脊椎及高频故障根治方案。

weixin_33674437的博客 404

缓慢变化维(SCD)实战指南:Type 1/2/3/4选型、建模与ETL避坑

缓慢变化维(SCD)是数据仓库中管理会变但需追溯的业务主数据的核心机制,本质是通过结构化时间快照保障历史可解释性。其设计原理围绕‘用空间换时间、用结构换可信’展开,技术价值在于支撑精准归因、合规审计与多时点分析。典型应用场景包括客户归属变更、商品类目调整、员工组织变动等需关联历史事实的分析需求。本文基于真实生产环境,深度解析SCD四大类型的本质差异与成本权衡,重点覆盖Type 2代理键设计、生效时间建模、并发ETL原子性保障,以及混合SCD模式下的字段级隔离实践。

weixin_33738982的博客 428

MySQL实战内核手记:ACID、隔离级别与索引优化真相

MySQL的ACID特性并非抽象理论,而是由Undo Log、Redo Log、MVCC和B+树索引共同支撑的工程实践体系。原子性依赖Undo Log的链式回滚机制,持久性依托WAL日志先行策略,隔离性体现为MVCC快照与锁(记录锁/间隙锁/Next-Key Lock)的动态协同,而索引性能则直接受最左前缀、选择度和执行计划影响。这些机制共同决定了高并发下事务稳定性、查询响应速度与系统吞吐能力,在电商、金融等实时性敏感场景中尤为关键。本文聚焦线上真实故障根因,解析隔离级别选型逻辑、索引失效陷阱及视图执行算法

weixin_34389926的博客 346

GLM-5.1代码能力跃迁:从SWE-Bench Pro登顶看大模型工程化落地

大语言模型的代码能力已超越基础语法生成,进入软件工程深度理解阶段。其核心在于对真实开发闭环(Bug定位→上下文分析→修复验证→CI通过)的建模能力,而非单纯参数规模或训练数据量。关键技术支撑包括AST感知编码、动态上下文管理与可验证思维链输出,显著提升日志语义解析、类型系统推理和测试驱动生成等硬性指标。在SWE-Bench Pro等高难度工程基准上取得突破,标志着模型正从‘能写代码’迈向‘像工程师一样思考’。这一演进直接赋能医疗HIS重构、开源项目贡献、企业级AI编程助手等典型场景,尤其适合需将AI深度嵌入

weixin_33860553的博客 344

Kimi K2.6 vs GLM-5.1实战对比:AI编程助手如何选型落地

AI编程助手正从概念验证走向工程化落地,其核心能力围绕代码生成、工具调用与调试闭环展开。理解模型在真实GitHub Issue修复中的表现,需回归到基础技术维度:参数激活效率决定推理延迟与API成本,MoE路由机制影响单文件调试精度,而MLA稀疏注意力则关乎跨文件规划能力。本文基于15个2026年真实关闭的工单,深度对比Kimi K2.6与GLM-5.1在Pydantic v2兼容性、Rust生命周期建模、Go数据竞争定位等关键场景的表现,揭示‘上下文长度’与‘实际可用token激活量’的本质差异,并给出C

ciqiaofu0192的博客 483

AI Agent数据库选型:向量检索、会话状态与联邦查询实战对比

AI Agent应用对数据库提出全新要求——它不再是传统OLTP的单一事务处理,而是融合向量相似度检索、实时会话状态同步、多源异构数据联邦查询的混合负载系统。其核心原理在于:高维向量ANN搜索需突破B+树索引限制,会话状态管理依赖分布式事务的低延迟强一致,而联邦查询则考验SQL下推与跨协议集成能力。技术价值体现在RAG端到端延迟降低、百万级Agent并发不丢上下文、实时决策链路免ETL。典型应用场景包括智能客服、投顾助手、电商导购等需语义理解+多跳推理的Agent系统。本文聚焦PolarDB、Aurora、

weixin_33937913的博客 287

从零实现工业级数据库内核:存储引擎、事务与查询优化实战

关系型数据库内核是计算机系统能力的核心体现,其本质涵盖存储引擎设计、ACID事务管理与基于代价的查询优化三大支柱。理解B+树索引的并发状态机实现、WAL日志的原子性保障机制、两阶段锁的死锁检测策略,以及如何用实测I/O与CPU周期构建可验证的代价模型,是掌握数据库原理的关键路径。这些技术不仅支撑TPC-C等工业级基准测试,更广泛应用于OLTP系统开发、数据库教学框架定制及系统能力竞赛备赛。本文以一个可运行、可复现、通过TPC-C验证的自研RMDB框架为载体,深入剖析缓冲池管理、事务状态跟踪与执行计划动态调优

weixin_34037173的博客 318
上一篇: PostgreSQL笔记31:咨询锁(Advisory Lock)在高并发场景下的原理与实践
下一篇: PostgreSQL笔记33: 索引底层原理、扫描类型与属性体系深度解析
Wang's Blog
博客等级 码龄12年 7438粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值