暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL中wal日志具备幂等性吗?

晟数学苑 2021-09-02
1617

数据库日志与幂等性

  《The Internals of PostgreSQL》这本书中发现这样一段话:                      

  In short, the redo  (replay)  operation of non-backup block is not idempotent.  Therefore,  to preserve the correct replaying order, non-backup block records should replay if and only  if  its LSN is greater than the corresponding  page’s pd_lsn.

  On the other hand, as the redo operation of backup block is idempotent, backup blocks can be replayed any number of times regardless of its LSN.


  看到这句话我感觉还是挺奇怪的,PG的wal日志的非备份区块竟然不具备幂等性,而与之类似的例如Oracle中的redo我们都知道是幂等的,难道PG的wal日志并不是这样的吗?


  这里简单说明下什么是幂等性。在数学上,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。而对于数据库的日志而言指的便是该日志可以重复回放多次且结果仍然相同。


  那么为什么Oracle的redo日志就是具有幂等性呢,而像我们熟悉的MySQL的binlog就是不具备幂等性的,这又是为什么呢?主要还是因为前者是物理日志而后者是逻辑日志。


物理日志与逻辑日志

  物理日志与逻辑日志在存储内容上有很大的区别,存储内容是区分它们的最重要手段。


物理日志

  存储数据库中特定记录的变更,通常是 page oriented,即描述具体某一个 page 的修改操作。例如存放一个page修改前的状态和修改后的状态。


逻辑日志

  存储事务中的一个操作。例如insert、update、delete等。


  下面我们来详细说明下它们之间的区别。


Physical Logging

  以上图为例,我们对Page42进行update,将字段 “Kemera” 修改为 “camera”。更新操作对应的日志大致为:

    "Page 42:image at 367,2; before:'ke';after:'ca'"


    • Page 42 用于说明更新操作作用的 page;

    • 367:用于说明更新操作相对于 page 的 offset;

    • 2:用于说明更新操作的作用长度,即 length,2 代表仅仅修改了两个字符;

    • before:‘Ke’:这里表示 undo information,也可以称为 undo log;

    • after:‘ca’:这里表示 redo log information,也可以称为 redo log;

      可以看到物理日志中存储的主要就是某条记录对应的page的状态变化,那么自然是具有幂等性的,因为无论应用多少次,最终的状态总是一样的。


    Logical Logging

      上图中表示将CameraLingo表中itermID为0处的“Kemera” 修改为 “Cemera“,其对应的日志大致为:

      CameraLingo:update(0,'Kermera'=>'camera')


        在逻辑日志中就没有存储page的修改了,而存储的是类似于一条SQL语句,所以不需要指定物理上的某个page,而只是指定逻辑上的某张表即可。

        这样有一个巨大的优势便是,如果我们一条SQL语句对大量的page进行了修改,那么逻辑日志的数据量相对于物理日志便会小很多。


        而除此之外,还有一种具备物理日志和逻辑日志特点的日志,Physiological Logging。


      Physiological Logging

        Physiological Logging 的格式类似如下:

        (Page ID,Record Offset,(Filed 1, Value 1) … (Filed i, Value i) … )


          其中,Page ID、Record Offset 的设计源于物理日志。(Field1,Value1) 的设计来源于逻辑日志。


        Physiological Logging 的特点是:


        • 与物理日志相同,更新操作相对于 page 进行,每一条日志仅仅涉及一个 page 的修改;

        • 与逻辑日志相同:日志内容为更新语句(update query)本身,而不是状态机某些字段更新前后的状态。

          可以发现Physiological日志兼具了逻辑日志数据量小和物理日志幂等性的特点。


        PostgreSQL中WAL日志幂等性

          介绍了主要的日志形式,那么我们来看看PostgreSQL中wal日志是哪张类型的,为什么又说其不具备幂等性呢?


        WAL日志格式

        以一个简单的wal日志举例:

          rmgr: Heap len (rec/tot): 65/ 177, tx: 717, lsn: 1/92021450, prev 1/92021418, desc: HOT_UPDATE off 1 xmax 717 flags 0x20 ; new off 2 xmax 0, blkref #0: rel 1663/16395/17623 blk 0 FPW


        • rmgr: Heap :PostgreSQL内部将WAL日志归类到20多种不同的资源管理器。这条WAL记录所属资源管理器为

          Heap,即堆表。除了Heap还有Btree,Transaction等。

        • len (rec/tot): 65/ 177:wal记录的长度。

        • tx: 717:事务号。

        • lsn: 1/92021450:本条wal记录的lsn。

        • prev 1/92021418:上条wal记录的lsn。

        • desc: HOT_UPDATE off 1 xmax 717 flags 0x20 ; new off 2 xmax 0:

          这是一条热更新类型的记录,旧数据

        • offset为1,xmax为717。旧tuple在page中的位置为1(即ctid的后半部分),新tuple在page中的位置为2。

        • blkref #0: rel 1663/16395/17623 blk 0

          :引用的第一个page(新tuple所在page)所属的堆表文件为1663/13543/16469,块号为0(即ctid的前半部分)。

          可以看出wal日志是类似于Physiological日志的,既有Page ID、Record Offset这些信息,也有记录数据修改类型操作的记录。而对于这些操作的回放,PostgreSQL中是通过资源管理器进行实现的。


          以PG10为例,有以下几种类型的资源管理器:

        WAL日志的回放

          以我们上面这条WAL日志为例,其资源管理器为 Heap,那么对这条日志进行回放的大致流程为:


        1. 读取到该日志,解析日志头部,根据日志的类型,将日志交由对应资源管理器回放

        2. 解析该WAL日志,根据具体的操作类型,交由具体的函数进行回放

        3. 解析WAL日志内容

        4. XLogReadBufferForRedo,读取需要修改的页面,进行PIN和LOCK操作,并根据LSN确认是否需要REDO

        5. 如果需要REDO,则将日志应用到页面上,更新页面的LSN,标记页面为脏页

        6. 对需要修改的页面进行UNPIN和UNLOCK操作,其他进程可以使用该页面,bgwriter可以向下刷该页面

          下面我们结合具体的代码来看看wal日志的回放流程,来看看究竟wal日志是不是具有幂等性。


        函数的调用顺序为:

          StartupXLOG->heap_redo->heap_xlog_insert

            而主要涉及到回放wal日志的是heap_xlog_insert。

            首先我们要判断回放wal日志是不是需要重新初始化一个新的page:

            然后要去判断wal日志的LSN和page的LSN(需要注意对于备份区块而言,不需要判断LSN而是直接全部应用)。

            紧接着我们便可以向page中插入这条数据:

            这便是上述一条普通数据插入的恢复流程,在恢复的过程中,并不是通过逻辑日志那种一条条SQL语句,而是通过资源管理器将操作记录进行回放。


          WAL日志与幂等性

            那么wal日志的恢复是不是具备幂等性呢?从上面的代码中我们可以看到,如果WAL日志记录的LSN比page中的LSN大,那么便会将WAL日志中记录的数据插入到page中,并将page的LSN更新成WAL日志的LSN。


            而对于WAL日志记录的LSN比page中的LSN小的情况,是直接跳过,没有对应的处理。


            那么其实PostgreSQL中的wal日志是具备幂等性的,因为我们重复应用日志得到的结果是同样的,而之所以一样是因为压根就直接跳过了,所以当然一样。


            而《The Internals of PostgreSQL》中说备份区块的wal日志具备幂等性,非备份区块不具备幂等性的这种说法应该是想将两者进行对比。毕竟备份区块是会重复去应用的,得到的结果是一样的,而非备份区块是直接跳过了,并没有去应用,如果从这个角度来说似乎也是合理的。


          总结

            PostgreSQL中的wal日志是具备幂等性的,只是对于非备份区块而言,如果已经应用过的wal日志是会直接跳过,而并不是真正的去重新应用,因此最终的状态自然是不变的。


          参考链接:

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

          https://spongecaptain.cool/post/database/logicalandphicallog/#13-physiological-logging

          http://mysql.taobao.org/monthly/2020/02/01/


          我知道你“在看”哟~

          文章转载自晟数学苑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论