暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Postgresql数据库锁种类及模拟

原创 _ All China Database Union 2024-03-18
2155

在PostgreSQL中,有多种类型的锁用于控制对数据库对象的并发访问。以下是PostgreSQL中常见的锁类型:

一、表级锁(Table-level Locks):

在PostgreSQL中,主要存在以下几种表级锁:

1、ACCESS SHARE锁(共享锁):
  • 形成:SELECT语句默认获取的锁。
  • 影响:不阻塞其他事务的读写操作,但阻塞其他事务的ACCESS EXCLUSIVE锁。
  • 排查定位:通过查询pg_locks系统视图,锁模式字段为AccessShareLock。
  • 处置:通常无需特殊处理,语句执行完会自动释放。
t1=# select * from pg_locks;
  locktype  | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid  |      mode       | granted | fastpath | waitstart
------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-------+-----------------+---------+----------+-----------
 relation   |    24595 |    12073 |      |       |            |               |         |       |          | 3/2468             | 62636 | AccessShareLock | t       | t        |

2、ROW SHARE锁(行共享锁):
  • 形成:SELECT FOR UPDATE/FOR SHARE语句获取的锁。
  • 影响:不阻塞其他事务的读操作,但阻塞其他事务的写操作和EXCLUSIVE锁。
  • 排查定位:通过pg_locks视图,锁模式字段为RowShareLock。
  • 处置:语句执行完或事务提交/回滚后释放。避免长事务持有该锁。
t1=# \set AUTOCOMMIT off
t1=# \set
AUTOCOMMIT = 'off'

--session 
t1=# select * from t1 for update;

--session 2
postgres=# select * from pg_locks;
   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid  |      mode       | granted | fastpath | waitstart
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-------+-----------------+---------+----------+-----------
 relation      |    24595 |    24596 |      |       |            |               |         |       |          | 5/36               | 64912 | RowShareLock    | t       | t        |

3、ROW EXCLUSIVE锁(行排它锁):
  • 形成:INSERT、UPDATE、DELETE、TRUNCATE等DML语句获取的锁。
  • 影响:阻塞其他事务的写操作和SHARE锁,不阻塞读操作。
  • 排查定位:通过pg_locks视图,锁模式字段为RowExclusiveLock。
  • 处置:语句执行完或事务提交/回滚后释放。避免长事务持有排它锁。
--session 1
t1=# update t1 set name='bj' where id=1;
UPDATE 1
t1=*#

--session 2
t1=# update t1 set name='sh' where id=1;

--session 3

postgres=# select * from pg_locks;
   locktype    | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid  |       mode       | granted | fastpath |           waitstart
---------------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-------+------------------+---------+----------+-------------------------------
 relation      |    24595 |    24611 |      |       |            |               |         |       |          | 4/1000             | 67656 | RowExclusiveLock | t       | t        |
 virtualxid    |          |          |      |       | 4/1000     |               |         |       |          | 4/1000             | 67656 | ExclusiveLock    | t       | t        |
 relation      |    24595 |    24611 |      |       |            |               |         |       |          | 5/88               | 67169 | RowExclusiveLock | t       | t        |
 virtualxid    |          |          |      |       | 5/88       |               |         |       |          | 5/88               | 67169 | ExclusiveLock    | t       | t        |
 relation      |        5 |    12073 |      |       |            |               |         |       |          | 3/2485             | 68225 | AccessShareLock  | t       | t        |
 virtualxid    |          |          |      |       | 3/2485     |               |         |       |          | 3/2485             | 68225 | ExclusiveLock    | t       | t        |
 transactionid |          |          |      |       |            |           781 |         |       |          | 5/88               | 67169 | ExclusiveLock    | t       | f        |
 tuple         |    24595 |    24611 |    0 |     1 |            |               |         |       |          | 4/1000             | 67656 | ExclusiveLock    | t       | f        |
 transactionid |          |          |      |       |            |           782 |         |       |          | 4/1000             | 67656 | ExclusiveLock    | t       | f        |
 transactionid |          |          |      |       |            |           781 |         |       |          | 4/1000             | 67656 | ShareLock        | f       | f        | 2024-03-15 16:57:47.249866+08
(10 rows)

postgres=# select datid,datid,pid,usesysid,client_addr,client_hostname,client_port,state_change,wait_event_type,wait_event,backend_xid,query from pg_stat_activity where backend_type='client backend';
 24595 | 24595 | 67656 |       10 |             |                 |          -1 | 2024-03-15 16:57:47.24915+08  | Lock            | transactionid |         782 |          781 | update t1 set name='sh' where id=1;
 24595 | 24595 | 67169 |       10 |             |                 |          -1 | 2024-03-15 16:57:33.297348+08 | Client          | ClientRead    |         781 |              | update t1 set name='bj' where id=1;

可以看到pid为67656的等待事件类型为Lock,等待事件为transactionid,backend_xid为782,backend_xmin为781。而67169的等待事件类型为Client,等待事件为ClientRead,backend_xid为781。

[root@db01 ~]# ps -ef|grep 67656
postgres  67656  54128  0 16:56 ?        00:00:00 postgres: postgres t1 [local] UPDATE waiting
[root@db01 ~]# ps -ef|grep 67169
postgres  67169  54128  0 16:55 ?        00:00:00 postgres: postgres t1 [local] idle in transaction

通过后台进程也能看到两个会话的状态,一个处于事务空闲,一个处于等待。那么可以得出查询阻塞的办法为查询pg_activety_stat或者直接查询后台进程

select datid,datid,pid,usesysid,client_addr,client_hostname,client_port,state_change,wait_event_type,wait_event,backend_xid,backend_xmin,query from pg_stat_activity where backend_type='client backend' and backend_xmin in (select backend_xid from pg_stat_activity);

select datid,datid,pid,usesysid,client_addr,client_hostname,client_port,state_change,wait_event_type,wait_event,backend_xid,backend_xmin,query from pg_stat_activity where backend_type='client backend' and backend_xmin is not null;
4、SHARE UPDATE EXCLUSIVE锁(共享排它锁):
1. 形成共享排他锁的情况:
  • 当一个事务需要对表进行结构修改操作时,如ALTER TABLE语句,会请求共享排他锁。
  • 当一个事务需要在表上创建索引时,也会请求共享排他锁。
  • 当一个事务需要在表上执行VACUUM FULL或CLUSTER操作时,也会请求共享排他锁。
2. 共享排他锁的影响:
  • 持有共享排他锁的事务可以读取表中的数据,但不能修改数据。
  • 其他事务可以读取表中的数据,但不能获取排他锁或请求修改表结构的操作。
  • 如果有其他事务已经持有共享锁(如SELECT语句),则请求共享排他锁的事务会被阻塞,直到所有的共享锁被释放。
  • 如果有其他事务已经持有排他锁(如UPDATE、DELETE等),则请求共享排他锁的事务会被阻塞,直到排他锁被释放。
3. 定位排查共享排他锁:
  • 使用pg_locks系统视图查询当前的锁信息。可以通过查询pg_locks视图中的locktypemode列来识别共享排他锁。
  • 使用pg_stat_activity系统视图查询当前活动的会话和它们的状态。可以通过查询pg_stat_activity视图中的state列来识别被阻塞的会话。
  • 使用pg_blocking_pids()函数获取阻塞指定进程的进程ID列表。
  • 使用第三方工具如pgAdmin或者pg_top等来监控和分析锁的情况。
4. 处置共享排他锁:
  • 如果共享排他锁的持有时间较短,可以等待锁被释放。
  • 如果共享排他锁的持有时间较长,影响了其他事务的执行,可以考虑取消持有锁的事务。可以使用pg_terminate_backend()函数来终止指定的后台进程。
  • 优化应用程序的设计,尽量减少长时间持有共享排他锁的操作,例如将ALTER TABLE等操作放在非高峰期执行。
  • 调整锁超时参数,如lock_timeout,设置合适的超时时间,以避免长时间的锁等待。
  • 合理设计事务的隔离级别和访问模式,尽量避免不必要的锁冲突。
5、模拟
--session 1 
t1=# begin;
BEGIN
t1=*# lock table t1 in share row exclusive  mode;
LOCK TABLE
t1=*#

--session 2 

t1=# update t1 set name='wz' where id=1;

--session 3
postgres=# SELECT locktype, mode, pid, granted,relation FROM pg_locks;
  locktype  |         mode          |  pid  | granted | relation
------------+-----------------------+-------+---------+----------
 virtualxid | ExclusiveLock         | 56685 | t       |
 virtualxid | ExclusiveLock         | 56121 | t       |
 relation   | AccessShareLock       | 57443 | t       |    12073
 virtualxid | ExclusiveLock         | 57443 | t       |
 relation   | RowExclusiveLock      | 56121 | f       |    24611
 relation   | ShareRowExclusiveLock | 56685 | t       |    24611
(6 rows)


postgres=# select * from pg_stat_activity;
 datid | datname  |  pid  | leader_pid | usesysid | usename  | application_name | client_addr | client_hostname | client_port |         backend_start         |          xact_start
    |          query_start          |         state_change          | wait_event_type |     wait_event      |        state        | backend_xid | backend_xmin | query_id |
     query                    |         backend_type
-------+----------+-------+------------+----------+----------+------------------+-------------+-----------------+-------------+-------------------------------+---------------------------
----+-------------------------------+-------------------------------+-----------------+---------------------+---------------------+-------------+--------------+----------+---------------
------------------------------+------------------------------
       |          | 54134 |            |       10 | postgres |                  |             |                 |             | 2024-02-20 19:16:19.000608+08 |
    |                               |                               | Activity        | LogicalLauncherMain |                     |             |              |          |
                              | logical replication launcher
       |          | 54133 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.00146+08  |
    |                               |                               | Activity        | AutoVacuumMain      |                     |             |              |          |
                              | autovacuum launcher
     5 | postgres | 57443 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:49:10.794515+08 | 2024-03-18 14:56:58.358838
+08 | 2024-03-18 14:56:58.358838+08 | 2024-03-18 14:56:58.35884+08  |                 |                     | active              |             |          930 |          | select * from
pg_stat_activity;             | client backend
 24595 | t1       | 56685 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:47:17.344158+08 | 2024-03-18 14:48:51.503435
+08 | 2024-03-18 14:48:53.424111+08 | 2024-03-18 14:48:53.424306+08 | Client          | ClientRead          | idle in transaction |             |              |          | lock table t1
in share row exclusive  mode; | client backend
 24595 | t1       | 56121 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:45:37.264456+08 | 2024-03-18 14:48:57.319499
+08 | 2024-03-18 14:48:57.319537+08 | 2024-03-18 14:48:57.319537+08 | Lock            | relation            | active              |             |          930 |          | update t1 set
name='wz' where id=1;         | client backend
       |          | 54130 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.996776+08 |
    |                               |                               | Activity        | BgWriterHibernate   |                     |             |              |          |
                              | background writer
       |          | 54129 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.997351+08 |
    |                               |                               | Activity        | CheckpointerMain    |                     |             |              |          |
                              | checkpointer
       |          | 54132 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.000129+08 |
    |                               |                               | Activity        | WalWriterMain       |                     |             |              |          |
                              | walwriter
(8 rows)
postgres=# select pg_blocking_pids(56121);
 pg_blocking_pids
------------------
 {56685}
(1 row)

5、ACCESS EXCLUSIVE锁(排它锁):
1. 形成排他锁的情况:
  • 当一个事务对表进行写操作时,如INSERT、UPDATE、DELETE等语句,会请求排他锁。
  • 当一个事务对表进行DDL操作时,如ALTER TABLE、DROP TABLE等语句,会请求排他锁。
  • 当一个事务显式地使用LOCK TABLE语句请求排他锁时。
2. 排他锁的影响:
  • 持有排他锁的事务可以读取和修改表中的数据。
  • 其他事务无法获取该表上的共享锁或排他锁,也无法读取或修改该表中的数据,直到持有排他锁的事务释放锁。
  • 如果有其他事务已经持有共享锁(如SELECT语句),则请求排他锁的事务会被阻塞,直到所有的共享锁被释放。
3. 定位排查排他锁:
  • 使用pg_locks系统视图查询当前的锁信息。可以通过查询pg_locks视图中的locktypemode列来识别排他锁。
  • 使用pg_stat_activity系统视图查询当前活动的会话和它们的状态。可以通过查询pg_stat_activity视图中的state列来识别被阻塞的会话。
  • 使用pg_blocking_pids()函数获取阻塞指定进程的进程ID列表。
  • 使用第三方工具如pgAdmin或者pg_top等来监控和分析锁的情况。
4. 处置排他锁:
  • 如果排他锁的持有时间较短,可以等待锁被释放。
  • 如果排他锁的持有时间较长,影响了其他事务的执行,可以考虑取消持有锁的事务。可以使用pg_terminate_backend()函数来终止指定的后台进程。
  • 优化应用程序的设计,尽量减少长时间持有排他锁的操作,例如将大批量的写操作拆分为多个小批量操作。
  • 调整锁超时参数,如lock_timeout,设置合适的超时时间,以避免长时间的锁等待。
  • 合理设计事务的隔离级别和访问模式,尽量避免不必要的锁冲突。
5. 模拟排他锁:
--session 1
t1=# begin;
BEGIN
t1=*# lock table t1 in exclusive mode;
LOCK TABLE


--session 2 

t1=*# insert into t1 values(2,'zw');



postgres=# select * from pg_stat_activity;
 datid | datname  |  pid  | leader_pid | usesysid | usename  | application_name | client_addr | client_hostname | client_port |         backend_start         |          xact_start
    |          query_start          |         state_change          | wait_event_type |     wait_event      |        state        | backend_xid | backend_xmin | query_id |              q
uery               |         backend_type
-------+----------+-------+------------+----------+----------+------------------+-------------+-----------------+-------------+-------------------------------+---------------------------
----+-------------------------------+-------------------------------+-----------------+---------------------+---------------------+-------------+--------------+----------+---------------
-------------------+------------------------------
       |          | 54134 |            |       10 | postgres |                  |             |                 |             | 2024-02-20 19:16:19.000608+08 |
    |                               |                               | Activity        | LogicalLauncherMain |                     |             |              |          |
                   | logical replication launcher
       |          | 54133 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.00146+08  |
    |                               |                               | Activity        | AutoVacuumMain      |                     |             |              |          |
                   | autovacuum launcher
     5 | postgres | 57443 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:49:10.794515+08 | 2024-03-18 15:02:49.054994
+08 | 2024-03-18 15:02:49.054994+08 | 2024-03-18 15:02:49.054996+08 |                 |                     | active              |             |          930 |          | select * from
pg_stat_activity;  | client backend
 24595 | t1       | 56685 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:47:17.344158+08 | 2024-03-18 15:00:51.350741
+08 | 2024-03-18 15:01:01.310725+08 | 2024-03-18 15:01:01.31078+08  | Client          | ClientRead          | idle in transaction |             |              |          | lock table t1
in exclusive mode; | client backend
 24595 | t1       | 56121 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:45:37.264456+08 | 2024-03-18 15:00:15.735059
+08 | 2024-03-18 15:02:07.382543+08 | 2024-03-18 15:02:07.382544+08 | Lock            | relation            | active              |             |          930 |          | insert into t1
 values(2,'zw');   | client backend
       |          | 54130 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.996776+08 |
    |                               |                               | Activity        | BgWriterHibernate   |                     |             |              |          |
                   | background writer
       |          | 54129 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.997351+08 |
    |                               |                               | Activity        | CheckpointerMain    |                     |             |              |          |
                   | checkpointer
       |          | 54132 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.000129+08 |
    |                               |                               | Activity        | WalWriterMain       |                     |             |              |          |
                   | walwriter
(8 rows)



postgres=# select pg_blocking_pids(56121);
 pg_blocking_pids
------------------
 {56685}
(1 row)


postgres=# SELECT locktype, mode, pid, granted,relation FROM pg_locks;
  locktype  |       mode       |  pid  | granted | relation
------------+------------------+-------+---------+----------
 virtualxid | ExclusiveLock    | 56685 | t       |
 virtualxid | ExclusiveLock    | 56121 | t       |
 relation   | AccessShareLock  | 57443 | t       |    12073
 virtualxid | ExclusiveLock    | 57443 | t       |
 relation   | AccessShareLock  | 56121 | t       |    24611
 relation   | RowExclusiveLock | 56121 | f       |    24611
 relation   | ExclusiveLock    | 56685 | t       |    24611

二、行级锁(Row-level Locks)

1、FOR UPDATE锁
  1. FOR UPDATE锁的形成场景:

    • 当一个事务使用SELECT FOR UPDATE语句查询数据时,会在查询结果集中的每一行上获取FOR UPDATE锁。
    • FOR UPDATE锁通常用于实现悲观并发控制,确保事务在修改数据之前获取了排他访问权。
    • 常见的场景包括:
      • 读取数据并准备进行更新操作时,使用SELECT FOR UPDATE锁定要更新的行。
      • 在事务中读取数据,并希望防止其他事务同时修改或删除这些数据时,使用SELECT FOR UPDATE锁定查询结果集。
  2. FOR UPDATE锁的影响:

    • 持有FOR UPDATE锁的行可以被其他事务读取,但不能被其他事务修改或删除。
    • 如果一个事务尝试对持有FOR UPDATE锁的行进行修改或删除操作,它将被阻塞,直到持有FOR UPDATE锁的事务释放锁。
    • 多个事务不能同时持有同一行上的FOR UPDATE锁,它们会相互阻塞。
    • FOR UPDATE锁的持有时间从锁定行被查询开始,直到事务提交或回滚为止。
  3. 定位和排查FOR UPDATE锁:

    • 使用pg_locks系统视图查询当前的锁信息。可以通过查询pg_locks视图中的locktypemodepid列来识别FOR UPDATE锁和持有锁的事务。
    • 使用pg_stat_activity系统视图查询当前活动的会话和它们的状态。可以通过查询pg_stat_activity视图中的state列来识别被阻塞的会话。
    • 使用pg_blocking_pids()函数获取阻塞指定进程的进程ID列表。
  4. 处置FOR UPDATE锁问题:

    • 如果FOR UPDATE锁的持有时间较短,可以等待锁被释放。
    • 如果FOR UPDATE锁的持有时间较长,影响了其他事务的执行,可以考虑取消持有锁的事务。可以使用pg_terminate_backend()函数来终止指定的后台进程。
    • 优化应用程序的设计,尽量减少长时间持有FOR UPDATE锁的操作,例如将长时间运行的事务拆分为多个短事务。
    • 调整锁超时参数,如lock_timeout,设置合适的超时时间,以避免长时间的锁等待。
    • 合理设计事务的隔离级别和访问模式,尽量避免不必要的锁冲突。
  5. 模拟FOR UPDATE锁:

--session 1 
t1=*# select * from t1 where id=1 for update;
 id |         name
----+----------------------
  1 | zw
(1 row)


--session 2
t1=# update t1 set name='zw' where id=

查看

postgres=# select * from pg_stat_activity;
 datid | datname  |  pid  | leader_pid | usesysid | usename  | application_name | client_addr | client_hostname | client_port |         backend_start         |          xact_start
    |          query_start          |         state_change          | wait_event_type |     wait_event      |        state        | backend_xid | backend_xmin | query_id |
   query                  |         backend_type
-------+----------+-------+------------+----------+----------+------------------+-------------+-----------------+-------------+-------------------------------+---------------------------
----+-------------------------------+-------------------------------+-----------------+---------------------+---------------------+-------------+--------------+----------+---------------
--------------------------+------------------------------
       |          | 54134 |            |       10 | postgres |                  |             |                 |             | 2024-02-20 19:16:19.000608+08 |
    |                               |                               | Activity        | LogicalLauncherMain |                     |             |              |          |
                          | logical replication launcher
       |          | 54133 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.00146+08  |
    |                               |                               | Activity        | AutoVacuumMain      |                     |             |              |          |
                          | autovacuum launcher
     5 | postgres | 57443 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:49:10.794515+08 | 2024-03-18 16:02:36.5801+0
8   | 2024-03-18 16:02:36.5801+08   | 2024-03-18 16:02:36.580102+08 |                 |                     | active              |             |          935 |          | select * from
pg_stat_activity;         | client backend
 24595 | t1       | 56121 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:45:37.264456+08 | 2024-03-18 16:02:29.995787
+08 | 2024-03-18 16:02:29.995825+08 | 2024-03-18 16:02:29.995826+08 | Lock            | transactionid       | active              |         936 |          935 |          | update t1 set
name='zw' where id=1;     | client backend
 24595 | t1       | 71115 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 15:30:44.725943+08 | 2024-03-18 16:02:14.179856
+08 | 2024-03-18 16:02:24.694908+08 | 2024-03-18 16:02:24.695065+08 | Client          | ClientRead          | idle in transaction |         935 |              |          | select * from
t1 where id=1 for update; | client backend
       |          | 54130 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.996776+08 |
    |                               |                               | Activity        | BgWriterHibernate   |                     |             |              |          |
                          | background writer
       |          | 54129 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.997351+08 |
    |                               |                               | Activity        | CheckpointerMain    |                     |             |              |          |
                          | checkpointer
       |          | 54132 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.000129+08 |
    |                               |                               | Activity        | WalWriterMain       |                     |             |              |          |
                          | walwriter
(8 rows)

postgres=# SELECT locktype, mode, pid, granted,relation FROM pg_locks;
   locktype    |       mode       |  pid  | granted | relation
---------------+------------------+-------+---------+----------
 relation      | RowShareLock     | 71115 | t       |    24611
 virtualxid    | ExclusiveLock    | 71115 | t       |
 relation      | RowExclusiveLock | 56121 | t       |    24611
 virtualxid    | ExclusiveLock    | 56121 | t       |
 relation      | AccessShareLock  | 57443 | t       |    12073
 virtualxid    | ExclusiveLock    | 57443 | t       |
 transactionid | ShareLock        | 56121 | f       |
 transactionid | ExclusiveLock    | 71115 | t       |
 transactionid | ExclusiveLock    | 56121 | t       |
 tuple         | ExclusiveLock    | 56121 | t       |    24611
(10 rows)

postgres=# select pg_blocking_pids(56121);
 pg_blocking_pids
------------------
 {71115}
(1 row)

--session 3

postgres=# select pg_terminate_backend(71115);
 pg_terminate_backend
----------------------
 t
(1 row)

--session 2
t1=# update t1 set name='zw' where id=1;
UPDATE 1

2、FOR NO KEY UPDATE锁
  1. FOR NO KEY UPDATE锁的形成场景:

    • 当一个事务使用SELECT FOR NO KEY UPDATE语句查询数据时,会在查询结果集中的每一行上获取FOR NO KEY UPDATE锁。
    • FOR NO KEY UPDATE锁通常用于实现悲观并发控制,确保事务在修改数据之前获取了排他访问权,但允许其他事务创建外键引用。
    • 常见的场景包括:
      • 读取数据并准备进行更新操作时,使用SELECT FOR NO KEY UPDATE锁定要更新的行,同时允许其他事务创建外键引用。
      • 在事务中读取数据,并希望防止其他事务同时修改或删除这些数据,但允许创建外键引用时,使用SELECT FOR NO KEY UPDATE锁定查询结果集。
  2. FOR NO KEY UPDATE锁的影响:

    • 持有FOR NO KEY UPDATE锁的行可以被其他事务读取,但不能被其他事务修改或删除。
    • 如果一个事务尝试对持有FOR NO KEY UPDATE锁的行进行修改或删除操作,它将被阻塞,直到持有FOR NO KEY UPDATE锁的事务释放锁。
    • 多个事务不能同时持有同一行上的FOR NO KEY UPDATE锁,它们会相互阻塞。
    • 与FOR UPDATE锁不同的是,FOR NO KEY UPDATE锁允许其他事务在锁定行上创建外键引用,而不会被阻塞。
    • FOR NO KEY UPDATE锁的持有时间从锁定行被查询开始,直到事务提交或回滚为止。
  3. 定位和排查FOR NO KEY UPDATE锁:

    • 使用pg_locks系统视图查询当前的锁信息。可以通过查询pg_locks视图中的locktypemodepid列来识别FOR NO KEY UPDATE锁和持有锁的事务。
    • 使用pg_stat_activity系统视图查询当前活动的会话和它们的状态。可以通过查询pg_stat_activity视图中的state列来识别被阻塞的会话。
    • 使用pg_blocking_pids()函数获取阻塞指定进程的进程ID列表。
  4. 处置FOR NO KEY UPDATE锁问题:

    • 如果FOR NO KEY UPDATE锁的持有时间较短,可以等待锁被释放。
    • 如果FOR NO KEY UPDATE锁的持有时间较长,影响了其他事务的执行,可以考虑取消持有锁的事务。可以使用pg_terminate_backend()函数来终止指定的后台进程。
    • 优化应用程序的设计,尽量减少长时间持有FOR NO KEY UPDATE锁的操作,例如将长时间运行的事务拆分为多个短事务。
    • 调整锁超时参数,如lock_timeout,设置合适的超时时间,以避免长时间的锁等待。
    • 合理设计事务的隔离级别和访问模式,尽量避免不必要的锁冲突。
  5. 模拟FOR NO KEY UPDATE

--session 1
t1=*# select * from t1 for no key update;
 id |         name
----+----------------------
  1 | zw
(1 row)


--session 2
t1=# update t1 set name='yc' where id=1;



--session 3
t1=# select * from t1 for no key update;


postgres=# SELECT locktype, mode, pid, granted,relation FROM pg_locks;
   locktype    |       mode       |  pid  | granted | relation
---------------+------------------+-------+---------+----------
 relation      | RowShareLock     | 82618 | t       |    24611
 virtualxid    | ExclusiveLock    | 82618 | t       |
 relation      | RowExclusiveLock | 56121 | t       |    24611
 virtualxid    | ExclusiveLock    | 56121 | t       |
 relation      | RowShareLock     | 88614 | t       |    24611
 virtualxid    | ExclusiveLock    | 88614 | t       |
 relation      | AccessShareLock  | 57443 | t       |    12073
 virtualxid    | ExclusiveLock    | 57443 | t       |
 transactionid | ExclusiveLock    | 82618 | t       |
 tuple         | ExclusiveLock    | 88614 | f       |    24611
 transactionid | ShareLock        | 56121 | f       |
 tuple         | ExclusiveLock    | 56121 | t       |    24611
 transactionid | ExclusiveLock    | 56121 | t       |
(13 rows)

postgres=# select * from pg_stat_activity;
 datid | datname  |  pid  | leader_pid | usesysid | usename  | application_name | client_addr | client_hostname | client_port |         backend_start         |          xact_start
    |          query_start          |         state_change          | wait_event_type |     wait_event      |        state        | backend_xid | backend_xmin | query_id |
 query                |         backend_type
-------+----------+-------+------------+----------+----------+------------------+-------------+-----------------+-------------+-------------------------------+---------------------------
----+-------------------------------+-------------------------------+-----------------+---------------------+---------------------+-------------+--------------+----------+---------------
----------------------+------------------------------
       |          | 54134 |            |       10 | postgres |                  |             |                 |             | 2024-02-20 19:16:19.000608+08 |
    |                               |                               | Activity        | LogicalLauncherMain |                     |             |              |          |
                      | logical replication launcher
       |          | 54133 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.00146+08  |
    |                               |                               | Activity        | AutoVacuumMain      |                     |             |              |          |
                      | autovacuum launcher
     5 | postgres | 57443 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:49:10.794515+08 | 2024-03-18 16:25:29.411073
+08 | 2024-03-18 16:25:29.411073+08 | 2024-03-18 16:25:29.411075+08 |                 |                     | active              |             |          949 |          | select * from
pg_stat_activity;     | client backend
 24595 | t1       | 82618 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 16:05:38.636406+08 | 2024-03-18 16:24:24.643109
+08 | 2024-03-18 16:24:36.730856+08 | 2024-03-18 16:24:36.73097+08  | Client          | ClientRead          | idle in transaction |         949 |              |          | select * from
t1 for no key update; | client backend
 24595 | t1       | 56121 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:45:37.264456+08 | 2024-03-18 16:24:40.515319
+08 | 2024-03-18 16:24:40.515369+08 | 2024-03-18 16:24:40.515369+08 | Lock            | transactionid       | active              |         950 |          949 |          | update t1 set
name='yc' where id=1; | client backend
 24595 | t1       | 88614 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 16:23:25.77185+08  | 2024-03-18 16:24:44.978784
+08 | 2024-03-18 16:24:44.978784+08 | 2024-03-18 16:24:44.978786+08 | Lock            | tuple               | active              |             |          949 |          | select * from
t1 for no key update; | client backend
       |          | 54130 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.996776+08 |
    |                               |                               | Activity        | BgWriterHibernate   |                     |             |              |          |
                      | background writer
       |          | 54129 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.997351+08 |
    |                               |                               | Activity        | CheckpointerMain    |                     |             |              |          |
                      | checkpointer
       |          | 54132 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.000129+08 |
    |                               |                               | Activity        | WalWriterMain       |                     |             |              |          |
                      | walwriter
(9 rows)



postgres=# select pg_blocking_pids(88614);
 pg_blocking_pids
------------------
 {56121}
(1 row)

postgres=# select pg_blocking_pids(56121);
 pg_blocking_pids
------------------
 {82618}
(1 row)

postgres=# select pg_blocking_pids(82618);
 pg_blocking_pids
------------------
 {}
(1 row)

3、FOR SHARE锁
  1. FOR SHARE锁的形成场景:

    • 当一个事务使用SELECT FOR SHARE语句查询数据时,会在查询结果集中的每一行上获取FOR SHARE锁。
    • FOR SHARE锁通常用于实现悲观并发控制,确保事务在读取数据时获取了共享访问权,防止其他事务同时修改或删除这些数据。
    • 常见的场景包括:
      • 在事务中读取数据,并希望防止其他事务同时修改或删除这些数据时,使用SELECT FOR SHARE锁定查询结果集。
      • 当多个事务需要同时读取相同的数据,但不允许同时修改或删除这些数据时,使用SELECT FOR SHARE锁定查询结果集。
  2. FOR SHARE锁的影响:

    • 持有FOR SHARE锁的行可以被其他事务读取,但不能被其他事务修改或删除。
    • 如果一个事务尝试对持有FOR SHARE锁的行进行修改或删除操作,它将被阻塞,直到持有FOR SHARE锁的事务释放锁。
    • 多个事务可以同时持有同一行上的FOR SHARE锁,它们之间不会相互阻塞。
    • FOR SHARE锁允许其他事务在锁定行上创建外键引用,而不会被阻塞。
    • FOR SHARE锁的持有时间从锁定行被查询开始,直到事务提交或回滚为止。
  3. 定位和排查FOR SHARE锁:

    • 使用pg_locks系统视图查询当前的锁信息。可以通过查询pg_locks视图中的locktypemodepid列来识别FOR SHARE锁和持有锁的事务。
    • 使用pg_stat_activity系统视图查询当前活动的会话和它们的状态。可以通过查询pg_stat_activity视图中的state列来识别被阻塞的会话。
    • 使用pg_blocking_pids()函数获取阻塞指定进程的进程ID列表。
  4. 处置FOR SHARE锁问题:

    • 如果FOR SHARE锁的持有时间较短,可以等待锁被释放。
    • 如果FOR SHARE锁的持有时间较长,影响了其他事务的执行,可以考虑取消持有锁的事务。可以使用pg_terminate_backend()函数来终止指定的后台进程。
    • 优化应用程序的设计,尽量减少长时间持有FOR SHARE锁的操作,例如将长时间运行的事务拆分为多个短事务。
    • 调整锁超时参数,如lock_timeout,设置合适的超时时间,以避免长时间的锁等待。
    • 合理设计事务的隔离级别和访问模式,尽量避免不必要的锁冲突。
  5. 模拟FOR SHARE锁:

--session 1
t1=# begin;
BEGIN
t1=*# select * from t1 for share;
 id |         name
----+----------------------
  1 | zw
(1 row)


--session 2
t1=*# update t1 set name='zw' where id=1;


--session 3
t1=*# select * from t1;
 id |         name
----+----------------------
  1 | zw
(1 row)

t1=*# begin;
WARNING:  there is already a transaction in progress
BEGIN
t1=*# select * from t1 for share;
 id |         name
----+----------------------
  1 | zw
(1 row)

查看锁


postgres=# select * from pg_stat_activity;
 datid | datname  |  pid  | leader_pid | usesysid | usename  | application_name | client_addr | client_hostname | client_port |         backend_start         |          xact_start
    |          query_start          |         state_change          | wait_event_type |     wait_event      |        state        | backend_xid | backend_xmin | query_id |
 query                |         backend_type
-------+----------+-------+------------+----------+----------+------------------+-------------+-----------------+-------------+-------------------------------+---------------------------
----+-------------------------------+-------------------------------+-----------------+---------------------+---------------------+-------------+--------------+----------+---------------
----------------------+------------------------------
       |          | 54134 |            |       10 | postgres |                  |             |                 |             | 2024-02-20 19:16:19.000608+08 |
    |                               |                               | Activity        | LogicalLauncherMain |                     |             |              |          |
                      | logical replication launcher
       |          | 54133 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.00146+08  |
    |                               |                               | Activity        | AutoVacuumMain      |                     |             |              |          |
                      | autovacuum launcher
     5 | postgres | 57443 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:49:10.794515+08 | 2024-03-18 16:16:23.115689
+08 | 2024-03-18 16:16:23.115689+08 | 2024-03-18 16:16:23.11569+08  |                 |                     | active              |             |          938 |          | select * from
pg_stat_activity;     | client backend
 24595 | t1       | 82618 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 16:05:38.636406+08 | 2024-03-18 16:13:27.003727
+08 | 2024-03-18 16:13:29.771336+08 | 2024-03-18 16:13:29.771445+08 | Client          | ClientRead          | idle in transaction |         938 |              |          | select * from
t1 for share;         | client backend
 24595 | t1       | 56121 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 14:45:37.264456+08 | 2024-03-18 16:13:35.123584
+08 | 2024-03-18 16:13:37.899357+08 | 2024-03-18 16:13:37.899359+08 | Lock            | transactionid       | active              |         939 |          938 |          | update t1 set
name='zw' where id=1; | client backend
 24595 | t1       | 85869 |            |       10 | postgres | psql             |             |                 |          -1 | 2024-03-18 16:15:03.244609+08 | 2024-03-18 16:15:26.97929+
08  | 2024-03-18 16:15:53.171367+08 | 2024-03-18 16:15:53.171648+08 | Client          | ClientRead          | idle in transaction |         940 |              |          | select * from
t1 for share;         | client backend
       |          | 54130 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.996776+08 |
    |                               |                               | Activity        | BgWriterHibernate   |                     |             |              |          |
                      | background writer
       |          | 54129 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:18.997351+08 |
    |                               |                               | Activity        | CheckpointerMain    |                     |             |              |          |
                      | checkpointer
       |          | 54132 |            |          |          |                  |             |                 |             | 2024-02-20 19:16:19.000129+08 |
    |                               |                               | Activity        | WalWriterMain       |                     |             |              |          |
                      | walwriter
(9 rows)

postgres=# SELECT locktype, mode, pid, granted,relation FROM pg_locks;
   locktype    |       mode       |  pid  | granted | relation
---------------+------------------+-------+---------+----------
 relation      | AccessShareLock  | 85869 | t       |    24611
 relation      | RowShareLock     | 85869 | t       |    24611
 virtualxid    | ExclusiveLock    | 85869 | t       |
 relation      | RowShareLock     | 82618 | t       |    24611
 virtualxid    | ExclusiveLock    | 82618 | t       |
 relation      | RowExclusiveLock | 56121 | t       |    24611
 virtualxid    | ExclusiveLock    | 56121 | t       |
 relation      | AccessShareLock  | 57443 | t       |    12073
 virtualxid    | ExclusiveLock    | 57443 | t       |
 transactionid | ExclusiveLock    | 82618 | t       |
 transactionid | ExclusiveLock    | 85869 | t       |
 transactionid | ExclusiveLock    | 56121 | t       |
 transactionid | ShareLock        | 56121 | f       |
 tuple         | ExclusiveLock    | 56121 | t       |    24611
(14 rows)

postgres=# select pg_blocking_pids(56121);
 pg_blocking_pids
------------------
 {82618}
(1 row)

4、FOR KEY SHARE
  1. FOR KEY SHARE锁的形成场景:

    • 当一个事务使用SELECT FOR KEY SHARE语句查询数据时,会在查询结果集中的每一行上获取FOR KEY SHARE锁。
    • FOR KEY SHARE锁通常用于实现悲观并发控制,确保事务在读取数据时获取了共享访问权,防止其他事务同时修改或删除这些数据,同时允许其他事务在这些行上获取FOR KEY SHARE锁或FOR SHARE锁。
    • 常见的场景包括:
      • 在事务中读取数据,并希望防止其他事务同时修改或删除这些数据,但允许其他事务同时读取这些数据时,使用SELECT FOR KEY SHARE锁定查询结果集。
      • 当多个事务需要同时读取相同的数据,并且允许其他事务同时获取FOR KEY SHARE锁或FOR SHARE锁时,使用SELECT FOR KEY SHARE锁定查询结果集。
  2. FOR KEY SHARE锁的影响:

    • 持有FOR KEY SHARE锁的行可以被其他事务读取,但不能被其他事务修改或删除。
    • 如果一个事务尝试对持有FOR KEY SHARE锁的行进行修改或删除操作,它将被阻塞,直到持有FOR KEY SHARE锁的事务释放锁。
    • 多个事务可以同时持有同一行上的FOR KEY SHARE锁,它们之间不会相互阻塞。
    • FOR KEY SHARE锁允许其他事务在锁定行上获取FOR KEY SHARE锁或FOR SHARE锁,而不会被阻塞。
    • FOR KEY SHARE锁的持有时间从锁定行被查询开始,直到事务提交或回滚为止。
  3. 定位和排查FOR KEY SHARE锁:

    • 使用pg_locks系统视图查询当前的锁信息。可以通过查询pg_locks视图中的locktypemodepid列来识别FOR KEY SHARE锁和持有锁的事务。
    • 使用pg_stat_activity系统视图查询当前活动的会话和它们的状态。可以通过查询pg_stat_activity视图中的state列来识别被阻塞的会话。
    • 使用pg_blocking_pids()函数获取阻塞指定进程的进程ID列表。
  4. 处置FOR KEY SHARE锁问题:

    • 如果FOR KEY SHARE锁的持有时间较短,可以等待锁被释放。
    • 如果FOR KEY SHARE锁的持有时间较长,影响了其他事务的执行,可以考虑取消持有锁的事务。可以使用pg_terminate_backend()函数来终止指定的后台进程。
    • 优化应用程序的设计,尽量减少长时间持有FOR KEY SHARE锁的操作,例如将长时间运行的事务拆分为多个短事务。
    • 调整锁超时参数,如lock_timeout,设置合适的超时时间,以避免长时间的锁等待。
    • 合理设计事务的隔离级别和访问模式,尽量避免不必要的锁冲突。
  5. 模拟FOR KEY SHARE锁:

--session 1

t1=# begin;
BEGIN
t1=*# select * from t1 for key share;
 id |         name
----+----------------------
  1 | zw
(1 row)



--session 2 
t1=# update t1 set name='zw' where id=1;

--session 3 
t1=*# select * from t1 for share;
 id |         name
----+----------------------
  1 | zw
(1 row)

t1=*# select * from t1 for key share;
 id |         name
----+----------------------
  1 | zw
(1 row)

t1=*# select * from t1;
 id |         name
----+----------------------
  1 | zw
(1 row)

t1=*# select * from t1 for update;

postgres=# SELECT locktype, mode, pid, granted,relation FROM pg_locks;
   locktype    |       mode       |  pid  | granted | relation
---------------+------------------+-------+---------+----------
 relation      | RowShareLock     | 82618 | t       |    24611
 virtualxid    | ExclusiveLock    | 82618 | t       |
 relation      | RowExclusiveLock | 56121 | t       |    24611
 virtualxid    | ExclusiveLock    | 56121 | t       |
 relation      | RowShareLock     | 88614 | t       |    24611
 virtualxid    | ExclusiveLock    | 88614 | t       |
 relation      | AccessShareLock  | 57443 | t       |    12073
 virtualxid    | ExclusiveLock    | 57443 | t       |
 transactionid | ShareLock        | 88614 | f       |
 transactionid | ExclusiveLock    | 82618 | t       |
 tuple         | ExclusiveLock    | 88614 | t       |    24611
 transactionid | ExclusiveLock    | 56121 | t       |
(12 rows)

6、兼容模式

Requested Lock Mode	FOR KEY SHARE 	FOR SHARE	FOR NO KEY UPDATE	FOR UPDATE
FOR KEY SHARE	 	 	 						X
FOR SHARE	 	 						X	X
FOR NO KEY UPDATE	 			X			X	X
FOR UPDATE		X			X			X	X

三、页级锁

这个锁与开发人员关系不大,主要用于数据块放入内存。类似latch: cache buffers chains、buffer pin、cache buffers lru chain 等。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论