PostgreSQL并发问题

事务的隔离级别

在不考虑隔离性的前提下,事务的并发可能会出现的问题:

  • 脏读:读到了其他事务未提交的数据(这种情况必须避免)。
  • 不可重复读:同一事务中,多次查询同一数据,结果不一致,因为其他事务修改造成的(一些业务中不可重复读不是问题)。
  • 幻读:同一事务中,多次查询同一数据,因为其他事务对数据进行了增删吗,导致出现了一些问题(一些业务中幻读不是问题)。

针对这些并发问题,关系型数据库有一些事务的隔离级别,一般用4种。

  • READ UNCOMMITTED:读未提交(啥用没有,并且PostgreSQL没有,提供了只是为了完整性);
  • READ COMMITTED:读已提交,可以解决脏读(PostgreSQL默认隔离级别);
  • REPEATABLE READ:可重复读,可以解决脏读和不可重复读(MySQL默认隔离级别,PostgreSQL也提供了,但是设置为可重复读,效果还是串行化);
  • SERIALIZABLE:串行化,问题都解决了,使用了锁,效率慢。

PostgreSQL在老版本中,只有两个隔离级别,读已提交和串行化,在PostgreSQL中就不存在脏读问题。

MVCC

如果一个数据库,频繁的进行读写操作,为了保证安全,采用锁的机制。但如果采用锁机制,如果一些事务在写数据,另外一个事务就无法读数据,会造成读写之间相互阻塞。 大多数的数据库都会采用 多版本并发控制MVCC 来解决这个问题。

比如你要查询一行数据,但是这行数据正在被修改,事务还没提交,如果此时对这行数据加锁,会导致其他的读操作阻塞,需要等待。如果采用PostgreSQL,他的内部会针对这一行数据保存多个版本,如果数据正在被写入,包就保存之前的数据版本。让读操作去查询之前的版本,不需要阻塞。等写操作的事务提交了,读操作才能查看到最新的数据。 这几个及时可以确保 读写操作没有冲突 ,这个就是MVCC的主要特点。

写写操作,和MVCC没关系,那个就是加锁的方式

注意:这里的MVCC是基于读已提交的,如果是串行化,那就读不到了。

在操作之前,先了解一下PostgreSQL中,每条数据都会自带两个字段:

  • xmin:给当前事务分配的数据版本。如果有其他事务做了写操作,并且提交事务了,就给xmin分配新的版本。
  • xmax:当前事务没有存在新版本,xmax就是0。如果有其他事务做了写操作,未提交事务,将写操作的版本放到xmax中。事务提交后,xmax会分配到xmin中,然后xmax归0。

xmin与xmax

基于上图的操作查看一波效果:

  • 事务A:
1
2
3
4
5
6
7
8
9
10
11
12
-- 左,事务A
--1、开启事务
begin;
--2、查询某一行数据, xmin = 630,xmax = 0
select xmin,xmax,* from test where id = 8;
--3、每次开启事务后,会分配一个事务ID 事务id=631
select txid_current();
--7、修改id为8的数据,然后在本事务中查询 xmin = 631, xmax = 0
update test set name = '铃铛' where id = 8;
select xmin,xmax,* from test where id = 8;
--9、提交事务
commit;
  • 事务B:
1
2
3
4
5
6
7
8
9
10
11
-- 右,事务B
--4、开启事务
begin;
--5、查询某一行数据, xmin = 630,xmax = 0
select xmin,xmax,* from test where id = 8;
--6、每次开启事务后,会分配一个事务ID 事务id=632
select txid_current();
--8、事务A修改完,事务B再查询 xmin = 630 xmax = 631
select xmin,xmax,* from test where id = 8;
--10、事务A提交后,事务B再查询 xmin = 631 xmax = 0
select xmin,xmax,* from test where id = 8;