PostgreSQL笔记29:事务隔离级别深度解析——从MVCC快照到SSI可串行化

PostgreSQL没有Gap Lock如何防幻读?MVCC与SSI机制深度解析 在数据库并发控制领域,PostgreSQL与MySQL InnoDB采用了截然不同的实现路径。InnoDB依靠Next-Key Lock(间隙锁+记录锁)来阻止幻读,而PostgreSQL则通过MVCC(多版本并发控制)的快照机制,让新提交的“幻影行”对当前事务不可见,从而在逻辑层面规避了标准幻读问题。理解这一差异,需要穿透表层的锁概念,深入xmin/xmax、ReadView可见性判断、事务快照边界等底层原理。PostgreSQL的设计哲学是“用事后检测代替事前阻塞”,这带来了更高的并发写入吞吐和更低的死 阅读详情

纲要

  • 事务快照(Transaction Snapshot)
    • 快照的组成:xmin、xmax、xip_list
    • 快照查看函数:pg_current_snapshot()(PG 13+)、txid_current_snapshot()(已废弃)
    • 快照的可见性判定规则
  • SQL标准定义的并发异常
    • 脏读(Dirty Read)
    • 不可重复读(Nonrepeatable Read)
    • 幻读(Phantom Read)
    • 序列化异常(Serialization Anomaly)
    • 更新丢失(Lost Update)
    • 读偏序(Read Skew)与写偏序(Write Skew)
  • PostgreSQL支持的隔离级别
    • READ COMMITTED(默认)
    • REPEATABLE READ
    • SERIALIZABLE
    • READ UNCOMMITTED 的行为等价于 READ COMMITTED
  • 各隔离级别下的异常对照表
  • 更新丢失的深入分析
    • READ COMMITTED 下应用层更新丢失的成因与复现
    • REPEATABLE READ 下更新冲突的处理
  • 可串行化快照隔离(SSI)
    • 谓词锁(Predicate Lock / SIREAD Lock)
    • 序列化失败的处理

事务快照:MVCC的基石

PostgreSQL通过多版本并发控制(MVCC)实现事务隔离,其核心机制是事务快照(Transaction Snapshot)。快照记录了在某一时刻所有事务的活动状态,每个事务根据自己所持有的快照来判断哪些数据版本可见。

快照的组成

快照的文本表示格式为 xmin:xmax:xip_list:

  • xmin:当前仍处于活跃状态的最早事务ID。所有小于 xmin 的事务,要么已提交(可见),要么已回滚(不可见)。
  • xmax:第一个尚未被分配的事务ID。所有大于等于 xmax 的事务ID,在快照时刻尚未启动,因此不可见。
  • xip_list:快照时刻活跃事务ID列表,仅包含 xmin 与 xmax 之间的活跃事务。

例如,快照 100:104:100,102 表示:

  • xmin = 100:事务ID小于100的均非活跃
  • xmax = 104:事务ID大于等于104的尚未启动
  • xip_list = 100,102:事务100和102在快照时刻处于活跃状态

查看当前快照

PostgreSQL提供了查看当前事务快照的函数:

-- PostgreSQL 13+ 推荐使用
SELECT pg_current_snapshot();

-- PostgreSQL 12及以下版本使用(已废弃)
SELECT txid_current_snapshot();

pg_current_snapshot() 函数返回 pg_snapshot 类型,自PostgreSQL 13起引入,替代了原有的 txid_current_snapshot()。

postgres=# SELECT pg_current_snapshot();
 pg_current_snapshot 
---------------------
 795:799:795,797
(1 row)

快照获取时机

不同隔离级别下,快照的获取策略不同:

  • READ COMMITTED:每个SQL命令执行时重新获取一次快照
  • REPEATABLE READ 和 SERIALIZABLE:事务中第一个SQL命令执行时获取快照,整个事务期间复用该快照

这一差异直接决定了不同隔离级别下可见性行为的不同。

SQL标准定义的并发异常

SQL标准定义了多种并发事务可能引发的异常现象:

异常类型描述
脏读(Dirty Read)一个事务读取了另一个事务尚未提交的数据
不可重复读(Nonrepeatable Read)同一事务内两次查询返回不同的结果集(因其他事务提交了更改)
幻读(Phantom Read)同一事务内两次查询返回不同的行集合(因其他事务插入了满足条件的新行)
序列化异常(Serialization Anomaly)并发执行的结果不等同于任何串行执行顺序的结果

此外,实践中还需关注:

  • 更新丢失(Lost Update):两个事务同时读取并更新同一行,后提交的事务覆盖了先提交事务的更改
  • 读偏序(Read Skew):事务读取了不一致的数据状态(如账户余额汇总时部分账户已更新)
  • 写偏序(Write Skew):两个事务各自基于读取的数据做出写入决策,整体结果不一致

PostgreSQL的隔离级别

PostgreSQL内部仅实现三种隔离级别:

隔离级别脏读不可重复读幻读序列化异常
READ UNCOMMITTED不可能(PG实现)可能可能可能
READ COMMITTED不可能可能可能可能
REPEATABLE READ不可能不可能不可能(PG实现)可能
SERIALIZABLE不可能不可能不可能不可能

关键要点:

  1. READ UNCOMMITTED 在PostgreSQL中的行为等价于 READ COMMITTED——虽然语法层面支持,但实际效果相同。
  2. PostgreSQL的 REPEATABLE READ 不允许幻读,这比SQL标准的要求更为严格(标准允许幻读)。
  3. READ COMMITTED 是PostgreSQL的默认隔离级别。

设置事务隔离级别的语法:

-- 方式一:BEGIN时指定
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 方式二:BEGIN后使用SET TRANSACTION
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 方式三:设置会话默认值
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;

版本说明:SET SESSION CHARACTERISTICS 和 BEGIN TRANSACTION ISOLATION LEVEL 语法在所有受支持的PostgreSQL版本中均可用。

各隔离级别行为详解

READ COMMITTED(读已提交)

READ COMMITTED 是默认隔离级别。每个查询开始时获取一次快照,因此:

  • 不会出现脏读:查询只能看到查询开始前已提交的数据
  • 会出现不可重复读:两次查询之间,其他事务可能提交更改,导致两次结果不同
  • 会出现幻读:其他事务插入的新行可能在后续查询中可见
  • 会出现序列化异常

UPDATE、DELETE、SELECT FOR UPDATE 等命令在搜索目标行时,只会找到命令开始时已提交的行。如果目标行被其他并发事务修改,当前事务会等待该事务提交或回滚。

REPEATABLE READ(可重复读)

事务在第一个命令执行时获取快照并在整个事务期间复用:

  • 不会出现不可重复读:整个事务使用同一快照,两次查询结果一致
  • 不会出现幻读(PostgreSQL实现比标准更严格)
  • 可能出现序列化异常
  • 更新冲突时抛出错误:ERROR: could not serialize access due to concurrent update

SERIALIZABLE(可串行化)

最严格的隔离级别:

  • 杜绝所有SQL标准定义的异常
  • 基于可串行化快照隔离(Serializable Snapshot Isolation, SSI) 实现
  • 检测到序列化冲突时,终止其中一个事务并返回 serialization_failure 错误
  • 性能开销最大,系统吞吐量显著下降
  • 热备(Hot Standby)备库不支持 SERIALIZABLE 隔离级别

更新丢失(Lost Update)深度分析

更新丢失是应用层面需要特别关注的问题。PostgreSQL在 READ COMMITTED 隔离级别下无法自动预防应用层的更新丢失。

READ COMMITTED下的更新丢失复现

-- 准备测试表
CREATE TABLE accounts (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    amount INT
);

INSERT INTO accounts VALUES (1, 'alice', 1000);

事务T1(窗口1):

BEGIN;  -- 默认 READ COMMITTED

-- 查询alice余额
SELECT amount FROM accounts WHERE id = 1;  -- 返回 1000

-- 应用层计算:1000 + 100 = 1100
UPDATE accounts SET amount = 1100 WHERE id = 1;

COMMIT;

事务T2(窗口2)——并发执行:

BEGIN;  -- 默认 READ COMMITTED

-- 查询alice余额(在T1提交前执行,返回1000)
SELECT amount FROM accounts WHERE id = 1;  -- 返回 1000

-- 应用层计算:1000 + 100 = 1100
-- 该UPDATE会被T1的行锁阻塞,直到T1提交
UPDATE accounts SET amount = 1100 WHERE id = 1;

COMMIT;

最终结果:alice 的余额变为 1100,而非预期的 1200。两次各加100的操作,丢失了100。

问题成因

PostgreSQL无法推断应用层 1000 这个值与 UPDATE 语句中 1100 的关联关系。在 READ COMMITTED 下,T2的UPDATE被阻塞等待T1提交后,会重新获取快照并读取T1提交后的最新值(1100),然后在此基础上应用 SET amount = 1100,导致T1的更新被覆盖。

正确做法:在UPDATE中直接计算

-- 正确:原子性地在原值基础上增加
BEGIN;
UPDATE accounts SET amount = amount + 100 WHERE id = 1;
COMMIT;

此时即使两个事务并发执行,行锁机制确保更新串行化,最终结果为1200。

REPEATABLE READ下的行为

-- 窗口1
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT amount FROM accounts WHERE id = 1;  -- 1000
-- 暂不提交

-- 窗口2
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT amount FROM accounts WHERE id = 1;  -- 1000
UPDATE accounts SET amount = 1100 WHERE id = 1;  -- 被阻塞

-- 窗口1提交后,窗口2收到错误
ERROR: could not serialize access due to concurrent update

在 REPEATABLE READ 下,PostgreSQL检测到更新冲突并主动报错,而非静默覆盖。

可串行化快照隔离(SSI)

PostgreSQL的 SERIALIZABLE 隔离级别基于可串行化快照隔离(Serializable Snapshot Isolation, SSI) 实现。

谓词锁(Predicate Lock / SIREAD Lock)

SSI的核心机制是谓词锁(SIREAD锁):

  • 与传统的读写锁不同,SIREAD锁不阻塞写操作
  • 其作用是跟踪事务间的读写依赖关系(rw-conflict edges)
  • 当检测到依赖图中存在环(即无法串行化),PostgreSQL终止其中一个事务

序列化失败处理

应用程序在使用 SERIALIZABLE 隔离级别时,必须处理 serialization_failure 错误并重试事务:

-- 应用层重试逻辑(伪代码)
WHILE TRUE:
    BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    TRY:
        -- 执行业务操作
        COMMIT;
        BREAK;
    EXCEPT serialization_failure:
        ROLLBACK;
        -- 等待后重试
        SLEEP(random_delay);
        CONTINUE;

版本说明

  • PostgreSQL 9.1 之前,SERIALIZABLE 级别实际上等价于快照隔离(Snapshot Isolation),而非真正的可串行化
  • PostgreSQL 9.1 起引入SSI,提供了完整的可串行化保证
  • pg_current_snapshot() 自 PostgreSQL 13 起可用

API 速览

1. pg_current_snapshot()

  • 所属库:系统函数
  • 方法签名:pg_current_snapshot() → pg_snapshot
  • 描述:返回当前事务的快照
  • 引入版本:PostgreSQL 13
  • 返回值:pg_snapshot 类型,文本格式为 xmin:xmax:xip_list
SELECT pg_current_snapshot();
-- 输出示例: 795:799:795,797

2. txid_current_snapshot()(已废弃)

  • 所属库:系统函数
  • 方法签名:txid_current_snapshot() → txid_snapshot
  • 描述:返回当前事务的快照(已废弃,推荐使用 pg_current_snapshot())
  • 适用版本:PostgreSQL 12及以下

3. SET TRANSACTION

  • 所属库:SQL命令
  • 语法:SET TRANSACTION transaction_mode [, ...]
  • 参数:ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
  • 描述:设置当前事务的特性
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 执行操作
COMMIT;

4. BEGIN 事务块

  • 所属库:SQL命令
  • 语法:BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ]
  • 参数:同 SET TRANSACTION
  • 描述:开始一个事务块
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 执行操作
COMMIT;

Demo 简单示例

以下是一个使用Node.js + pg 驱动演示各隔离级别行为的完整示例。

运行说明

  1. 确保PostgreSQL运行中,创建测试数据库
  2. 安装依赖:npm install pg
  3. 运行:node isolation_demo.js

代码

const { Client } = require('pg');

const config = {
    host: 'localhost',
    port: 5432,
    database: 'testdb',
    user: 'postgres',
    password: 'your_password'
};

// 对应的PostgreSQL原生指令:
// psql -d testdb -U postgres

async function setupTable(client) {
    await client.query(`
        DROP TABLE IF EXISTS accounts;
        CREATE TABLE accounts (
            id INT PRIMARY KEY,
            name VARCHAR(50),
            amount INT
        );
        INSERT INTO accounts VALUES (1, 'alice', 1000);
    `);
    console.log('[Setup] 表已创建,初始余额: 1000');
}

// Demo 1: READ COMMITTED 下的更新丢失
async function demoLostUpdateRC() {
    const client1 = new Client(config);
    const client2 = new Client(config);
    await client1.connect();
    await client2.connect();

    try {
        await setupTable(client1);

        // T1: 读取余额
        await client1.query('BEGIN');
        const res1 = await client1.query('SELECT amount FROM accounts WHERE id = 1');
        const balance1 = res1.rows[0].amount;
        console.log(`[T1] 读取余额: ${balance1}`);

        // T2: 并发读取(在T1提交前)
        await client2.query('BEGIN');
        const res2 = await client2.query('SELECT amount FROM accounts WHERE id = 1');
        const balance2 = res2.rows[0].amount;
        console.log(`[T2] 读取余额: ${balance2}`);

        // T1: 更新为 balance1 + 100
        await client1.query('UPDATE accounts SET amount = $1 WHERE id = 1', [balance1 + 100]);
        await client1.query('COMMIT');
        console.log(`[T1] 提交更新: ${balance1} -> ${balance1 + 100}`);

        // T2: 更新为 balance2 + 100(此时T1已提交)
        // 在READ COMMITTED下,T2会读取到T1提交后的新值,然后应用自己的更新
        await client2.query('UPDATE accounts SET amount = $1 WHERE id = 1', [balance2 + 100]);
        await client2.query('COMMIT');
        console.log(`[T2] 提交更新: ${balance2} -> ${balance2 + 100}`);

        const final = await client1.query('SELECT amount FROM accounts WHERE id = 1');
        console.log(`[结果] 最终余额: ${final.rows[0].amount} (预期: 1200, 实际: ${final.rows[0].amount})`);
        // 输出: 1100 —— 更新丢失!
    } finally {
        await client1.end();
        await client2.end();
    }
}

// Demo 2: 正确的原子更新
async function demoCorrectUpdate() {
    const client = new Client(config);
    await client.connect();

    try {
        await setupTable(client);

        // 使用原子操作: amount = amount + 100
        await client.query('BEGIN');
        await client.query('UPDATE accounts SET amount = amount + 100 WHERE id = 1');
        await client.query('COMMIT');

        const res = await client.query('SELECT amount FROM accounts WHERE id = 1');
        console.log(`[正确方式] 最终余额: ${res.rows[0].amount} (预期: 1100)`);
    } finally {
        await client.end();
    }
}

// Demo 3: REPEATABLE READ 下检测更新冲突
async function demoRRConflict() {
    const client1 = new Client(config);
    const client2 = new Client(config);
    await client1.connect();
    await client2.connect();

    try {
        await setupTable(client1);

        // T1: REPEATABLE READ
        await client1.query('BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ');
        const res1 = await client1.query('SELECT amount FROM accounts WHERE id = 1');
        console.log(`[RR-T1] 读取余额: ${res1.rows[0].amount}`);

        // T2: REPEATABLE READ
        await client2.query('BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ');
        const res2 = await client2.query('SELECT amount FROM accounts WHERE id = 1');
        console.log(`[RR-T2] 读取余额: ${res2.rows[0].amount}`);

        // T1 更新并提交
        await client1.query('UPDATE accounts SET amount = $1 WHERE id = 1', [res1.rows[0].amount + 100]);
        await client1.query('COMMIT');
        console.log('[RR-T1] 提交成功');

        // T2 尝试更新 —— 应该报错
        try {
            await client2.query('UPDATE accounts SET amount = $1 WHERE id = 1', [res2.rows[0].amount + 100]);
            await client2.query('COMMIT');
            console.log('[RR-T2] 提交成功(不应发生)');
        } catch (err) {
            await client2.query('ROLLBACK');
            console.log(`[RR-T2] 预期错误: ${err.message}`);
            // 输出: could not serialize access due to concurrent update
        }
    } finally {
        await client1.end();
        await client2.end();
    }
}

// Demo 4: 查看事务快照
async function demoSnapshot() {
    const client = new Client(config);
    await client.connect();

    try {
        // PostgreSQL 13+
        const res = await client.query('SELECT pg_current_snapshot()');
        console.log(`[快照] pg_current_snapshot: ${res.rows[0].pg_current_snapshot}`);

        // 开启事务查看快照变化
        await client.query('BEGIN');
        const snap1 = await client.query('SELECT pg_current_snapshot()');
        console.log(`[快照-事务中] ${snap1.rows[0].pg_current_snapshot}`);
        await client.query('COMMIT');
    } finally {
        await client.end();
    }
}

async function main() {
    console.log('=== Demo 1: READ COMMITTED 更新丢失 ===');
    await demoLostUpdateRC();

    console.log('\n=== Demo 2: 正确原子更新 ===');
    await demoCorrectUpdate();

    console.log('\n=== Demo 3: REPEATABLE READ 冲突检测 ===');
    await demoRRConflict();

    console.log('\n=== Demo 4: 事务快照 ===');
    await demoSnapshot();
}

main().catch(console.error);

对应的PostgreSQL原生SQL命令:

-- 创建测试表
CREATE TABLE accounts (id INT PRIMARY KEY, name VARCHAR(50), amount INT);
INSERT INTO accounts VALUES (1, 'alice', 1000);

-- READ COMMITTED 更新丢失复现
-- 窗口1
BEGIN;
SELECT amount FROM accounts WHERE id = 1;  -- 1000
UPDATE accounts SET amount = 1100 WHERE id = 1;
COMMIT;

-- 窗口2(并发)
BEGIN;
SELECT amount FROM accounts WHERE id = 1;  -- 1000
UPDATE accounts SET amount = 1100 WHERE id = 1;
COMMIT;
-- 结果: 1100

-- 正确原子更新
BEGIN;
UPDATE accounts SET amount = amount + 100 WHERE id = 1;
COMMIT;

-- REPEATABLE READ 冲突检测
-- 窗口1
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT amount FROM accounts WHERE id = 1;
UPDATE accounts SET amount = 1100 WHERE id = 1;
COMMIT;

-- 窗口2
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT amount FROM accounts WHERE id = 1;
UPDATE accounts SET amount = 1100 WHERE id = 1;
-- 等待后报错: ERROR: could not serialize access due to concurrent update

-- 查看快照
SELECT pg_current_snapshot();

技术点总结

  • READ COMMITTED 更新丢失:演示了应用层读取后计算再更新导致的丢失更新问题
  • 原子更新:使用 amount = amount + 100 避免更新丢失
  • REPEATABLE READ 冲突检测:展示快照隔离下并发更新的冲突报错机制
  • 快照查看:使用 pg_current_snapshot() 观察事务快照

多语言示例

Go语言示例

使用 pgx 驱动(推荐)或 lib/pq,这里采用 pgx/v5。

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/jackc/pgx/v5"
)

func main() {
    ctx := context.Background()

    // 连接配置
    connStr := "postgres://postgres:your_password@localhost:5432/testdb"
    conn, err := pgx.Connect(ctx, connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close(ctx)

    // 准备测试表
    setup := `
        DROP TABLE IF EXISTS accounts;
        CREATE TABLE accounts (id INT PRIMARY KEY, name VARCHAR(50), amount INT);
        INSERT INTO accounts VALUES (1, 'alice', 1000);
    `
    if _, err := conn.Exec(ctx, setup); err != nil {
        log.Fatal(err)
    }
    fmt.Println("[Setup] 表已创建,初始余额: 1000")

    // Demo 1: READ COMMITTED 更新丢失
    fmt.Println("\n=== Demo 1: READ COMMITTED 更新丢失 ===")
    demoLostUpdateRC(ctx, conn)

    // Demo 2: 正确原子更新
    fmt.Println("\n=== Demo 2: 正确原子更新 ===")
    demoCorrectUpdate(ctx, conn)

    // Demo 3: REPEATABLE READ 冲突检测
    fmt.Println("\n=== Demo 3: REPEATABLE READ 冲突检测 ===")
    demoRRConflict(ctx, conn)

    // Demo 4: 快照查看
    fmt.Println("\n=== Demo 4: 快照查看 ===")
    demoSapshot(ctx, conn)
}

func demoLostUpdateRC(ctx context.Context, conn *pgx.Conn) {
    // 重置表数据
    conn.Exec(ctx, "TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)")

    // 使用两个独立连接模拟并发
    conn1, _ := pgx.Connect(ctx, conn.Config().ConnString())
    defer conn1.Close(ctx)
    conn2, _ := pgx.Connect(ctx, conn.Config().ConnString())
    defer conn2.Close(ctx)

    // T1 读取
    tx1, _ := conn1.Begin(ctx)
    var balance1 int
    tx1.QueryRow(ctx, "SELECT amount FROM accounts WHERE id = 1").Scan(&balance1)
    fmt.Printf("[T1] 读取余额: %d\n", balance1)

    // T2 读取(在T1提交前)
    tx2, _ := conn2.Begin(ctx)
    var balance2 int
    tx2.QueryRow(ctx, "SELECT amount FROM accounts WHERE id = 1").Scan(&balance2)
    fmt.Printf("[T2] 读取余额: %d\n", balance2)

    // T1 更新并提交
    _, _ = tx1.Exec(ctx, "UPDATE accounts SET amount = $1 WHERE id = 1", balance1+100)
    tx1.Commit(ctx)
    fmt.Printf("[T1] 提交更新: %d -> %d\n", balance1, balance1+100)

    // T2 更新(此时T1已提交,在READ COMMITTED下会读到新值)
    _, _ = tx2.Exec(ctx, "UPDATE accounts SET amount = $1 WHERE id = 1", balance2+100)
    tx2.Commit(ctx)
    fmt.Printf("[T2] 提交更新: %d -> %d\n", balance2, balance2+100)

    var final int
    conn.QueryRow(ctx, "SELECT amount FROM accounts WHERE id = 1").Scan(&final)
    fmt.Printf("[结果] 最终余额: %d (预期: 1200, 实际: %d)\n", final, final)
    // 输出: 1100
}

func demoCorrectUpdate(ctx context.Context, conn *pgx.Conn) {
    conn.Exec(ctx, "TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)")

    tx, _ := conn.Begin(ctx)
    _, _ = tx.Exec(ctx, "UPDATE accounts SET amount = amount + 100 WHERE id = 1")
    tx.Commit(ctx)

    var final int
    conn.QueryRow(ctx, "SELECT amount FROM accounts WHERE id = 1").Scan(&final)
    fmt.Printf("[正确方式] 最终余额: %d (预期: 1100)\n", final)
}

func demoRRConflict(ctx context.Context, conn *pgx.Conn) {
    conn.Exec(ctx, "TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)")

    conn1, _ := pgx.Connect(ctx, conn.Config().ConnString())
    defer conn1.Close(ctx)
    conn2, _ := pgx.Connect(ctx, conn.Config().ConnString())
    defer conn2.Close(ctx)

    tx1, _ := conn1.Begin(ctx)
    tx1.Exec(ctx, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
    var b1 int
    tx1.QueryRow(ctx, "SELECT amount FROM accounts WHERE id = 1").Scan(&b1)
    fmt.Printf("[RR-T1] 读取余额: %d\n", b1)

    tx2, _ := conn2.Begin(ctx)
    tx2.Exec(ctx, "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
    var b2 int
    tx2.QueryRow(ctx, "SELECT amount FROM accounts WHERE id = 1").Scan(&b2)
    fmt.Printf("[RR-T2] 读取余额: %d\n", b2)

    // T1 更新并提交
    tx1.Exec(ctx, "UPDATE accounts SET amount = $1 WHERE id = 1", b1+100)
    tx1.Commit(ctx)
    fmt.Println("[RR-T1] 提交成功")

    // T2 尝试更新,应报错
    _, err := tx2.Exec(ctx, "UPDATE accounts SET amount = $1 WHERE id = 1", b2+100)
    if err != nil {
        tx2.Rollback(ctx)
        fmt.Printf("[RR-T2] 预期错误: %v\n", err)
    } else {
        tx2.Commit(ctx)
        fmt.Println("[RR-T2] 提交成功(不应发生)")
    }
}

func demoSapshot(ctx context.Context, conn *pgx.Conn) {
    var snap string
    conn.QueryRow(ctx, "SELECT pg_current_snapshot()").Scan(&snap)
    fmt.Printf("[快照] pg_current_snapshot: %s\n", snap)

    tx, _ := conn.Begin(ctx)
    var snap2 string
    tx.QueryRow(ctx, "SELECT pg_current_snapshot()").Scan(&snap2)
    fmt.Printf("[快照-事务中] %s\n", snap2)
    tx.Commit(ctx)
}

运行说明:

  1. 安装Go 1.18+,依赖 github.com/jackc/pgx/v5
  2. 执行 go mod init demo && go get github.com/jackc/pgx/v5
  3. 修改连接串,运行 go run main.go

Python语言示例

使用 psycopg2(或 asyncpg,这里采用同步 psycopg2)。

import psycopg2
import psycopg2.extras

def main():
    conn_str = "dbname=testdb user=postgres password=your_password host=localhost port=5432"
    conn = psycopg2.connect(conn_str)
    conn.autocommit = True
    cur = conn.cursor()

    # 准备测试表
    cur.execute("DROP TABLE IF EXISTS accounts")
    cur.execute("CREATE TABLE accounts (id INT PRIMARY KEY, name VARCHAR(50), amount INT)")
    cur.execute("INSERT INTO accounts VALUES (1, 'alice', 1000)")
    print("[Setup] 表已创建,初始余额: 1000")

    print("\n=== Demo 1: READ COMMITTED 更新丢失 ===")
    demo_lost_update_rc(conn_str)

    print("\n=== Demo 2: 正确原子更新 ===")
    demo_correct_update(conn_str)

    print("\n=== Demo 3: REPEATABLE READ 冲突检测 ===")
    demo_rr_conflict(conn_str)

    print("\n=== Demo 4: 快照查看 ===")
    demo_snapshot(conn_str)

    cur.close()
    conn.close()

def demo_lost_update_rc(conn_str):
    # 重置表
    conn1 = psycopg2.connect(conn_str)
    conn1.autocommit = True
    c1 = conn1.cursor()
    c1.execute("TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)")
    c1.close()

    # 两个连接模拟并发
    conn1 = psycopg2.connect(conn_str)
    conn2 = psycopg2.connect(conn_str)
    c1 = conn1.cursor()
    c2 = conn2.cursor()

    # T1
    conn1.autocommit = False
    c1.execute("BEGIN")
    c1.execute("SELECT amount FROM accounts WHERE id = 1")
    balance1 = c1.fetchone()[0]
    print(f"[T1] 读取余额: {balance1}")

    # T2
    conn2.autocommit = False
    c2.execute("BEGIN")
    c2.execute("SELECT amount FROM accounts WHERE id = 1")
    balance2 = c2.fetchone()[0]
    print(f"[T2] 读取余额: {balance2}")

    # T1 更新提交
    c1.execute("UPDATE accounts SET amount = %s WHERE id = 1", (balance1 + 100,))
    conn1.commit()
    print(f"[T1] 提交更新: {balance1} -> {balance1 + 100}")

    # T2 更新(READ COMMITTED下读到新值)
    c2.execute("UPDATE accounts SET amount = %s WHERE id = 1", (balance2 + 100,))
    conn2.commit()
    print(f"[T2] 提交更新: {balance2} -> {balance2 + 100}")

    c3 = conn1.cursor()
    c3.execute("SELECT amount FROM accounts WHERE id = 1")
    final = c3.fetchone()[0]
    print(f"[结果] 最终余额: {final} (预期: 1200, 实际: {final})")
    c1.close(); c2.close(); c3.close()
    conn1.close(); conn2.close()

def demo_correct_update(conn_str):
    conn = psycopg2.connect(conn_str)
    conn.autocommit = False
    cur = conn.cursor()
    cur.execute("TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)")
    cur.execute("BEGIN")
    cur.execute("UPDATE accounts SET amount = amount + 100 WHERE id = 1")
    conn.commit()
    cur.execute("SELECT amount FROM accounts WHERE id = 1")
    final = cur.fetchone()[0]
    print(f"[正确方式] 最终余额: {final} (预期: 1100)")
    cur.close(); conn.close()

def demo_rr_conflict(conn_str):
    conn1 = psycopg2.connect(conn_str)
    conn2 = psycopg2.connect(conn_str)
    c1 = conn1.cursor()
    c2 = conn2.cursor()

    conn1.autocommit = False
    conn2.autocommit = False

    # 重置
    c1.execute("TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)")
    conn1.commit()

    c1.execute("BEGIN")
    c1.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
    c1.execute("SELECT amount FROM accounts WHERE id = 1")
    b1 = c1.fetchone()[0]
    print(f"[RR-T1] 读取余额: {b1}")

    c2.execute("BEGIN")
    c2.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
    c2.execute("SELECT amount FROM accounts WHERE id = 1")
    b2 = c2.fetchone()[0]
    print(f"[RR-T2] 读取余额: {b2}")

    c1.execute("UPDATE accounts SET amount = %s WHERE id = 1", (b1 + 100,))
    conn1.commit()
    print("[RR-T1] 提交成功")

    try:
        c2.execute("UPDATE accounts SET amount = %s WHERE id = 1", (b2 + 100,))
        conn2.commit()
        print("[RR-T2] 提交成功(不应发生)")
    except psycopg2.errors.SerializationFailure as e:
        conn2.rollback()
        print(f"[RR-T2] 预期错误: {e}")

    c1.close(); c2.close()
    conn1.close(); conn2.close()

def demo_snapshot(conn_str):
    conn = psycopg2.connect(conn_str)
    cur = conn.cursor()
    cur.execute("SELECT pg_current_snapshot()")
    snap = cur.fetchone()[0]
    print(f"[快照] pg_current_snapshot: {snap}")

    cur.execute("BEGIN")
    cur.execute("SELECT pg_current_snapshot()")
    snap2 = cur.fetchone()[0]
    print(f"[快照-事务中] {snap2}")
    cur.execute("COMMIT")
    cur.close(); conn.close()

if __name__ == "__main__":
    main()

运行说明:

  1. 安装Python 3.8+,依赖 psycopg2-binary:pip install psycopg2-binary
  2. 修改连接参数,运行 python demo.py

Java语言示例

使用JDBC(PostgreSQL驱动)和HikariCP(可选,这里简单用DriverManager)。

import java.sql.*;

public class IsolationDemo {
    static final String URL = "jdbc:postgresql://localhost:5432/testdb";
    static final String USER = "postgres";
    static final String PASSWORD = "your_password";

    public static void main(String[] args) throws SQLException {
        // 初始化表
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             Statement stmt = conn.createStatement()) {
            stmt.execute("DROP TABLE IF EXISTS accounts");
            stmt.execute("CREATE TABLE accounts (id INT PRIMARY KEY, name VARCHAR(50), amount INT)");
            stmt.execute("INSERT INTO accounts VALUES (1, 'alice', 1000)");
            System.out.println("[Setup] 表已创建,初始余额: 1000");
        }

        System.out.println("\n=== Demo 1: READ COMMITTED 更新丢失 ===");
        demoLostUpdateRC();

        System.out.println("\n=== Demo 2: 正确原子更新 ===");
        demoCorrectUpdate();

        System.out.println("\n=== Demo 3: REPEATABLE READ 冲突检测 ===");
        demoRRConflict();

        System.out.println("\n=== Demo 4: 快照查看 ===");
        demoSapshot();
    }

    static void demoLostUpdateRC() throws SQLException {
        try (Connection conn1 = DriverManager.getConnection(URL, USER, PASSWORD);
             Connection conn2 = DriverManager.getConnection(URL, USER, PASSWORD)) {
            // 重置
            try (Statement stmt = conn1.createStatement()) {
                stmt.execute("TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)");
            }

            conn1.setAutoCommit(false);
            conn2.setAutoCommit(false);

            // T1 读取
            int balance1;
            try (Statement stmt = conn1.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT amount FROM accounts WHERE id = 1")) {
                rs.next();
                balance1 = rs.getInt(1);
                System.out.println("[T1] 读取余额: " + balance1);
            }

            // T2 读取
            int balance2;
            try (Statement stmt = conn2.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT amount FROM accounts WHERE id = 1")) {
                rs.next();
                balance2 = rs.getInt(1);
                System.out.println("[T2] 读取余额: " + balance2);
            }

            // T1 更新提交
            try (Statement stmt = conn1.createStatement()) {
                stmt.executeUpdate("UPDATE accounts SET amount = " + (balance1 + 100) + " WHERE id = 1");
            }
            conn1.commit();
            System.out.println("[T1] 提交更新: " + balance1 + " -> " + (balance1 + 100));

            // T2 更新(READ COMMITTED)
            try (Statement stmt = conn2.createStatement()) {
                stmt.executeUpdate("UPDATE accounts SET amount = " + (balance2 + 100) + " WHERE id = 1");
            }
            conn2.commit();
            System.out.println("[T2] 提交更新: " + balance2 + " -> " + (balance2 + 100));

            // 查询结果
            try (Statement stmt = conn1.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT amount FROM accounts WHERE id = 1")) {
                rs.next();
                int finalBal = rs.getInt(1);
                System.out.printf("[结果] 最终余额: %d (预期: 1200, 实际: %d)\n", finalBal, finalBal);
            }
        }
    }

    static void demoCorrectUpdate() throws SQLException {
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             Statement stmt = conn.createStatement()) {
            stmt.execute("TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)");
            conn.setAutoCommit(false);
            stmt.executeUpdate("UPDATE accounts SET amount = amount + 100 WHERE id = 1");
            conn.commit();
            try (ResultSet rs = stmt.executeQuery("SELECT amount FROM accounts WHERE id = 1")) {
                rs.next();
                int finalBal = rs.getInt(1);
                System.out.println("[正确方式] 最终余额: " + finalBal + " (预期: 1100)");
            }
        }
    }

    static void demoRRConflict() throws SQLException {
        try (Connection conn1 = DriverManager.getConnection(URL, USER, PASSWORD);
             Connection conn2 = DriverManager.getConnection(URL, USER, PASSWORD)) {
            // 重置
            try (Statement stmt = conn1.createStatement()) {
                stmt.execute("TRUNCATE accounts; INSERT INTO accounts VALUES (1, 'alice', 1000)");
            }

            conn1.setAutoCommit(false);
            conn2.setAutoCommit(false);

            // T1 RR
            try (Statement stmt = conn1.createStatement()) {
                stmt.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
                ResultSet rs = stmt.executeQuery("SELECT amount FROM accounts WHERE id = 1");
                rs.next();
                int b1 = rs.getInt(1);
                System.out.println("[RR-T1] 读取余额: " + b1);
                stmt.executeUpdate("UPDATE accounts SET amount = " + (b1 + 100) + " WHERE id = 1");
            }
            conn1.commit();
            System.out.println("[RR-T1] 提交成功");

            // T2 RR 尝试更新
            try (Statement stmt = conn2.createStatement()) {
                stmt.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
                ResultSet rs = stmt.executeQuery("SELECT amount FROM accounts WHERE id = 1");
                rs.next();
                int b2 = rs.getInt(1);
                System.out.println("[RR-T2] 读取余额: " + b2);
                stmt.executeUpdate("UPDATE accounts SET amount = " + (b2 + 100) + " WHERE id = 1");
            }
            conn2.commit();
            System.out.println("[RR-T2] 提交成功(不应发生)");
        } catch (SQLException e) {
            if (e.getSQLState().equals("40001")) { // serialization failure
                System.out.println("[RR-T2] 预期错误: " + e.getMessage());
            } else {
                throw e;
            }
        }
    }

    static void demoSapshot() throws SQLException {
        try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
             Statement stmt = conn.createStatement()) {
            try (ResultSet rs = stmt.executeQuery("SELECT pg_current_snapshot()")) {
                rs.next();
                System.out.println("[快照] pg_current_snapshot: " + rs.getString(1));
            }
            conn.setAutoCommit(false);
            try (ResultSet rs = stmt.executeQuery("SELECT pg_current_snapshot()")) {
                rs.next();
                System.out.println("[快照-事务中] " + rs.getString(1));
            }
            conn.commit();
        }
    }
}

运行说明:

  1. JDK 11+,添加PostgreSQL JDBC驱动依赖(如 org.postgresql:postgresql:42.7.2)
  2. 编译:javac IsolationDemo.java
  3. 运行(需把驱动jar加入classpath):java IsolationDemo

多语言对比

维度Node.js (pg)Go (pgx)Python (psycopg2)Java (JDBC)
驱动/库pgpgx/v5psycopg2PostgreSQL JDBC Driver
连接方式回调/async/await上下文context + 连接池同步阻塞同步阻塞
事务控制client.query('BEGIN') 或 client.query 自动事务Begin(ctx) 返回 Txconnection.autocommit=False 或 BEGINconnection.setAutoCommit(false)
隔离级别设置SQL语句 SET TRANSACTIONSQL语句 SET TRANSACTIONSQL语句 SET TRANSACTIONstmt.execute("SET TRANSACTION ...")
错误处理try/catch返回errortry/excepttry/catch (SQLException)
类型映射自动映射为JS类型扫描到变量自动转换Python类型通过 ResultSet.getXxx()
连接池支持内置 Poolpgxpoolpsycopg2.poolHikariCP / 内置
性能特点事件驱动,高并发协程,高吞吐同步,适中同步,连接池优化
适用场景Web后端,高I/O微服务,云原生数据科学,脚本企业级应用
对应PostgreSQL原生SQL示例与SQL直接对应与SQL直接对应与SQL直接对应与SQL直接对应

核心行为一致性:所有语言示例均实现了相同的四个Demo:

  1. READ COMMITTED更新丢失:展示“读取-计算-写入”模式下的丢失更新。
  2. 正确原子更新:使用 amount = amount + 100 避免更新丢失。
  3. REPEATABLE READ冲突检测:展示并发更新时序列化冲突报错。
  4. 查看事务快照:调用 pg_current_snapshot()。

官方文档

参考链接

总结

本文系统梳理了PostgreSQL事务隔离级别的完整知识体系。PostgreSQL基于MVCC架构通过事务快照实现并发控制,内部提供三种隔离级别:READ COMMITTED(默认)、REPEATABLE READ和SERIALIZABLE,其中READ UNCOMMITTED在行为上等价于READ COMMITTED。各隔离级别在脏读、不可重复读、幻读和序列化异常方面的表现可参照官方表13.1。

关键实践要点:

  1. READ COMMITTED下需警惕应用层更新丢失,应使用原子更新(如 SET amount = amount + delta)替代“读取-计算-写入”模式。
  2. REPEATABLE READ通过快照复用保证可重复读,但并发更新冲突时会抛出 could not serialize access due to concurrent update 错误。
  3. SERIALIZABLE基于SSI提供最高级别保证,但需处理 serialization_failure 错误并实现重试逻辑。
  4. 快照查看使用 pg_current_snapshot()(PG 13+)或已废弃的 txid_current_snapshot()(PG 12-)。

在实际应用中,READ COMMITTED 作为默认级别适用于大多数场景,兼顾性能与一致性;备份等需要一致视图的场景使用 REPEATABLE READ;对数据一致性要求极高的金融类业务可考虑 SERIALIZABLE,但需评估性能开销。

MySQL 8.0 与 PostgreSQL 15 事务并发控制对比:3种隔离级别下的性能差异实测 本文对比了MySQL 8.0与PostgreSQL 15在三种事务隔离级别(读已提交、可重复读、序列化)下的性能差异,通过实际基准测试数据揭示两者在高并发场景中的表现优劣。测试结果显示PostgreSQL在MVCC实现和SSI机制上具有显著优势,特别是在高并发和冲突检测场景下,为数据库选型提供了重要参考。 阅读详情

相关推荐

PostgreSQL 9.3三大核心特性:SSI、FDW与JSON演进解析

可序列化快照隔离(SSI)是数据库事务一致性的重要保障机制,其基于MVCC的冲突检测设计兼顾性能与严格语义;外部数据包装器(FDW)作为统一异构数据源访问的标准接口,支撑现代数据编织(Data Fabric)架构落地;而原生JSON支持则标志着关系型数据库向混合数据模型迈出关键一步。这三项技术共同构成PostgreSQL从传统OLTP向现代数据平台跃迁的基石,广泛应用于金融强一致场景、跨源实时分析及半结构化日志处理等工程实践。本文深入拆解2013年Postgres Open确立的SSI实现原理、FDW协议规

优快云头条 424

PostgreSQL中的MVCC机制

MVCC,Multi-Version Concurrency Control,多版本并发控制。 一句话讲,MVCC就是用同一份数据临时保留多版本的方式,实现并发控制。它可以避免读写事务之间的互相阻塞,相比通常的封锁技术可极大的提高业务的并发性能。 为何需要MVCC 如果有人从数据库中读数据的同时,有另外的人写入数据,有可能读数据的人会看到『半写』或者不一致的数据。有很多种方法来解决这个问题,叫做并...

hmxz2nn的博客 8923

PostgreSQL 深度剖析:从 MVCC 到执行器、从索引到复制与恢复

它既是严格的 ACID 事务数据库(OLTP),又能靠扩展生态(FDW、索引族、存储过程、流复制、插件)向 HTAP/地理/时序/JSON 文档场景伸展。当你能用 EXPLAIN 解释每一次“慢”和“快”的原因时,PostgreSQL 才真正成为你的工程资产,而不是“运气好的黑盒”。每次事务写数据页之前,先把日志(WAL)的记录写落盘,以便崩溃恢复(AR/REDO)。:块级最小索引,适合“随时间递增插入”的超大表(时间序列、日志),空间极省。:通用搜索树,用于空间/模糊匹配(PostGIS、全文近似)。

aifs2025的博客 1945

深入理解 PostgreSQL 中的 MVCC(多版本并发控制)机制

简要介绍MVCC(多版本并发控制)概念和其在数据库中的重要性。提出写作目的,即深入理解PostgreSQL中的MVCC机制,并解释读者可以从本文中学到什么。MVCC,即多版本并发控制,是一种用于处理数据库中并发操作的机制。在传统的并发控制方式中,常见的做法是通过锁定资源来确保在某一时刻只有一个事务可以修改或读取数据,以防止数据不一致或冲突。然而,传统的锁定机制可能会导致性能瓶颈和并发性下降,尤其在高并发访问的情况下。MVCC通过引入多个数据版本来解决传统锁定机制的一些局限性。

猫头虎技术团队:授渔优于赠鱼,兴趣引领智慧,探索之乐尤显珍贵。商务合作+:Libin9iOak ,万粉变现+:优快云WF,猫头虎承诺每年免费为100名C站创作者做账号流量诊断服务!全网搜:猫头虎技术团队,点击文章底部名片或直接私信我一切皆可谈,快找虎哥! 3948

深入理解PostgreSQL的MVCC并发处理方式

Postgre数据库的很大的卖点之一就是它处理并发的方式。我们的期望很简单:读永远不阻塞写,反之亦然。Postgres通过一个叫做 多版本并发控制(MVCC) 的机制做到了这一点。这个技术并不是Postgres所特有的:还有好几种数据库都实现了不同形式的MVCC,包括 Oracle、Berkeley DB、CouchDB 等等 。当你使用PostgreSQL来设计高并发的应用时,理解它的MVCC是怎么实现的很重要。它事实上是复杂问题的一种非常优雅和简单的解法。 MVCC如何工作 在Postgres中,每一个事务都会得到一个被称作为 XID 的事务ID。这里说的事务不仅仅是被 BEGIN – C

优快云积分获取与管理全攻略

技术社区积分系统是知识共享生态的核心激励机制,通过量化用户贡献实现资源交换。其底层逻辑遵循Web2.0时代的UGC经济模型,用户通过内容创作、社区互动等行为积累虚拟资产。在开发者社区场景中,积分体系能有效促进技术文档、工具源码等资源的流通,优快云作为国内头部平台,其积分规则具有典型参考价值。掌握签到策略、内容互动技巧和资源上传规范等实操方法,可系统性提升积分获取效率。2023年新规强化了连续签到奖励和互动质量检测机制,结合GitHub等开源平台资源互补,能构建更高效的技术资源获取路径。

weixin_34233974的博客 218

PostgreSQL、Oracle、Mysql中MVCC机制详解

mvcc,也就是多版本并发控制,是为了在读取数据时不加锁来提高读取效率和并发性的一种手段。读-读:不存在任何问题。读-写:有线程安全问题,可能出现脏读、幻读、不可重复读。写-写:有线程安全问题,可能存在更新丢失等。mvcc解决的就是读写时的线程安全问题,线程不用去争抢读写锁。MVCC:Multi-version Concurrency Control (多版本并发控制)。S2PL: Strict Two-Phase Locking(严格二阶段锁):读写互斥,保证串行。

meser88的博客 1559

SimMechanics二连杆仿真避坑指南:Solid模块坐标系设置与Rigid Transform常见错误

本文详细解析了SimMechanics中二连杆平面臂仿真的常见问题,重点探讨Solid模块坐标系设置与Rigid Transform模块的配置陷阱。通过实战案例和MATLAB代码示例,揭示多体系统仿真中的关键错误点,包括坐标系方向定义、参数匹配、端口连接规则等,帮助工程师有效规避仿真风险,提升建模效率。

weixin_29309213的博客 414

PostgreSQL技术内幕11:PostgreSQL事务原理解析-MVCC

本文主要介绍在事务模块中MVCC(多版本并发控制)常见的实现方式,优缺点以及PG事务模块中MVCC(多版本并发控制)的实现。

qq_40529346的博客 1551

医院网络设计(完整文档+思科拓扑图)

大家好,我是小华学长,一名计算机领域的博主。经过多年的学习和实践,我积累了丰富的计算机知识和经验,在这里我想与大家分享我的学习心得和技巧,帮助你成为更好的程序员。作为一名计算机博主,我一直专注于编程、算法、软件开发等领域,在这些方面积累了大量的经验。我相信分享是一种双赢的方式,通过分享,我可以帮助他人提升技术水平,同时也能够得到学习交流的机会。在我的文章中,你将会看到我对于各种编程语言、开发工具以及常见问题的解析和分析。我会结合自己的实际项目经验,为你提供实用的解决方案和优化技巧。

qq1325513482的博客 2万+

【头歌educoder】离散数学实训参考-第二章-关系-part1-关系基础

python实现离散数学-关系基础-二元关系

wander_alice的博客 8466

PostgreSQL多版本并发控制 MVCC

(5 行记录)(1 行记录)VACUUM(1 行记录)

qiao2458的博客 1604

GPL vs MIT:独立开发者选择开源协议的法律与商业考量

你可能听过这样的故事:某个开发者用业余时间写了个超好用的工具库,开源后被某大厂悄悄集成到闭源产品里赚得盆满钵满,开发者却连杯奶茶钱都没拿到;也可能听说过另一个案例:开发者选了GPL协议,结果用户修改代码后必须开源,反而推动项目社区快速壮大,最终靠技术服务赚到了钱。这些故事的核心矛盾,都源于开源协议的选择。对于独立开发者(尤其是单枪匹马写代码的个人或小团队),开源协议不是“随便选一个就行”的小事——它是代码的“使用说明书”,更是保护自身权益、规划项目未来的关键工具。

AI天才研究院 1182

MySQL与PostgreSQL事务隔离级别对比分析(跨数据库隔离机制深度揭秘)

深入解析SQL事务隔离级别详解,对比MySQL与PostgreSQL在读未提交、读已提交、可重复读及串行化场景下的实现差异,揭示跨数据库隔离机制的核心原理与性能影响。掌握各引擎锁机制与MVCC策略的关键优势,优化并发控制方案,值得收藏。

DebugLoom的博客 863

复制幻灯片到另一个PPT编程

请确保将源PPT文件和目标PPT文件的文件名修改为你实际使用的文件名。运行上述代码后,目标PPT将包含源PPT中的所有幻灯片和形状。这里假设源PPT文件名为"source.pptx",目标PPT文件名为"target.pptx"。方法将每个幻灯片的布局添加到目标PPT中。是一个强大的库,可以用于创建、修改和操作PPT文件。要将一个PPT中的幻灯片复制到另一个PPT,可以使用Python的。这段代码遍历源PPT的每个幻灯片,并使用。步骤 3: 打开源PPT和目标PPT。这将保存修改后的目标PPT文件。

ByteSparkX的博客 1322

STM32实战开发(99):基于STM32的智能交通灯控制系统设计与实现

随着城市化进程的加速和交通流量的不断增长,传统的交通灯控制系统已逐渐不能满足现代城市交通管理的需求。为了更好地提高交通效率、减少交通拥堵、降低事故发生率,智能交通灯控制系统成为了解决这一问题的重要途径。通过智能算法和自动化控制,智能交通灯控制系统能够根据实时交通流量和交通状况来自动调整红绿灯的切换,从而优化交通流和提高交通安全。STM32作为一款高性能的微控制器,具备较强的运算能力、丰富的外设接口和优良的功耗表现,非常适合用来设计和开发智能交通灯控制系统。

2201_76125393的博客 1219

MySQL与PostgreSQL事务隔离级别与幻读现象深度解析

事务隔离级别是数据库并发控制的核心机制,通过MVCC(多版本并发控制)等技术实现数据一致性。幻读(Phantom Read)是事务隔离中的典型问题,指同一事务内连续查询结果集不一致的现象。不同数据库如MySQL和PostgreSQL在实现事务隔离时存在显著差异,MySQL通过间隙锁(Gap Lock)防御幻读,而PostgreSQL采用SSI(可串行化快照隔离)机制。理解这些差异对Java后端开发至关重要,特别是在高并发场景下,合理选择隔离级别和锁策略能有效平衡性能与数据一致性。本文通过实验对比和实战代码,

weixin_29587015的博客 263

包络检波电路(Envelope Detector)

把 100 kHz 载波想象成"电台的高频信号",把 1 kHz 包络想象成"主持人说话的声波"。而今天电路的工作,就是把这个"调大调小的外壳"(包络)重新剥出来,还原成主持人的声音 🎵。这是三极管开关电路的重要搭档,也是从"二极管家族"过渡到"三极管家族"的桥梁 🔌。——倍压整流看的是"幅值累积"(多个电容接力),包络检波看的是"频率分离"(高低频分开)。同样的"D1 串联 + RC 并联"拓扑,今天被赋予了"AM 解调"的新生命。——电感负载的"泄洪闸",保护三极管开关不被反向电动势击穿。

飞多学堂的博客 97

PostgreSQL 15 事务隔离级别深度评测:4级别对并发性能与数据一致性的影响

本文深入评测了PostgreSQL 15的四种事务隔离级别(Read Uncommitted、Read Committed、Repeatable Read和Serializable)对并发性能与数据一致性的影响。通过详细的基准测试和量化分析,揭示了不同隔离级别在吞吐量、延迟和冲突率等方面的表现差异,并提供了针对性的优化建议和调优策略,帮助数据库设计者在性能与一致性之间做出合理权衡。

weixin_33908217的博客 403

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

PostgreSQL 为什么没有 Gap Lock,却依然能解决幻读?(深度解析MVCC机制)

理解这一点,就理解了 PostgreSQL 事务的核心设计。这也是 PostgreSQL 并发能力强的重要原因。那 PostgreSQL 是如何解决幻读问题的呢?PostgreSQL 的并发性能明显更好。这也是为什么 PostgreSQL 在。之后所有查询都会基于这个快照读取数据。这样其他事务就无法插入新的记录。而 PostgreSQL。

情深深几许 436

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

PostgreSQL MVCC机制解析与并发控制实践

多版本并发控制(MVCC)是现代数据库实现事务隔离的核心技术,通过维护数据版本快照而非锁机制,实现读不阻塞写、写不阻塞读的高并发访问。PostgreSQL采用独特的元组版本链设计,每个数据行包含xmin、xmax等隐藏字段记录事务版本信息,结合事务快照机制确保隔离性。在可重复读隔离级别下,通过SSI算法有效防止幻读问题,相比MySQL的间隙锁方案显著提升吞吐量。该技术特别适合CDC数据同步、时态查询等场景,但需注意版本膨胀和事务ID回卷等运维挑战。合理配置autovacuum参数和fillfactor等优化

weixin_30984001的博客 185

PostgreSQL笔记28: PostgreSQL ACID 特性与实现机制深度解析

摘要 PostgreSQL的事务机制严格遵循ACID原则,通过原子性、一致性、隔离性和持久性保障数据可靠性。原子性通过WAL和CLOG实现全事务回滚,子事务(SAVEPOINT)提供细粒度控制但需注意XID消耗和性能影响。一致性由约束条件确保数据完整性。隔离性通过MVCC实现高效并发,支持READ COMMITTED(默认)、REPEATABLE READ和SERIALIZABLE三级隔离,其中REPEATABLE READ已防止幻读。持久性依赖WAL日志和检查点机制,确保事务提交后数据永久保存。合理使用事

Wang的专栏 464

PostgreSQL MVCC机制解析与高并发优化实践

MVCC(多版本并发控制)是现代数据库实现事务隔离的核心技术,通过为每个事务创建数据快照,实现读写操作的非阻塞并发。相比传统锁机制,MVCC显著提升了系统吞吐量,特别适用于电商库存、金融交易等高并发场景。PostgreSQL通过xmin/xmax事务ID标记和CTID指针实现版本链存储,配合VACUUM机制自动清理死元组。合理配置autovacuum参数和监控dead_ratio指标能有效预防表膨胀问题,而HOT更新优化可减少索引维护开销。深入理解MVCC的可见性判断逻辑和事务隔离级别实现差异,是解决长事务

weixin_31364787的博客 294

数据库系统 ACID 特性与事务调度:5种调度关系深度解析与实战验证

本文深入解析数据库系统ACID特性与事务调度的五种核心关系,包括串行调度、严格两阶段封锁协议调度、两阶段封锁协议调度、可串行化调度和正确调度。通过Python实战模拟银行转账场景,直观展示不同调度类型的行为差异与一致性保证,帮助开发者深入理解数据库并发控制机制及其在实际系统中的应用。

weixin_34268579的博客 315

MVCC技术解析:数据库高并发控制的核心原理与实践

MVCC(多版本并发控制)是数据库系统中实现高并发的关键技术,它通过维护数据的多个版本实现读写操作的并行执行。该技术的核心原理是版本链机制,每个事务只能看到特定时间点的数据快照,从而避免读写冲突。在工业实践中,MySQL和PostgreSQL等主流数据库采用不同的MVCC实现方案,如InnoDB的回滚段和PostgreSQL的堆表存储。MVCC技术能显著提升系统吞吐量,特别适合电商订单、金融交易等高并发场景。但在实际应用中需要注意版本膨胀、长事务等问题,合理配置autovacuum等参数。随着分布式数据库发

weixin_30768881的博客 208

MySQL迁移PostgreSQL实战:性能优化与关键差异解析

数据库迁移是系统架构演进中的关键决策,特别是从MySQL转向PostgreSQL时,需要深入理解两者的核心差异。PostgreSQL凭借其先进的JSONB数据类型和MVCC并发控制机制,在复杂查询和高并发场景下展现出显著优势。JSONB类型通过GIN索引实现高效文档查询,而MVCC机制则大幅降低死锁概率,这对电商、金融等业务至关重要。在迁移过程中,数据类型转换、索引重建和事务隔离级别等细节直接影响系统性能,合理的工具链选择和后期调优(如VACUUM配置和WAL优化)能提升数倍吞吐量。通过实际案例可见,正确实

weixin_32576389的博客 203
上一篇: PostgreSQL笔记28: PostgreSQL ACID 特性与实现机制深度解析
下一篇: PostgreSQL笔记30:锁机制深度解析——表级锁、行级锁、轻量级锁与自旋锁
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、付费专栏及课程。

余额充值