PG-事务、并发和锁

1 事务

1. 1 ACID

原子性(Atomicity):事务必须以一个整体单元的形式进行工作,对于其数据的修改,要么全都执行,要么全都不执行。如果只执行事务中多个操作的前半部分就出现错误,那么必须回滚所有的操作,让数据在逻辑上回滚到原先的状态。

·一致性(Consistency):在事务完成时,必须使所有的数据都保持在一致状态。
·隔离性(Isolation):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务是不会查看中间状态的数据的。
·持久性(Durability):事务完成之后,它对于系统的影响是永久性的。即使今后出现致命的系统故障(如机器重启、断电),数据也将
一直保持
在PostgreSQL中,可使用多版本并发控制(MVCC)来维护数据的一致性。相比于锁定模型,其主要优点是在MVCC下对检索(读)数据的锁请求与写数据的锁请求不冲突,读不会阻塞写,而写也从不阻塞读。在PostgreSQL中也提供了表和行级别的锁定语句,让应用能更方便地操作并发数据

DDL事务

在PostgreSQL中,与其他数据库最大的不同是,大多数DDL也是可以包含在一个事务中的,而且也是可以回滚的。该功能非常适合把PostgreSQL作为Sharding的分布式数据系统的底层数据库。比如,在Sharding中,常常需要在多个节点中建相同的表,此时可以考虑把建表语句放在同一个事务中,这样就可以在各个节点上先启动一个事务,然后再执行建表语句,如果某个节点执行失败,也可以回滚前面已执行建表成功的操作,自然就不会出现部分节点建表成功,部分节点建表失败的情况

1. 2 事务的使用方法

1)关闭自动提交
postgres=# \set AUTOCOMMIT off
postgres=# \echo :AUTOCOMMIT
off
postgres=# insert into testtab01 values(1);
INSERT 0 1
postgres=# insert into testtab01 values(2);
INSERT 0 1
postgres=# select * from testtab01;
id
----
1
2
(2 rows)
postgres=# rollback;
ROLLBACK
postgres=# select * from testtab01;
id
----
(0 rows)



2)通过使用BEGIN语句来启动一个事务,这也相当于关闭了自动提交功能:
postgres=# begin;
BEGIN
postgres=# insert into testtab01 values(1);
INSERT 0 1
postgres=# insert into testtab01 values(2);
INSERT 0 1
postgres=# select * from testtab01;
id
----
1
2
(2 rows)
postgres=# rollback;
ROLLBACK
postgres=# select * from testtab01;
id
----
(0 rows)


1.3 事务隔离级别

数据库的事务隔离级别有以下4种。
·READ UNCOMMITTED:读未提交。
·READ COMMITTED:读已提交。
·REPEATABLE READ:重复读。
·SERIALIZABLE:串行化。
对于并发事务,我们不希望发生不一致的情况,这类情况的级别从高到低排序如下。
·脏读:一个事务读取了另一个未提交事务写入的数据。
·不可重复读:指一个事务重新读取前面读取过的数据时,发现该数据已经被另一个已提交事务修改了。在大多数情况下,这还是可以接
受的,只是在少数情况下会出现问题。
·幻读:一个事务开始后,需要根据数据库中现有的数据做一些更新,于是重新执行一个查询,返回一套符合查询条件的行,这时发现这
些行因为其他最近提交的事务而发生了改变,此时现有的事务如果再进行下去的话就可能会导致数据在逻辑上的一些错误。

2. 锁机制

2.1 行级锁模式

行级锁模式比较简单,只有两种,即“共享锁”“排它锁”,或者可以说是“读锁”或“写锁”。而在PostgreSQL中不称其为“读锁”的原因是,由于有多版本的实现,所以实际读取行数据时,并不会在行上执行任何锁(包括“读锁”)

--死锁的发生必须具备以下4个必要条件
·互斥条件:指事务对所分配到的资源加了排它锁,即在一段时间内只能由一个事务加锁占用。如果此时还有其他进程请求排它锁,则请求者只能等待,直至持有排它锁的事务释放排它锁。

·请求和保持条件:指事务已经至少持有了一把排它锁,但又提出了新的排它锁请求,而该资源上的排它锁已被其他事务占有,此时请求被阻塞,但同时它对自己已获得的排它锁又持有不放。

·不剥夺条件:指事务已获得的锁在未使用完之前不能被其他进程剥夺,只能在使用完时由自己释放。

·环路等待条件:指在发生死锁时,必然存在一个事务—资源的环形链,即事务集合{T0,T1,T2,…,Tn}中的T0正在等待T1持有的排它锁;P1正在等待P2持有的排它锁,……,Pn正在等待已被P0持有的排它锁。

理解了死锁的原因,尤其是死锁产生的4个必要条件,就可以最大可能地避免、预防和解除死锁。
防止死锁最好的方法通常是保证使用一个数据库的所有应用都以相同的顺序在多个对象上请求排它锁。比如,在应用编程中,人为规定在一个事务中只能以一个固定顺序来更新表。假设在数据库中,有A、B、C、D 4张表,现规定只能按如下顺序修改这几张表:B→C→A→D,若某个进程先更新了A表,如果想更新C表,则必须先回滚先前对A表的更新,然后再按规定的顺序,先更新C表,再更新A表。

由于数据库可以自动检测出死锁,所以应用也可以通过捕获死锁异常来处理死锁。但这个方法并不是很好,因为数据库检测死锁需要付出一定的代价,可能会导致应用程序过久地持有排它锁而使系统的并发处理能力下降。排它锁持有的时间越长也就越容易导致死锁,所以在进行程序设计时要尽量短时间地持有排它锁。



·lockmode:就是前面介绍的几种表级锁模式,即ACCESS SHARE、ROW SHARE、ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE、ACCESS EXCLUSIVE。
·NOWAIT:如果没有NOWAIT这个关键字,当无法获得锁时会一直等待,而如果加了NOWAIT关键字,在无法立即获取该锁时,此命令会立即退出并且报错。
在PostgreSQL中,事务自己的锁是从不冲突的,因此一个事务可以在持有SHARE模式的锁时再请求ROW EXCLUSIVE锁,而不会出现自己的锁阻塞自己的情况。
当事务要更新表中的数据时,应该申请ROW EXCLUSIVE锁,而不应该申请SHARE锁,因为在更新数据时,事务还是会对表加ROWEXCLUSIVE锁,想象一下,在两个并发的事务都请求SHARE锁后,开始更新数据前要对表加ROW EXCLUSIVE锁,但由于各自先前已加了SHARE锁,所以都要等待对方释放SHARE锁,因而出现死锁。如果涉及多种锁模式,那么事务应该总是最先请求最严格的锁模式,否则就容易出现死锁

2.2 锁的查看

可通过查询系统视图“pg_locks”来得到
在这里插入图片描述

“transactionid”代表事务ID,简写为“xid”,“virtualxid”代表虚拟事务ID,简写为“vxid”。每产生一个事务ID,都会在pg_clog下的commit log
文件中占用2bit。最早在PostgreSQL中是没有虚拟事务ID的,但后来发现,有一些事务根本没有产生任何实质的变更,如一个只读事务或一个空事务,若在这种情况下也分配一个事务ID会造成资源浪费,于是提出了虚拟事务ID的概念。对于这类只读事务,只分配一个虚拟事务ID,而不实际分配一个真实的事务ID,这样就不需要在commit log中占用2bit的空间了。

·virtualtransaction字段之前的字段(不包括virtualtransaction字段),我们称其为“第一部分”。
·virtualtransaction字段之后的字段(包括virtualtransaction字段)我们称其为“第二部分”。第一部分字段用于描述锁定对象(Locked Object)的信息,第二部分字段描述的是持有锁或等待锁session的信息。

了解了上面的概念,就容易理解“virtualxid”和“virtualtransaction”这两个字段的意思了,“virtualxid”在第一部分字段中,表示锁对象是一个“virtualxid”,而“virtualtransaction”表示持有锁或等待锁session的虚拟事务ID。

2.3 案例

1)先开一个psql的窗口
postgres=# select pg_backend_pid();

2)开第二个psql窗口
postgres=# select pg_backend_pid();

3)在第一个psql窗口中锁定一张表:
postgres=# begin;
BEGIN
postgres=# lock table testtab01;
LOCK TABLE

4)过在第三个psql窗口中运行的SQL命令查看数据库中的锁的情况
select locktype,
relation::regclass as rel,
virtualxid as vxid,
transactionid as xid ,
virtualtransaction as vxid2,
pid,
mode,
granted
from pg_locks
where pid = xxx;


4)执行加锁命令后,有时会在系统表上产生一些附加的锁。这些系统表上的锁在系统第一次启动后可以看到,而第二次运行后就看不到了。如果从第一个窗口中退出,再重新进去,重新执行“lock table testtab01”命令

5
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韶博雅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值