• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

PostgreSQL 事务和并发控制

武飞扬头像
0x13
帮助1

Read Uncommitted(读未提交):

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果,在多用户数据库中,脏读是非常危险的,在并发情况下,查询结果非常不可控,即使不考虑结果的严谨性只追求性能,它的性能也并不比其他事务隔离级别好多少,可以说脏读没有任何好处。所以未提交读这一事务隔离级别很少用于实际应用。

Read Committed(读已提交):

这是PostgreSQL的默认隔离级别,它满足了一个事务只能看见已经提交事务对关联数据所做的改变的隔离需求。

Repeatable Read(可重复读):

确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。

Serializable(可序列化):

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

标准SQL隔离级别:

学新通

PostgreSQL隔离级别:

学新通

可以发现,PostgreSQL的读未提交和读已提交的行为模式相同,默认读已提交,重复读也解决了幻读问题。

标准的SQL隔离级别下,标准的事务实现下都是提供非阻塞读来实现读未提交这个事务隔离级别,因为在写时不阻塞读,所以可能会产生脏读问题。但是postgreSQL这里通过其他办法实现了非阻塞读同时避免了脏读问题,所以他的读未提交和读已提交的行为是一样,即postgreSQL相当于只实现了三种事务隔离级别。PostgreSQL支持在事务开启时临时设置这个事务的隔离级别。

【基于锁的并发控制】

同样的有排他锁X和共享锁S。

排它锁:被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象。

共享锁:被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。

封锁对象的大小称为封锁粒度(Granularity)。封锁的对象可以是逻辑单元,也可以是物理单元。以关系数据库为例子,封锁对象可以是这样一些逻辑单元:属性值、属性值的集合、元组、关系、索引项、整个索引项甚至整个数据库;也可以是这样的一些物理单元:页(数据页或索引页)、物理记录等。封锁的策略是一组规则,这些规则阐明了事务何时对数据项进行加锁和解锁,通常称为封锁协议(Locking Protocol)。由于采用了封锁策略,一次只能执行一个事务,所以只会产生串行调度,迫使事务只能等待前面的事务结束之后才可以开始,所以基于锁的并发控制机制导致性能低下,并发程度低,所以pg的策略是提供一系列锁由用户自行调用。

Advisory Lock(咨询锁)是postgresql特有的锁,有十多种类,按照生效范围、锁类型、申请和释放区分:

学新通

* 1,申请/释放:有申请就有释放,Session级别的锁需要显式释放,随着连接的关闭自动释放;事务级别的锁也需要显式释放,或者会随着事务的结束(提交或者回滚)一并释放

* 2,锁类型:共享锁和排它锁,比如pg_advisory_lock是排它锁,pg_advisory_lock_shared是共享锁

* 3,生效范围:Session级的或者事务级的,很好理解,比如pg_advisory_lock是添加Session级的排它锁,pg_advisory_xact_lock是申请事务级排它锁

* 4,参数个数,这个看概念是有点蒙的,有的锁函数是1个参数,有的是2个参数,一个参数的情况下,锁是库级别的,举个例子就很容易理解了。

Name

Return Type

Description

pg_advisory_lock(key bigint)

void

获取独占会话级通知锁

pg_advisory_lock(key1 int, key2 int)

void

获取独占会话级通知锁

pg_advisory_lock_shared(key bigint)

void

获取共享会话级通知锁

pg_advisory_lock_shared(key1 int, key2 int)

void

获取独共享话级通知锁

pg_advisory_unlock(key bigint)

boolean

释放独占会话级通知锁

pg_advisory_unlock(key1 int, key2 int)

boolean

释放独占会话级通知锁

pg_advisory_unlock_all()

void

释放所有独占会话级通知锁

pg_advisory_unlock_shared(key bigint)

boolean

释放共享会话级通知锁

pg_advisory_unlock_shared(key1 int, key2 int)

boolean

释放共享会话级通知锁

pg_advisory_xact_lock(key bigint)

void

获取独占事务级通知锁

pg_advisory_xact_lock(key1 int, key2 int)

void

获取独占事务级通知锁

pg_advisory_xact_lock_shared(key bigint)

void

获取共享事务级通知锁

pg_advisory_xact_lock_shared(key1 int, key2 int)

void

获取共享事务级通知锁

pg_try_advisory_lock(key bigint)

boolean

获取独占会话级通知锁(如果可用)

pg_try_advisory_lock(key1 int, key2 int)

boolean

获取独占会话级通知锁(如果可用)

pg_try_advisory_lock_shared(key bigint)

boolean

获取共享会话级通知锁(如果可用)

pg_try_advisory_lock_shared(key1 int, key2 int)

boolean

获取共享会话级通知锁(如果可用)

pg_try_advisory_xact_lock(key bigint)

boolean

获得独占事务级通知锁(如果可用)

pg_try_advisory_xact_lock(key1 int, key2 int)

boolean

获得独占事务级通知锁(如果可用)

pg_try_advisory_xact_lock_shared(key bigint)

boolean

获得共享事务级通知锁(如果可用)

pg_try_advisory_xact_lock_shared(key1 int, key2 int)

boolean

获得共享事务级通知锁(如果可用)

学新通

PostgreSQL从8.2版本就开始支持advisory lock这一功能。那么什么是advisory lock呢?官方文档的解释是:

PostgreSQL提供了一种方法创建由应用定义其含义的锁。这种锁被称为咨询锁,因为系统并不强迫其使用——而是由应用来保证其正确的使用。

【基于版本的并发控制】

基于锁的并发控制机制要么延迟一项操作,要么中止发出该操作的事务来保证可串行性。如果每一数据项的旧值副本保存在系统中,这些问题就可以避免。这种基于多个旧值版本的并发控制即MVCC。

一般把基于锁的并发控制机制称成为悲观机制,而把MVCC机制称为乐观机制。这是因为锁机制是一种预防性的机制,读会阻塞写,写也会阻塞读,当封锁粒度较大,时间较长时并发性能就不会太好;而MVCC是一种后验性的机制,读不阻塞写,写也不阻塞读,等到提交的时候才检验是否有冲突,由于没有锁,所以读写不会相互阻塞,避免了大粒度和长时间的锁定,能更好地适应对读的响应速度和并发性要求高的场景,大大提升了并发性能。

常见的数据库如Oracle、PostgreSQL、MySQL(Innodb)都使用MVCC并发控制机制。在MVCC 中,每一个写操作创建一个新的版本。当事务发出一个读操作时,并发控制管理器选择一个版本进行读取。也就是为数据增加一个关于版本的标识,在读取数据时,连同版本号一起读出,在更新时对此版本号加一。

读写相互不阻塞:读只会读当前事务能可见的版本,而写是在新版本上面操作。

PostgreSQL

每一个事务分配一个递增的、类型为 nt32 整型数作为唯一事务ID ,称为xid。同时每行数据都会保存操作这条数据的事务id信息。

学新通

* xmin,当前处于active状态的最小事务编号;

* xmax,未来产生的事务中,第一个将被分配的事务编号;

* xip_list,当前处于active 状态的事务列表(包括in progress和future状态的事务),其余为inactive。

例如:

#select txid_current_snapshot();

txid_current_snapshot

-----------------------

639:642:639,641

1.xmin=639,表示当前时刻快照中最小的是639这个事务。小于该编号的事务都已经终止(提交、回滚或异常终止),这些事务属于“过去的”范围区域。

2.xmax=642,表示将来新事务产生时分配到的第一个事务编号txid,大于等于642的事务未产生,属于“将来的”范围区域。

3.xip_list=(639,641),表示该快照时刻639和641这两个事务正处于active状态,属于“当前的”范围区域。

在PostgreSQL中,提交读级别下,session中同一事务的每条SQL执行的时候都会自动去读取当前时刻的事务快照;而在可重读级别下,session中同一事务只会在事务开始的第一个SQL获取一次事务快照。

因为读提交级别下,同一事务中不同时刻的SQL获取的快照可能不一样,因此读到的数据可能会不一样。而重复读在整个事务周期只获取一次事务快照,所以同一事务内所有SQL使用的快照都是一致的,因此可以实现重复读,规避了幻读的产生。

如何产生多个版本:

INSERT一条数据:

创建版本=v1,xmin=a,xmax=null。

UPDATE这条数据:

修改版本=v1,xmin=a,xmax=b。

创建版本=v2,xmin=c,xmax=null。

所以更新数据相当于在版本v1上设置xmax标志为删除,然后新建一个版本v2,这个时候就存在两个版本了。这两个版本有各自的xip_list,来标识该版本对某个事务是否可见。

多版本MVVC的数据膨胀问题:

因为UPDATE操作是新建一个新的数据版本,极端情况下对全表所有数据执行UPDATE那么数据所占用磁盘是理论的两倍。postgresql通过pg_repack的机制来解决膨胀问题。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfkfcbc
系列文章
更多 icon
同类精品
更多 icon
继续加载