基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

PostgreSQL 表级锁和行级锁的区别?

知识点图片

在 PostgreSQL 中,锁机制是保证数据一致性和并发控制的核心。PostgreSQL 采用了 MVCC(多版本并发控制) 来处理大部分的读写冲突(即“读不阻塞写,写不阻塞读”),但在涉及修改相同数据或更改表结构时,仍然需要使用锁。

PostgreSQL 的锁主要分为表级锁(Table-level Locks)行级锁(Row-level Locks)。它们的区别主要体现在粒度、并发性能、触发场景以及底层实现机制上。

以下是详细的对比和解析:


1. 核心区别对比表

维度 表级锁 (Table-level Lock) 行级锁 (Row-level Lock)
锁定粒度 整个表 表中的某一行或多行
并发性能 较低。容易阻塞其他事务对该表的操作。 极高。仅阻塞操作同一行的事务,其他行不受影响。
触发场景 DDL操作 (ALTER, DROP), 维护操作 (VACUUM), 显式锁表 (LOCK TABLE),以及DML的意向锁。 DML操作 (UPDATE, DELETE),显式行锁 (SELECT ... FOR UPDATE)。
底层存储 存储在内存中的锁表 (Lock Table) 中。 存储在数据块(磁盘/内存缓冲区)的行元组头信息中(xmax字段)。
数量限制 受内存限制(max_locks_per_transaction)。 无数量限制。锁1行和锁1亿行消耗的内存一样。
锁升级 不适用 PostgreSQL 绝对不会发生锁升级(不会因为锁了太多行而变成表锁)。

2. 表级锁(Table-level Locks)详解

表级锁作用于整个表。当你在表上执行某些操作时,系统会自动获取表级锁。

  • 特点:影响面大,主要用于保护表结构不被破坏,或者在进行大规模数据清理时防止干扰。
  • 锁的模式:PostgreSQL 共有 8 种表级锁模式。常见的有:
    • Access Share(访问共享锁):普通的 SELECT 获取此锁。它只与排他锁冲突。
    • Row Exclusive(行排他锁):INSERTUPDATEDELETE 获取此锁。它表示“我正在修改表里的行”。
    • Share(共享锁):CREATE INDEX 获取此锁。允许别人读,但不允许修改数据。
    • Access Exclusive(访问排他锁):最高级别的锁。DROP TABLEALTER TABLEVACUUM FULL 获取此锁。它会阻塞所有对该表的读写操作。
  • 注意:即使你只更新表中的一行数据(行级锁),PostgreSQL 也会先在表级别获取一个 Row Exclusive 锁,这是为了防止在你更新数据的同时,别人把这个表给删了(DROP)。

3. 行级锁(Row-level Locks)详解

行级锁仅作用于被操作的具体行。

  • 特点:并发度极高。事务 A 更新 id=1 的行,事务 B 更新 id=2 的行,两者完全互不干扰。
  • 锁的模式:共有 4 种行级锁:
    • FOR UPDATE:修改行数据(或显式 SELECT ... FOR UPDATE)时获取。阻塞其他任何试图修改或锁定该行的操作。
    • FOR NO KEY UPDATE:类似于 FOR UPDATE,但不修改主键/唯一键。允许其他事务获取 FOR KEY SHARE 锁。
    • FOR SHARE:读取数据并希望别人不要修改它。
    • FOR KEY SHARE:只锁定键值,允许别人修改非键值列。
  • PG 行锁的特殊实现(重中之重)
    与 MySQL (InnoDB) 或 Oracle 将行锁维护在内存结构中不同,PostgreSQL 的行锁是直接写在数据行(Tuple)本身的头部信息里的。 具体来说,是通过修改行头部的 xmax 事务 ID 字段来实现的。
    • 优势:由于行锁存在数据本身上,PostgreSQL 可以同时锁定无数行,而不会消耗额外的内存,也永远不会发生锁升级(Lock Escalation,即行锁太多撑爆内存从而升级为表锁导致全表瘫痪,这在某些其他数据库中存在)。

4. 它们是如何协同工作的?

在实际运行中,表级锁和行级锁是配合使用的。

场景:执行 UPDATE users SET name = 'Alice' WHERE id = 1;

  1. 表级意向:PostgreSQL 首先会在 users 表上申请一个 表级 Row Exclusive。这个锁不会阻止别人读写其他行,但会阻止别人执行 DROP TABLE usersALTER TABLE users
  2. 行级锁定:接着,PostgreSQL 找到 id = 1 的那一行,在这一行的头部标记 xmax 为当前事务ID,获取 行级 FOR UPDATE。这会阻止其他事务同时更新 id = 1 的这一行。

5. 常见的锁阻塞(死锁与卡顿)场景

  • 表级锁阻塞
    • 现象:开发人员在线上执行 ALTER TABLE ADD COLUMN(或者创建普通索引)。
    • 结果:获取了表级 Access Exclusive 锁,导致该表所有的 SELECT, UPDATE, INSERT 全部卡死排队,造成线上故障。
    • 解决:DDL 操作应在低峰期进行,或者使用并发模式(如 CREATE INDEX CONCURRENTLY)。
  • 行级锁阻塞
    • 现象:事务 A 执行了 UPDATE users ... WHERE id = 1 且未 COMMIT,此时事务 B 也执行 UPDATE users ... WHERE id = 1
    • 结果:事务 B 会一直等待,直到事务 A 提交或回滚。
    • 解决:确保事务尽可能短,及时 COMMITROLLBACK

总结

  • 表级锁是用来管宏观的:保护表结构、协调 DDL 和 DML。存在内存里,粒度大。
  • 行级锁是用来管微观的:保护具体数据行的并发修改。存在磁盘数据块的行头里,粒度小,并发高,且在 PostgreSQL 中无数量限制、无锁升级。
00:00
00:00