第五章 PostgreSQL中的并发控制

本文深入探讨了PostgreSQL中的并发控制机制,包括事务ID、元组结构、插入、删除和更新的操作,以及提交日志(clog)的工作原理。文章详细阐述了事务快照、可见性检查以及防止更新丢失的策略,特别是如何在可序列化快照隔离(SSI)中实现真正的可序列化。通过对PostgreSQL中MVCC实现的解析,读者可以理解其在并发环境下如何维护一致性和隔离性。
摘要由CSDN通过智能技术生成

最近搞了一个公众号PostgreSQL运维技术,欢迎来踩~

悄悄放一张:

PostgreSQL运维技术 

原文:https://www.interdb.jp/pg/pgsql5.html

 

本章主要介绍PostgreSQL中的并发控制。

注:本文是The Internals of PostgreSQL的第5章

(https://www.interdb.jp/pg/pgsql05.html)


 

并发控制是一种当多个事务在数据库中并发运行时,它可以维护一致性和隔离性的机制。一致性和隔离性我们都知道,这是ACID的两个属性。

 

一般来说,有三种广泛的并发控制技术,即多版本并发控制(MVCC)、严格两阶段锁定(S2PL)和乐观并发控制(OCC)。

每种技术都有许多变体。在MVCC中,每次写操作都会创建一个数据项的新版本,同时保留旧版本。当事务读取数据项时,系统选择其中一个版本,以确保各个事务的隔离。MVCC的主要优点是“读取器不会阻塞写入器,写入器也不会阻塞读取器”,相反,当写入器写入一个条目时,基于s2pl的系统必须阻塞读取器,因为写入器获得了该条目的排他锁。PostgreSQL和一些rdbms使用MVCC的变体,称为快照隔离(SI)。

 

为了实现SI,一些rdbms,例如Oracle,使用回滚段。当写入一个新的数据项时,该项的旧版本被写入回滚段,随后新项被覆盖到数据区域。PostgreSQL使用一种更简单的方法。一个新的数据项被直接插入到相关的表页中。当读取条目时,PostgreSQL通过应用可见性检查规则来选择条目的适当版本以响应单个事务。

 

SI不允许ANSI SQL-92标准中定义的三种异常,即脏读、不可重复读和幻读。然而,SI不能实现真正的可序列化性,因为它允许序列化异常,比如写倾斜和只读事务倾斜。请注意,基于经典的可串行性定义的ANSI SQL-92标准并不等同于现代理论中的定义。为了解决这个问题,在9.1版中添加了可序列化快照隔离(SSI)。SSI可以检测序列化异常,并解决由此类异常引起的冲突。因此,PostgreSQL 9.1及更高版本提供了一个真正可序列化的隔离级别。(另外,SQL Server也使用SSI, Oracle仍然只使用SI。)

 

本章包含以下几个主题:

5.1~5.3   事务ID和元组结构等基本概念,以及元组是如何插入、删除和更新的。

5.4~5.6   介绍提交日志(clog)

5.7~5.9   检查可见性、如何防止更新丢失,SSI

5.10   VACUUM


本章主要关注PG中的MVCC实现,关于死锁预防和锁定模式的内容按下不表。

 

PostgreSQL使用SSI实现DML(数据操作语言,如SELECT、UPDATE、INSERT、DELETE),使用2PL实现DDL(数据定义语言,如CREATE TABLE等)。

 

 

5.1. 事务ID


每当事务开始时,事务管理器就会分配一个被称为事务id (txid)的唯一标识符。PostgreSQL的txid是一个32位无符号整数,大约是42亿(万亿)。如果在事务开始后执行内置的txid_current()函数,该函数将返回当前的txid,如下所示。

testdb=# BEGIN;BEGIN
testdb=# SELECT txid_current();
 txid_current 
--------------
          100(1 row)

 

0表示无效txid。

1表示引导txid,它只用于数据库集群的初始化。

2表示冻结的txid,如章节5.10.1所述。

Txids可以相互比较。例如,在txid 100的点上,大于100的txid是“在未来”,它们在txid为100中是不可见的;txids小于100是“过去”和可见(图5.1 a))。

图5.1 PostgreSQL中的事务id

图片

图片来源:

https://www.interdb.jp/pg/pgsql05.html

 

由于txid空间在实际系统中不足,PostgreSQL将txid空间视为一个圆。之前的21亿txids是“在过去”,接下来的21亿txids是“在未来”(图5.1 b)。

注:txid回卷问题在章节5.10.1中描述。

另外:BEGIN命令没有分配txid。在PostgreSQL中,当BEGIN命令执行后第一个命令执行时,事务管理器分配一个tixd,然后它的事务开始。

 

5.2. 元组结构

 

表页中的堆元组分为普通数据元组和TOAST元组。本节只介绍常用的元组。

一个堆元组由三部分组成,即HeapTupleHeaderData结构、 NULL bitmap和用户数据(图5.2)。

 

图5.2 元组结构

图片

 

图片来源:

https://www.interdb.jp/pg/pgsql05.html

 

注:HeapTupleHeaderData结构在src/include/access/htup_details.h中定义。

虽然HeapTupleHeaderData结构包含7个字段,但在MVCC只需要以下4个字段。

t_xmin:保存插入该元组的事务的txid。

t_xmax:保存删除或更新该元组的事务的txid。如果这个元组没有被删除或更新,t_xmax将被设置为0,这意味着无效。

t_cid:保存命令id (cid),这意味着在从0开始的当前事务中执行这个命令之前执行了多少SQL命令。例如,假设我们在一个事务中执行三个INSERT命令:'BEGIN;插入;插入;插入;Commit;”。如果第一个命令插入这个元组,t_cid被设置为0。如果第二个命令插入这个,t_cid将被设置为1,依此类推。

t_ctid:保存着元组标识符(tid),它指向自己或一个新的元组。第1.3节中描述的tid用于标识表中的元组。当这个元组被更新时,这个元组的t_ctid指向新的元组;否则,t_ctid指向自身。

 

5.3. 插入、删除、更新元组

 

本节介绍元组如何插入、删除和更新。然后,简要介绍了用于元组插入和更新的自由空间映射(FSM)。

为了关注元组,页头和行指针不在下面表示。图5.3展示了元组如何表示的示例。

图5.3 元组的表示

图片

 

5.3.1 插入

 

通过插入操作,一个新的元组被直接插入到目标表的页面中(图5.4)。

 

图5.4 元组插入

图片

假设一个元组被txid为99的事务插入到一个页面中。在本例中,插入的元组的头字段设置如下。

Tuple_1:

t_xmin被设置为99,因为这个元组是由txid 99插入的。

t_xmax设置为0,因为这个元组还没有被删除或更新。

t_cid被设置为0,因为这个元组是txid 99插入的第一个元组。

t_ctid被设置为(0,1),它指向自身,因为这是最新的元组。

 

PostgreSQL提供了一个pageinspect 扩展,它是一个 contribution 模块,用来显示数据库页面的内容。

testdb=# CREATE EXTENSION pageinspect;
CREATE EXTENSION
t
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值