摘要: 数据库 ACID 中的持久化如何实现 数据库 ACID 里面的 D,持久化。 指的是对于用户来说提交的事务,数据是可靠的,即使数据库 crash了,在硬件完好的情况下,也能恢复回来。PostgreSQL 是怎么做到的呢,看一幅图,画得比较丑,凑合看吧。假设一个事务,对数据库做了一些操作,并且产生了一些脏数据。


温馨提示:代码部分左右滑动浏览。

数据库 ACID 中的持久化如何实现

数据库 ACID 里面的 D,持久化。 指的是对于用户来说提交的事务,数据是可靠的,即使数据库 crash 了,在硬件完好的情况下,也能恢复回来。

PostgreSQL 是怎么做到的呢,看一幅图,画得比较丑,凑合看吧。

假设一个事务,对数据库做了一些操作,并且产生了一些脏数据,首先这些脏数据会在数据库的 shared buffer 中。

同时,产生这些脏数据的同时也会产生对应的 redo 信息,产生的 REDO 会有对应的 LSN 号(你可以理解为 REDO 的虚拟地址空间的一个唯一的 OFFSET,每一笔 REDO 都有),这个 LSN 号也会记录到 shared buffer 中对应的脏页中。

walwriter 是负责将 wal buffer flush 到持久化设备的进程,同时它会更新一个全局变量,记录已经 flush 的最大的 LSN 号。

bgwriter 是负责将 shared buffer 的脏页持久化到持久化设备的进程,它在 flush 时,除了要遵循 LRU 算法之外,还要通过 LSN 全局变量的比对,来保证脏页对应的 REDO 记录已经 flush 到持久化设备了,如果发现还对应的 REDO 没有持久化,会触发 WAL writer 去 flush wal buffer。 (即确保日志比脏数据先落盘)

当用户提交事务时,也会产生一笔提交事务的 REDO,这笔 REDO 也携带了 LSN号。backend process 同样需要等待对应 LSN flush 到磁盘后才会返回给用户提交成功的信号。(保证日志先落盘,然后返回给用户)

数据库同步复制原理浅析

同步流复制,即保证standby节点和本地节点的日志双双落盘。

PostgreSQL 使用另一组全局变量,记录同步流复制节点已经接收到的 XLOG LSN,以及已经持久化的 XLOG LSN。

用户在发起提交请求后,backend process 除了要判断本地 wal 有没有持久化,同时还需要判断同步流复制节点的XLOG有没有接收到或持久化(通过 synchronous_commit 参数控制)。

如果同步流复制节点的 XLOG 还没有接收或持久化,backend process 会进入等待状态。

数据库同步复制代码浅析

对应的代码和解释如下:

CommitTransaction @ src/backend/access/transam/xact.c 
RecordTransactionCommit @ src/backend/access/transam/xact.c

        /** If we didn't create XLOG entries, we're done here; otherwise we* should trigger flushing those entries the same as a commit record* would.  This will primarily happen for HOT pruning and the like; we* want these to be flushed to disk in due time.*/if (!wrote_xlog)  // 没有产生redo的事务,直接返回goto cleanup;    if (wrote_xlog && markXidCommitted)  // 如果产生了redo, 等待同步流复制SyncRepWaitForLSN(XactLastRecEnd);

SyncRepWaitForLSN @ src/backend/replication/syncrep.c

/** Wait for synchronous replication, if requested by user.** Initially backends start in state SYNC_REP_NOT_WAITING and then* change that state to SYNC_REP_WAITING before adding ourselves* to the wait queue. During SyncRepWakeQueue() a WALSender changes* the state to SYNC_REP_WAIT_COMPLETE once replication is confirmed.* This backend then resets its state to SYNC_REP_NOT_WAITING.*/void
SyncRepWaitForLSN(XLogRecPtr XactCommitLSN)
{
...    /** Fast exit if user has not requested sync replication, or there are no* sync replication standby names defined. Note that those standbys don't* need to be connected.*/if (!SyncRepRequested() || !SyncStandbysDefined())  //  如果不是同步事务或者没有定义同步流复制节点,直接返回return;
...    /** We don't wait for sync rep if WalSndCtl->sync_standbys_defined is not* set.  See SyncRepUpdateSyncStandbysDefined.** Also check that the standby hasn't already replied. Unlikely race* condition but we'll be fetching that cache line anyway so it's likely* to be a low cost check.*/if (!WalSndCtl->sync_standbys_defined ||    XactCommitLSN <= WalSndCtl->lsn[mode])  //  如果没有定义同步流复制节点,或者判断到commit lsn小于已同步的LSN,说明XLOG已经flush了,直接返回。  {LWLockRelease(SyncRepLock);        return;}
...// 进入循环等待状态,说明本地的xlog已经flush了,只是等待同步流复制节点的REDO同步状态。/** Wait for specified LSN to be confirmed.** Each proc has its own wait latch, so we perform a normal latch* check/wait loop here.*/for (;;)  // 进入等待状态,检查latch是否满足释放等待的条件(wal sender会根据REDO的同步情况,实时更新对应的latch){int            syncRepState;        /* Must reset the latch before testing state. */ResetLatch(&MyProc->procLatch);syncRepState = MyProc->syncRepState;        if (syncRepState == SYNC_REP_WAITING){LWLockAcquire(SyncRepLock, LW_SHARED);syncRepState = MyProc->syncRepState;LWLockRelease(SyncRepLock);}        if (syncRepState == SYNC_REP_WAIT_COMPLETE)  // 说明XLOG同步完成,退出等待break;//  如果本地进程挂了,输出的消息内容是,本地事务信息已持久化,但是远程也许还没有持久化if (ProcDiePending){ereport(WARNING,(errcode(ERRCODE_ADMIN_SHUTDOWN),errmsg("canceling the wait for synchronous replication and terminating connection due to administrator command"),errdetail("The transaction has already committed locally, but might not have been replicated to the standby.")));whereToSendOutput = DestNone;SyncRepCancelWait();            break;}//  如果用户主动cancel query,输出的消息内容是,本地事务信息已持久化,但是远程也许还没有持久化if (QueryCancelPending){QueryCancelPending = false;ereport(WARNING,(errmsg("canceling wait for synchronous replication due to user request"),errdetail("The transaction has already committed locally, but might not have been replicated to the standby.")));SyncRepCancelWait();            break;}// 如果postgres主进程挂了,进入退出流程。  if (!PostmasterIsAlive()){ProcDiePending = true;whereToSendOutput = DestNone;SyncRepCancelWait();            break;}//  等待wal sender来修改对应的latch/** Wait on latch.  Any condition that should wake us up will set the* latch, so no need for timeout.*/WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1);

注意用户进入等待状态后,只有主动 cancel , 或者 kill(terminate) , 或者主进程 die 才能退出无限的等待状态。后面会讲到如何将同步级别降级为异步。

前面提到了,用户端需要等待 LATCH 的释放信号。 
那么谁来给它这个信号了,是 wal sender 进程,源码和解释如下 :

src/backend/replication/walsender.c

StartReplicationWalSndLoopProcessRepliesIfAnyProcessStandbyMessageProcessStandbyReplyMessage    if (!am_cascading_walsender)  // 非级联流复制节点,那么它将调用SyncRepReleaseWaiters修改backend process等待队列中它们对应的 latch。    SyncRepReleaseWaiters();

SyncRepReleaseWaiters @ src/backend/replication/syncrep.c

/** Update the LSNs on each queue based upon our latest state. This* implements a simple policy of first-valid-standby-releases-waiter.** Other policies are possible, which would change what we do here and what* perhaps also which information we store as well.*/void
SyncRepReleaseWaiters(void)
{
...      //  释放满足条件的等待队列/** Set the lsn first so that when we wake backends they will release up to* this location.*/if (walsndctl->lsn[SYNC_REP_WAIT_WRITE] < MyWalSnd->write){walsndctl->lsn[SYNC_REP_WAIT_WRITE] = MyWalSnd->write;numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE);}    if (walsndctl->lsn[SYNC_REP_WAIT_FLUSH] < MyWalSnd->flush){walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = MyWalSnd->flush;numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH);}
...

SyncRepWakeQueue @ src/backend/replication/syncrep.c

/** Walk the specified queue from head.  Set the state of any backends that* need to be woken, remove them from the queue, and then wake them.* Pass all = true to wake whole queue; otherwise, just wake up to* the walsender's LSN.** Must hold SyncRepLock.*/static int
SyncRepWakeQueue(bool all, int mode)
{...    while (proc)  // 修改对应的backend process 的latch{        /** Assume the queue is ordered by LSN*/if (!all && walsndctl->lsn[mode] < proc->waitLSN)            return numprocs;        /** Move to next proc, so we can delete thisproc from the queue.* thisproc is valid, proc may be NULL after this.*/thisproc = proc;proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),&(proc->syncRepLinks),offsetof(PGPROC, syncRepLinks));        /** Set state to complete; see SyncRepWaitForLSN() for discussion of* the various states.*/thisproc->syncRepState = SYNC_REP_WAIT_COMPLETE;  // 满足条件时,改成SYNC_REP_WAIT_COMPLETE  ....

如何设置事务可靠性级别

PostgreSQL 支持在会话中设置事务的可靠性级别。

off 表示 commit 时不需要等待 wal 持久化。

local 表示 commit 是只需要等待本地数据库的 wal 持久化。

remote_write 表示 commit 需要等待本地数据库的 wal 持久化,同时需要等待 sync standby wal write buffer 完成(不需要持久化)。

on 表示 commit 需要等待本地数据库的 wal 持久化,同时需要等待 sync standby 节点 wal 持久化。

提醒一点, synchronous_commit 的任何一种设置,都不影响 wal 日志持久化必须先于 shared buffer 脏数据持久化。 所以不管你怎么设置,都不好影响数据的一致性。

synchronous_commit = off                # synchronization level;                                        # off, local, remote_write, or on

如何实现同步复制降级

从前面的代码解析可以得知,如果 backend process 进入了等待循环,只接受几种信号降级。 并且降级后会告警,表示本地 wal 已持久化,但是 sync standby 节点不确定 wal 有没有持久化。 
如果你只配置了1个 standby,并且将它配置为同步流复制节点。一旦出现网络抖动,或者 sync standby 节点故障,将导致同步事务进入等待状态。 
怎么降级呢?

方法1. 修改配置文件并重置

$ vi postgresql.conf
synchronous_commit = local
$ pg_ctl reload

然后 cancel 所有 query .

postgres=# select pg_cancel_backend(pid) from pg_stat_activity where pid<>pg_backend_pid();

收到这样的信号,表示事务成功提交,同时表示 WAL 不知道有没有同步到 sync standby。

WARNING:  canceling wait for synchronous replication due to user request
DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.
COMMIT
postgres=# show synchronous_commit ;synchronous_commit
-------------------- off(1 row)

同时它会读到全局变量 synchronous_commit 已经是 local了。 
这样就完成了降级的动作。

方法2. 
方法1的降级需要对已有的正在等待 wal sync 的 pid 使用 cancel 进行处理,有点不人性化。

可以通过修改代码的方式,做到更人性化。

SyncRepWaitForLSN for 循环中,加一个判断,如果发现全局变量 sync commit 变成 local, off 了,则告警并退出。这样就不需要人为的去 cancel query 了.

WARNING:  canceling wait for synchronous replication due to user request
DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.

本文转载自:云栖社区,原文链接:https://yq.aliyun.com/articles/55676



资源下载

关注公众号:数据和云(OraNews)回复关键字获取

2018DTCC , 数据库大会PPT

2017DTC,2017 DTC 大会 PPT

DBALIFE ,“DBA 的一天”海报

DBA04 ,DBA 手记4 电子书

122ARCH ,Oracle 12.2体系结构图

2017OOW ,Oracle OpenWorld 资料

PRELECTION ,大讲堂讲师课程资料

近期文章

仅仅使用AWR做报告? 性能优化还未入门

实战课堂:一则CPU 100%的故障分析

杨廷琨:如何编写高效SQL(含PPT)

一份高达555页的技术PPT会是什么样子?

大象起舞:用PostgreSQL解海盗分金问题

近期活动

来了!PostgreSQL 同步流复制原理和代码浅析,请签收相关推荐

  1. PostgreSQL 12 的同步流复制搭建及主库hang问题处理与分析

    点击上方"蓝字" 关注我们,享更多干货! 前言 主备流复制,是PostgreSQL最常用.最简单的一种高可用.读写分离的形式,类似于Oracle的ADG,主库用于读写,备库可以只读 ...

  2. 再不了解PostgreSQL,你就晚了之PostgreSQL主从流复制部署...

    再不了解PostgreSQL,你就晚了之PostgreSQL主从流复制部署 前言 在MySQL被收购之后,虽然有其替代品为: MariaDB,但是总感觉心里有点膈应.大家发现了另一款开源的数据库: P ...

  3. postgresql 在流复制模式下,WAL发生以下错误的对处方法

    postgresql在流复制模式(stream)时,slave侧log出现以下错误: record with zero length at XXX FATAL:terminating walrecei ...

  4. 同步流复制报错“psql: FATAL: the database system is starting up”

    目录 环境 症状 问题原因 解决方案 相关文档 报错编码 环境 系统平台:Linux x86 Red Hat Enterprise Linux 6,Linux x86-64 Red Hat Enter ...

  5. Postgresql Hot_Standby 流复制 基于Linux 对postgresql 和 linux 会基本的操作

    Master 192.168.1.10 2.在master上安装数据库,并且初始化数据库,在slave上安装数据库,(不用初始化数据库) 3.在master上创建流复制用户: <span sty ...

  6. java底层原理书籍_不愧是阿里p8大佬!终于把Java 虚拟机底层原理讲清楚了,请签收...

    概述 JVM 的内存模型和 JVM 的垃圾回收机制一直是 Java 业内从业者绕不开的话题(实际调优.面试)JVM是java中很重要的一块知识,也是面试常问的问题之一,直至今天,仍然还有许多面试者在被 ...

  7. PostgreSQL 之 流复制主备库切换

    作者:瀚高PG实验室 (Highgo PG Lab)- 波罗 在主从复制的两个节点中,当主节点数据库服务访问异常的时候后,可以手动通过promote 命令在从节点执行将从节点状态切换到可读写的状态,变 ...

  8. 整流2-单同步坐标锁相原理+算法+代码

    锁相环原理 锁相环主要由三个部分构成:鉴相器(Phase Detector,以下简称PD).环路滤波器(Loop Filter,以下简称LF)和压控振荡器(Voltage ControlOscilla ...

  9. pitr 原理_PostgreSQL热备原理研究及流复制运用

    付莎 摘要:高可用性(HA-High Availability)是所有商用数据库系统必须具备的一项基本功能.该文阐述了PostgreSQL数据库的高可用性的实现原理及方法,并对PostgreSQL数据 ...

最新文章

  1. SpringBoot之集成swagger2
  2. AI在软硬件开发中的应用落地!
  3. nuxt 过滤 query 参数
  4. python drop函数_用python帮财务小姐姐自动生成财务报表
  5. windbg检测句柄泄露(定位到具体代码)
  6. Passport用户认证
  7. 此计算机无法设置密码,电脑该怎么设置密码
  8. DB2 设置CodePage
  9. [Linux] 通过shell给unix socket发送数据
  10. Linux分卷压缩后,Windows解压缩
  11. 升级wireshark支持openflow13抓包
  12. 从学员角度体验教练技术之考驾照
  13. python 百度地图api热力图,Python+百度API 画出美美哒热力地图(代码+数据)
  14. 给一个不多于5位的正整数,要求:一、求它是几位数,二、逆序打印出各位数字。
  15. new和delete,malloc和free
  16. C#程序设计之windows应用程序设计基础
  17. 10-4 查找至少选修两门课程的学生学号
  18. 双主机切换下导致的显示器闪动
  19. Oracle技术分享 创建外键报错ORA-00906
  20. 写在前面——说说我的故事

热门文章

  1. 决策者根据什么曲线做出决策_如何在开放社区中做出品牌决策
  2. vr设备应用程序_在15分钟内构建一个VR Web应用程序
  3. patricia tree_前5名:专访Patricia Torvalds和Ada Initiative,印度采用开源,等等
  4. 真香 | 谁说的 StringJoiner 不好!真香警告……
  5. Python笔记(1) Python简介
  6. python中mean算函数吗_Python numpy.mean函数方法的使用
  7. mysql导出可执行文件_实例讲解MYSQL注入中导出可执行文件至启动项原理(三)...
  8. net 架构师-数据库-sql server-002-工具
  9. oracle查询时根据查询的时间字段改变状态字段的值
  10. [Luogu5105]不强制在线的动态快速排序