美文网首页
pg并发控制

pg并发控制

作者: 奥利奥蘸墨水 | 来源:发表于2022-05-24 17:37 被阅读0次

    当多个事务同时在数据库中运行时,并发控制是一种用于维持一致性与隔离性的技术,一致性与隔离性是ACID的两个属性。

    ACID指数据库事务正确执行的四个基本要素的缩写:

    • 原子性(Atomicity)
    • 一致性(Consistency)
    • 隔离性(lsolation)
    • 持久性(Durability)

    从宽泛的意义上讲,有三种并发控制技术,分别是多版本并发控制、严格两阶段锁定和乐观并发控制。每项技术都有多种变体。

    PostgreSQL中的事务隔离等级

    事务隔离等级

    事务标识

    每当事务开始时,事务管理器就会为其分配一个成为事务标识的唯一标识符。pg的txid是一个32位无符号整数,取之空间大小约为42亿。在事务启动后执行内置的txid_current()函数,即可获取当前事务的txid。

    获取事务txid

    pg保留三个特殊的txid:

    • 0表示无效的txid
    • 1表示初始起动的txid,仅用于数据库集群的初始化过程
    • 2表示冻结的txid,仅用于数据库集群的初始化过程

    txid可以相互比较大小。且在逻辑上无限。

    txid并非是在begin命令执行时分配的。在pg中当执行begin命令后的第一条命令时,事务管理器才会分配txid,并真正启动其事务。

    元组结构

    堆元组可分为普通元组和TOAST元组两类。下面介绍普通元组。

    堆元组由三个部分组成,即HeapTupleHeaderData结构,控制位图及用户数据。

    元组结构

    其中有四个字段需要了解:

    • t_xmin保存插入此元组的事务的txid。
    • t_xmax保存删除或更新此元组的事务的txid。如果尚未删除或更新此元组,则t_xmax设置为0,即无效。
    • t_cid保存命令标识(cid) ,cid的意思是在当前事务中,执行当前命令之前执行了多少sql命令,从0开始计数。
    • t_ctid保存着指向自身或新元组的元组标识符(tid)。tid用于标识表中的元组。在更新该元组时,t_ctid会指向新版本的元组,否则t_ctid会指向自己。

    元组的增、删、改

    元组的具体表示如图:

    元组的具体表示

    插入

    在插入操作中,新元组将直接插入目标表的页面中:

    插入的新元组

    假设元组时由txid=99的事务插入页面中的,在这种情况下,被插入元组的首部字段会依一下步骤设置。

    Tuple_1:

    • t_xmin设置为99,因为此远足由txid=99的事务所插入。
    • t_xmax设置为0,因为此元组尚未被删除或更新。
    • t_cid设置为0,因为此元组时由txid=99的事务所执行的第一条命令插入的。
    • t_ctid设置为(0,1),指向自身,因为这是该元组的最新版本。

    pg自带了一个第三方贡献的扩展模块pageinsepect,可以用于检查数据库页面的具体内容。

    检查数据库页面的具体内容

    删除

    在删除操作中,目标元组只是在逻辑上被标记为删除。目标元组的t_xmax字段将被设置为执行delete命令事务的txid。

    删除元组

    假设Tuple_1被txid=111的事务删除。在这种情况下,Tuple_1首部字段t_xmax被设为111。

    如果txid=111的事务已经提交,就不一定要Tuple_1。通常不需要的元组在pg中被称为死元组。

    死元组最终将从页面中被移除。清除死元组的过程被称为清理(VACUUM)过程。

    更新

    在更新操作中,pg在逻辑上实际执行的是删除最新的元组,并插入一条新的元组。

    更新元组

    过程如图所示,就不再详细讨论。

    需要理解的是:如果txid=100的事务已经提交,那么Tuple_1和Tuple_2就成了死元组,而如果txid=100的事务种植,Tuple_2和Tuple_3就成了死元组。

    空闲空间映射

    插入堆或索引元组时,pg使用表与索引相应的FSM来选择可供插入的页面。

    表和索引都有各自的FSM。每个FSm存储着相应表或索引文件中每个页面可用空间容量的信息。

    所有FSM都以后缀存储,在需要时他们会被加载到共享内存中。

    扩展pg_freespacemap能提供特定表或索引上的空闲空间信息。一下查询列出了特性表中每个页面的空闲率。

    testdb=# CREATE EXTENSION pg_freespacemap;
    CREATE EXTENSION
    
    testdb=# SELECT *, round(100 * avail/8192 ,2) as "freespace ratio"
                    FROM pg_freespace('accounts');
     blkno | avail | freespace ratio 
    -------+-------+-----------------
         0 |  7904 |           96.00
         1 |  7520 |           91.00
         2 |  7136 |           87.00
         3 |  7136 |           87.00
         4 |  7136 |           87.00
         5 |  7136 |           87.00
    ....
    

    提交日志

    pg在提交日志(CLOG)中保存事务的状态。提交日志分配与共享内存中,并用于事务处理过程的全过程。

    事务状态

    pg定义了4中事务状态,即

    • IN_PROGRESS
    • COMMITTED
    • ABORTED
    • SUB_COMMITTED

    前三种状态很好理解。而SUB_COMMITTED状态用于子事务。这里不详细描述。

    提交日志如何工作

    提交日志(CLOG)在逻辑上是一个数组,由共享内存中一路系列8KB页面组成。数组的序号索引对应着相应事务的标识,其内容则是相应事务的状态。

    CLOG结构

    T1:txid 200提交;txid 200的状态从IN_PROGRESS变为COMMITTED。

    T2:txid 201终止;txid 201的状态从INPROGRESS变为ABORTED。

    txid不断前进,当CLOG空间耗尽无法存储新的事务状态时,就会追加分配一个新的页面。

    当需要获取事务的状态时,pg将调用相应内部函数读取CLOG,并返回所请求的事务的状态。

    提交日志的维护

    当pg关机或执行存档过程时,CLOG数据会写入pg_clog子目录下的文件中。这些文件被命名为0000,0001等。文件最大尺寸为256KB。

    当pg启动时会加载存储在pg_clog中的文件,用其数据初始化CLOG。

    CLOG的大小会不断增长,因为只要CLOG一天慢就会追加新的页面。但并非所有数据都是必要的。

    事务快照

    事务快照是一个数据集,存储着某个特定事务在某个时间点所看到的状态信息:哪些事务处于活跃状态。活跃状态意味着事务正在进行中或还没有开始。

    内置函数txid_current_snapshot及其文本表示

    获取快照信息

    txid_current_snapshot的文本标识是xmin:xmax:xip_list,各部分描述如下。

    • xmin

      最早仍然活跃的事务的txid。所有比它更早的事务(txid < xmin),要么已经提交并可见,要么已经回滚并生成死元组。

    • xmax

      第一个尚未分配的txid。所有txid>=xmax的事务在获取快照时尚未启动,因此结果对当前事务不可见。

    • xip_list

      获取快照时活跃事务的txid列表。该列表仅包括xmin和xmax之间的txid。

    例如,在快照100 : 104 : 100, 102中,xmin是100,xmax是104, 而xip_list为100,102。


    两个例子

    事务快照时由事务管理器提供的。在READ COMMITTED隔离级别,事务在执行每条SQL时都会获取快照,在其他情况下,事务只会在执行第一条SQL命令时获取一次快照。获取的事务快照用于元组的可见性检查。

    使用获取的快照进行可见性检查时,所有活跃的事务都必须被当成IN_PROGRESS的事务同等对待,无论他们实际上是否已经提交或终止。这条规则非常重要,因为它正是READ COMMITTED和REPEATABLE READ/SERIALIZABLE隔离级别中表现差异的根本来源

    可见性检查规则

    可见性检查规则是一组规则,用于确定一条元组是否对一个事务可见,可见性检查会用到元组的t_min和t_xmax,提交日志CLOG,以及已获取的事务快照。所选规则有10调,可以分为三种情况。

    t_xmin的状态为ABORTED

    t_xmin的状态为ABORTED的元组始终不可见,因为插入此元组的事务已中止。

     /* t_xmin status == ABORTED */
    Rule 1: IF t_xmin status is 'ABORTED' THEN
                      RETURN 'Invisible'
                END IF
    

    该规则明确表示为以下数学表达式。

    规则1: If Status(t_xmin) = ABORTED ⇒ Invisible

    t_xmin的状态为IN_PROGRESS

    t_xmin状态为INPROGRESS的元组基本上是不可见的(规则3和规则4),但在一个条件下例外。

    /* t_xmin status == IN_PROGRESS */
                  IF t_xmin status is 'IN_PROGRESS' THEN
                       IF t_xmin = current_txid THEN
    Rule 2:              IF t_xmax = INVALID THEN
                      RETURN 'Visible'
    Rule 3:              ELSE  /* this tuple has been deleted or updated by the current transaction itself. */
                      RETURN 'Invisible'
                             END IF
    Rule 4:        ELSE   /* t_xmin ≠ current_txid */
                      RETURN 'Invisible'
                       END IF
                 END IF
    

    如果该元组被另一个进行中的事务插入,则该元组显然是不可见的。(规则3)

    如果t_min等于当前事务的txid(即当前事务插入了该元组),且t_xmax != 0,则该元组是不可见的,因为它已被当前事务更新或删除。(规则2)

    有个例外是,当前事务插入此元组且t_xmax = 0(当前元组尚未被更新或删除)。在这种情况下,此元组对当前事务可见。

    规则2: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax = INVAILD ⇒ Visible

    规则3: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax ≠ INVAILD ⇒ Invisible

    规则4: If Status(t_xmin) = IN_PROGRESS ∧ t_xmin ≠ current_txid ⇒ Invisible

    t_xmin状态为COMMITTED

    t_xmin状态为COMMITTED的元组时可见的,但在三个条件下除外。

     /* t_xmin status == COMMITTED */
                IF t_xmin status is 'COMMITTED' THEN
    Rule 5:      IF t_xmin is active in the obtained transaction snapshot THEN
                          RETURN 'Invisible'
    Rule 6:      ELSE IF t_xmax = INVALID OR status of t_xmax is 'ABORTED' THEN
                          RETURN 'Visible'
                     ELSE IF t_xmax status is 'IN_PROGRESS' THEN
    Rule 7:           IF t_xmax =  current_txid THEN
                                RETURN 'Invisible'
    Rule 8:           ELSE  /* t_xmax ≠ current_txid */
                                RETURN 'Visible'
                          END IF
                     ELSE IF t_xmax status is 'COMMITTED' THEN
    Rule 9:           IF t_xmax is active in the obtained transaction snapshot THEN
                                RETURN 'Visible'
    Rule 10:         ELSE
                                RETURN 'Invisible'
                          END IF
                     END IF
                END IF
    

    规则5:If Status(t_xmin) = COMMITTED ∧ Snapshot(t_xmin) = active ⇒ Invisible

    规则6:If Status(t_xmin) = COMMITTED ∧ (t_xmax = INVALID ∨ Status(t_xmax) = ABORTED) ⇒ Visible

    规则7:If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = IN_PROGRESS ∧ t_xmax = current_txid ⇒ Invisible

    规则8:If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = IN_PROGRESS ∧ t_xmax ≠ current_txid ⇒ Visible

    规则9: If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = COMMITTED ∧ Snapshot(t_xmax) = active ⇒ Visible

    规则10:If Status(t_xmin) = COMMITTED ∧ Status(t_xmax) = COMMITTED ∧ Snapshot(t_xmax) ≠ active ⇒ Invisible

    相关文章

      网友评论

          本文标题:pg并发控制

          本文链接:https://www.haomeiwen.com/subject/kqzjprtx.html