heavyweight locks 分析:基础

本页目录

重量级锁,包括关系级锁和行级锁,是由查询获取的,并且始终会保持到该查询所属事务结束为止。因此,重要原则是:一旦获取了锁,便不会释放,直到 COMMIT 或 ROLLBACK。

文档:Explicit locking。关于该文档有几点需要注意:

我们如何查看某个特定事务/会话中已经获取的锁 (granted),或正在尝试但尚未获取的锁 (pending)?

为此,Postgres 提供了一个系统视图:pg_locks

重要的规则是:分析应在一个单独的会话中进行,以避免"观察者效应" (即由分析本身获取的锁)。

例如,考虑以下表:

PSQL
nik=# \d t1
                Table "public.t1"
 Column |  Type  | Collation | Nullable | Default
--------+--------+-----------+----------+---------
 c1     | bigint |           |          |
Indexes:
    "t1_c1_idx" btree (c1)
    "t1_c1_idx1" btree (c1)
    "t1_c1_idx10" btree (c1)
    "t1_c1_idx11" btree (c1)
    "t1_c1_idx12" btree (c1)
    "t1_c1_idx13" btree (c1)
    "t1_c1_idx14" btree (c1)
    "t1_c1_idx15" btree (c1)
    "t1_c1_idx16" btree (c1)
    "t1_c1_idx17" btree (c1)
    "t1_c1_idx18" btree (c1)
    "t1_c1_idx19" btree (c1)
    "t1_c1_idx2" btree (c1)
    "t1_c1_idx20" btree (c1)
    "t1_c1_idx3" btree (c1)
    "t1_c1_idx4" btree (c1)
    "t1_c1_idx5" btree (c1)
    "t1_c1_idx6" btree (c1)
    "t1_c1_idx7" btree (c1)
    "t1_c1_idx8" btree (c1)
    "t1_c1_idx9" btree (c1)

在第一个 (主) 会话中:

PSQL
nik=# begin;
BEGIN

nik=*# select from t1 limit 0;
--
(0 rows)

我们打开了一个事务,执行了 SELECT from t1 — 请求 0 行和 0 列,但这已足够获取关系级锁。要查看这些锁,我们首先需要获取第一个会话的 PID,运行以下命令:

SQL
nik=*# select pg_backend_pid();
 pg_backend_pid
----------------
          73053
(1 row)

然后,在另一个会话中:

PSQL
nik=# select relation, relation::regclass as relname, mode, granted, fastpath
from pg_locks
where pid = 73053 and locktype = 'relation'
order by relname;
 relation |   relname   |      mode       | granted | fastpath
----------+-------------+-----------------+---------+----------
    74298 | t1          | AccessShareLock | t       | t
    74301 | t1_c1_idx   | AccessShareLock | t       | t
    74318 | t1_c1_idx1  | AccessShareLock | t       | t
    74319 | t1_c1_idx2  | AccessShareLock | t       | t
    74320 | t1_c1_idx3  | AccessShareLock | t       | t
    74321 | t1_c1_idx4  | AccessShareLock | t       | t
    74322 | t1_c1_idx5  | AccessShareLock | t       | t
    74323 | t1_c1_idx6  | AccessShareLock | t       | t
    74324 | t1_c1_idx7  | AccessShareLock | t       | t
    74325 | t1_c1_idx8  | AccessShareLock | t       | t
    74326 | t1_c1_idx9  | AccessShareLock | t       | t
    74327 | t1_c1_idx10 | AccessShareLock | t       | t
    74328 | t1_c1_idx11 | AccessShareLock | t       | t
    74337 | t1_c1_idx12 | AccessShareLock | t       | t
    74338 | t1_c1_idx13 | AccessShareLock | t       | t
    74339 | t1_c1_idx14 | AccessShareLock | t       | t
    74345 | t1_c1_idx20 | AccessShareLock | t       | f
    74346 | t1_c1_idx15 | AccessShareLock | t       | f
    74347 | t1_c1_idx16 | AccessShareLock | t       | f
    74348 | t1_c1_idx17 | AccessShareLock | t       | f
    74349 | t1_c1_idx18 | AccessShareLock | t       | f
    74350 | t1_c1_idx19 | AccessShareLock | t       | f
(22 rows)

注意事项:

我感

文章中提到的锁冲突表格如下,也挺清晰:

Runs concurrently with SELECT INSERT UPDATE DELETE CREATE INDEX CONC VACUUM ANALYZE CREATE INDEX CREATE TRIGGER ALTER TABLE DROP TABLE TRUNCATE VACUUM FULL
SELECT ✅ ✅ ✅ ✅ ✅ ❌
INSERT UPDATE DELETE ✅ ✅ ✅ ❌ ❌ ❌
CREATE INDEX CONC VACUUM ANALYZE ✅ ✅ ❌ ❌ ❌ ❌
CREATE INDEX ✅ ❌ ❌ ✅ ❌ ❌
CREATE TRIGGER ✅ ❌ ❌ ❌ ❌ ❌
ALTER TABLE DROP TABLE TRUNCATE VACUUM FULL ❌ ❌ ❌ ❌ ❌ ❌

另外,重锁会形成一个公平的等待队列 (这一点不用于行级锁)。如果进程试图获取与当前锁或与队列中其他进程已请求的锁不兼容的锁,那么这个进程便会加入队列。

讨论与补充

在 GitHub 查看讨论

支持作者

感谢支持 PostgreSQL Howto 的长期维护。

微信赞赏码

读者交流群

二维码若已过期,可通过公众号 PostgreSQL 学徒或邮件联系。

PostgreSQL 学徒读者交流群二维码