zoukankan      html  css  js  c++  java
  • postgresql中的咨询锁(advisory lock)

    咨询锁(advisory lock),有的地方翻译为顾问锁,作为Postgresql中一种特有的锁,关于对其介绍,仅从咨询锁的描述性定义来看,一开始还真的没明白这个咨询锁是干什么的。

    暂时抛开咨询锁的概念,先说数据库中传统的锁机制。
    默认情况下的事务性锁,读/写会自动加锁,读/写完成后会自动解锁(加解锁机制在细节上复杂),这是一种隐式的锁机制,Postgresql也不例外。
    对于加锁后的并发控制,也就是默认的写不阻塞读,是通过MVCC解决的,这种锁完全不需要认为干预。
    相对于隐式锁机制和MVCC并发控制机制,咨询锁可以认为是一种显式锁,需要人为地控制,这类锁需要显式的申请和释放,在使用这类锁的时候,可以自行控制读写的排他性。

    什么场景下使用显式锁?
    比如想实现写阻塞读,或者读阻塞读的场景,因为默认的隐式锁加上MVCC机制,是做不到的。
    实际业务类型需求的场景也很多:一个经典的问题,并发情况下,对唯一键的存性判断,然后决定存在则更新,不存在则插入这种逻辑,就需要咨询锁,默认的MVCC下是做不到的,当然也不是说咨询锁只能做这个事儿。
    再举个例子:多线程编程中的线程共享变量,在读写共享变量时需要线程锁做控制(比如python的lock.acquire()),完成之后释放锁,咨询锁就有点这个味道(当然不完全相同),这些都是隐式锁无法完成的。

    查看问官方文档的时候还是吓了一跳,Postgresql有这么多类型的咨询锁。

    Table 9-73. Advisory Lock Functions

    NameReturn TypeDescription
    pg_advisory_lock(key bigint) void Obtain exclusive session level advisory lock
    pg_advisory_lock(key1 intkey2 int) void Obtain exclusive session level advisory lock
    pg_advisory_lock_shared(key bigint) void Obtain shared session level advisory lock
    pg_advisory_lock_shared(key1 intkey2 int) void Obtain shared session level advisory lock
    pg_advisory_unlock(key bigint) boolean Release an exclusive session level advisory lock
    pg_advisory_unlock(key1 intkey2 int) boolean Release an exclusive session level advisory lock
    pg_advisory_unlock_all() void Release all session level advisory locks held by the current session
    pg_advisory_unlock_shared(key bigint) boolean Release a shared session level advisory lock
    pg_advisory_unlock_shared(key1 intkey2 int) boolean Release a shared session level advisory lock
    pg_advisory_xact_lock(key bigint) void Obtain exclusive transaction level advisory lock
    pg_advisory_xact_lock(key1 intkey2 int) void Obtain exclusive transaction level advisory lock
    pg_advisory_xact_lock_shared(key bigint) void Obtain shared transaction level advisory lock
    pg_advisory_xact_lock_shared(key1 intkey2 int) void Obtain shared transaction level advisory lock
    pg_try_advisory_lock(key bigint) boolean Obtain exclusive session level advisory lock if available
    pg_try_advisory_lock(key1 intkey2 int) boolean Obtain exclusive session level advisory lock if available
    pg_try_advisory_lock_shared(key bigint) boolean Obtain shared session level advisory lock if available
    pg_try_advisory_lock_shared(key1 intkey2 int) boolean Obtain shared session level advisory lock if available
    pg_try_advisory_xact_lock(key bigint) boolean Obtain exclusive transaction level advisory lock if available
    pg_try_advisory_xact_lock(key1 intkey2 int) boolean Obtain exclusive transaction level advisory lock if available
    pg_try_advisory_xact_lock_shared(key bigint) boolean Obtain shared transaction level advisory lock if available
    pg_try_advisory_xact_lock_shared(key1 intkey2 int) boolean Obtain shared transaction level advisory lock if available
    其实细看下去,并不复杂,按照“申请/释放,生效范围”,锁类型,参数个数,等待行为,这个咨询锁从几个维度分类之后,还是比较清晰的。
    所有的咨询锁函数都是这几个维度的不同组合,只要弄清楚这些锁的不同维度,上面表格中洋洋洒洒的数十个锁函数,加上备注,理解起来还是比较容易的。
     
    如下对生效范围,锁类型,申请/释放,参数个数,等待行为逐一解释:
    • 1,申请/释放:有申请就有释放,Session级别的锁需要显式释放,随着连接的关闭自动释放;事务级别的锁也需要显式释放,或者会随着事务的结束(提交或者回滚)一并释放
    • 2,锁类型:共享锁和排它锁,比如pg_advisory_lock是排它锁,pg_advisory_lock_shared是共享锁
    • 3,生效范围:Session级的或者事务级的,很好理解,比如pg_advisory_lock是添加Session级的排它锁,pg_advisory_xact_lock是申请事务级排它锁
    • 4,参数个数,这个看概念是有点蒙的,有的锁函数是1个参数,有的是2个参数,一个参数的情况下,锁是库级别的,举个例子就很容易理解了
      SessionA
      dbtest=> select pg_advisory_lock(id),* from t_advisory1 where id = 1;
       pg_advisory_lock | id
      ------------------+----
                  | 1
      (1 row)
       
      SessionB
      dbtest=>select pg_advisory_lock(id),* from t_advisory2 where id = 1;
      --当前Session一直被挂起,或者说阻塞,直到SessionA解锁。
      这里的两个Session是在两个不同的表上申请的相同的Id的锁,但是SessionB一样会被阻塞,这个就是解释了pg_advisory_lock在一个参数的时候,是一个库级别的锁。
      如果想要设置一个同一个表的同一个Id的锁,相信聪明的少侠一定知道该怎么办了,pg_advisory_lock这个函数重载的两个参数的方法,就是在另外一个维度定义锁定信息的。
      这里说的两个参数,可以从不同维度定义锁定目标,而不是单单为了表级别的锁定。
    • 5,等待行为,对于锁的申请,其结果有两种可能性,1是申请到了,2是没有申请到,对于没有申请到的情况,有两中可选行为,要么一直等下去,要么不等了直接返回表面没申请到
      对于上面所说的,SessionB因为无法获取Id上的排它锁,导致挂起的行为,对应用程序表现的不太友好,也容易造成长时间持有连接造成数据库连接的暴增,如何破解?
      如果注意上述列表中锁函数的返回值,就会返现,有一部分返回值是void,一部分返回值是boolean,返回boolean的方法就是可以根据锁定目标时,根据返回值来判断是否成功锁定。
      对于范围值为boolean的函数,请求发起后都会立即返回,只不过是如果成功申请到了锁,返回T(true),如果没有成功申请到锁,返回F(False)
      这样的话,处理起来就比较灵活一点,而不是在申请不到锁的时候,Session处于一直挂起的状态,用流行专业的术语说就是Session一直hang起(一直不怎么敢用hang这个词,感觉都是大神才能用的)
     
    比如pg_try_advisory_xact_lock(key1 int, key2 int)这个锁,就是:非等待模式_申请_一个参数_事务级_排他锁
    这样一来,需要什么类型的锁,或者一个是某个函数实现什么类型的锁效果,从这几个维度区分后,就比较清楚了,而不需要逐个尝试其效果。
    以上简单总结了Postgresql中咨询锁的概念和用法,咨询锁作为一种显式定义的锁,青脆爽口,轻便灵活,为处理不同逻辑提供了一定的方便性,但也需要使用咨询锁时的潜在的问题。

    简单测试一把
    --锁定某个表的某一行
    db01=# select pg_try_advisory_lock(cast('t1'::regclass::oid as int),id),id from t1 where id = 1;
     pg_try_advisory_lock | id
    ----------------------+----
     t                    |  1
    (1 row)
    
    --解锁锁定某个表的某一行
    db01=# select pg_advisory_unlock(cast('t1'::regclass::oid as int),id),id from t1 where id = 1;
     pg_advisory_unlock | id
    --------------------+----
     t                  |  1
    (1 row)
    
    --直接基于变量的锁定
    db01=# select pg_try_advisory_lock(100,1);
     pg_try_advisory_lock
    ----------------------
     t
    (1 row)
    
    --同一个Session内可以重复锁定
    db01=# select pg_try_advisory_lock(100,1);
     pg_try_advisory_lock
    ----------------------
     t
    (1 row)
    
    --解锁,解锁成功返回t
    db01=# select pg_advisory_unlock(100,1);
     pg_advisory_unlock
    --------------------
     t
    (1 row)
    
    --解锁,解锁成功返回t,多次加锁后需要多次解锁,如果脑袋没问题的话,相信没人会在一个Session或者事务里连续对一个Id加锁,虽然postgre支持这么干
    db01=# select pg_advisory_unlock(100,1);
     pg_advisory_unlock
    --------------------
     t
    (1 row)
    
    --如果解锁的时候锁不存在,解锁失败
    db01=# select pg_advisory_unlock(100,1);
    WARNING:  you don''t own a lock of type ExclusiveLock
     pg_advisory_unlock
    --------------------
     f
    (1 row)
    
    
    db01=#
    
    
    --如果上一个Session的排它锁解锁之前,其他Session尝试加锁,直接返回失败
    db01=# select pg_try_advisory_lock(100,1);
     pg_try_advisory_lock
    ----------------------
     f
    (1 row)
    
    
    --活动锁的查看
    select locktype as lc,relation::regclass as relname,page||','||tuple as ctid,virtualxid ,transactionid as txid,virtualtransaction,pid,mode,granted from pg_locks ;
     
    if you want do something well, understand it well first.
     
  • 相关阅读:
    深入理解javascript原型和闭包(3)——prototype原型
    深入理解javascript原型和闭包(2)——函数和对象的关系
    深入理解javascript原型和闭包(1)——一切都是对象
    js 基本类型与引用类型的区别
    一次完整的HTTP事务是怎样一个过程
    PHP+MySql+jQuery实现的“顶”和“踩”投票功能
    PHP获得真实客户端的真实时用到的IP REMOTE_ADDR,HTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR
    Jenkins设置自动发邮件
    Jenkins+SVN+maven+Tomcat构建自动化集成任务
    Jenkins详细安装教程
  • 原文地址:https://www.cnblogs.com/wy123/p/13499526.html
Copyright © 2011-2022 走看看