通过上面的描述,我们知道一条查询SQL执行的主要过程,客户端执行一条SQL前需要先经过连接数据库、解析器、优化器、执行器等阶段,再由执行器返回查询结果,具体过程可以参考下面的简图。
查询SQL是对已有数据的访问和返回,一般情况下不会涉及到数据存储的更新,那么有数据更新的场景是怎么执行的呢?
你是不是也遇到过或者听说过误操作导致数据表被删除或者写乱,需要回到操作前的某一时间点的案例,它是怎么做到的呢?
常见的生产系统都有主备节点,主节点出现故障时可以通过切换备机快速恢复业务,主备之间的数据同步是通过什么来完成的呢?
接下来我们以一条UPDATE SQL来举例,下面的表有一个主键ID1和一个整型字段ID2,两个字符字段C1和C2,下面是更新SQL操作:
postgres=> update t10 set id2=id2 2 where id1=156;
对于一条更新SQL来说,它和查询SQL一样要经过SQL解析、优化、执行等阶段,同时会多出来写WAL(Write Ahead Logging)日志的步骤。顾名思义,数据库在执行更新操作前先写日志,后写数据。
以上面的UPDATE操作为例,执行器从存储读入ID1=156的数据到内存中,并在内存中做ID2 2计算,完成计算后并不马上把这条记录刷新到硬盘,而是先记录一条日志,就告诉客户端这个操作已经完成,继续处理其他任务。
WAL日志
在10版本以前,WAL日志叫做XLOG日志,10版本之后统一更名为WAL。它有以下几个特点,这些特点和数据库运维息息相关:
第一、WAL日志记录物理文件块变更信息,而不是逻辑变化,我们常常有听到能不能把WAL日志解析成SQL,我想看看当时执行的SQL是什么样的,PostgreSQL本身功能是不支持WAL转换成SQL的。好在PostgreSQL是一种开放式设计,开发之后留有很多hook,可以方便的实现很多个性需求。 开源WalMiner插件可以非常方便的把WAL转换成前滚和回滚SQL,下面是工具链接,有兴趣可以尝试下:https://gitee.com/movead/XLogMiner。
第二、固定大小16MB,WAL日志文件序号向上递增,依次顺序写入。
├── pg_wal
│ ├── 000000010000000100000018
│ ├── 000000010000000100000019
│ ├── 00000001000000010000001A
│ └── archive_status
从WAL日志设计看,随着业务不断写数据,日志文件也会一直堆积,那么必然会导致硬盘容量占满,针对这个问题PostgreSQL设计了专门的archive进程做日志归档。在PG12及之前版本中通过wal_keep_segments参数设置保留WAL日志的个数,而在PG13版本中,该参数修改为wal_keep_size。在实际运维过程,经常出现WAL堆积的问题,著名的CheckPoint Skip也会影响WAL归档。
第三、顺序写入,WAL日志按事务提交顺序写入,虽然现在SSD硬盘已经非常普遍的应用,但是顺序写入性能相对随机读写依然有很大优势。
WAL日志非常重要,那么怎么保证它的完整性就是一个重要课题,此处先知道它由synchronous_commit参数控制刷盘策略,后面再聊WAL日志的应用。
如何恢复数据到任意时间点呢,比如说误操作某个表要恢复上午10点?
- 如果运气好,刚好有凌晨的数据库全量备份和连续的WAL日志,那么我们可以用全量备份文档 追增量WAL日志进行数据恢复,这样就能构造出误操作前的数据快照;
- 如果运行不是特别好,刚好全量备份都丢失了(当然,连续全量备份不成功在生产系统是不可接受的),那么可以用之前介绍的WalMiner工具生成‘回滚SQL’,根据SQL把数据恢复到误操作前,不得不说明这种方法存在一些缺憾,不到万不得已不要尝试。