WAL 全称是Write-Ahead Logging,预写式日志在数据库系统中是一个保证数据完整性的标准解决方案。事物中对数据文件的变更会优先写入wal文件,只有等事物提交之后(日志文件刷盘成功)才会应用到数据文件。如果这个期间数据库发生崩溃,那么没有被应用的数据可以通过重放wal日志进行恢复。
通常我们对数据库的变更操作(INSERT、 UPDATE、 DELET)在写入wal成功之后就会给客户端返回成功,此时变更存在于wal日志和RAM中,后续在经过check point之后,数据才会落到data page中。
在pg中wal日志可以用来保证数据完整性,也可以用来做数据复制。那么wal文件到底是个什么东西呢?
通过观察数据库的wal文件我们可以发现,wal文件保存在pg的数据目录下的pg_wal目录下,如下图所示:
可以看到每个wal文件的大小为16MB(这个大小是可以调整的), 内部被分成多个段(segement),每个段为8kb大小(可以调整)。
Wal文件中的内容无法直接以人可读的方式查看, 需要借助pg的内置的工具进行查看。如下模拟一个事物中插入三条数据:
我们通过pg的内置工具pg_waldump查看 介于 事物开始之前和事物提交之后的lsn之间的wal的内容。
这里提到了lsn, 那么lsn是什么东西呢?
在pg中的每写入一条记录都会在wal日志中增加一条wal记录,写入这个记录的位置就是lsn,全称为Log Sequence Number,lsn的值是wal文件中字节偏移量。在pg中wal文件的命名跟lsn之间有着关联关系。 首先来看下wal文件命名:
00000001 高8位,代表数据库的timeline
0000000E 中8位,代表logid
00000043 低8位,代表logseq
LSN的格式: E/43D09EB8
Lsn被“/”分隔,高位和wal名称中的中八位对应, 低位中的前两位和wal的低位对应, 后六位的十进制对应的是wal中的字节码的偏移量。Pg中可以通过提供的内置函数pg_walfile_name_offset来查询某个具体的lsn对应的wal文件个偏移量:
既然有逻辑二字来限定复制那就说明还有其他的复制方式, 在pg中不止存在逻辑复制, 还有物理复制, 物理复制是逐块逐字节的进行复制。逻辑复制是一种基于对象的复制标识(replica identify)来复制数据对象及其变化的方式。
相较于物理复制, 逻辑复制提供了更加细粒度的控制。逻辑复制整体上采用的是一个发布订阅的模型,订阅者可以订阅一个或者多个发布者, 发布者也可以被一个或者多个订阅者订阅,通常我们在做逻辑复制的通用步骤是:订阅者首先获取发布者数据库上的数据快照并拷贝这部分存量数据,当这个快照复制执行完成之后,发布者数据库上的变更数据就可以实时的发送给订阅者,订阅者也会顺序处理这些增量数据,基于此逻辑复制可以保证事物的一致性,所以有时候也称逻辑复制为事物复制。
要使用pg的逻辑复制,就得用逻辑解码器,经过逻辑解码器解码后的数据基本上就可以肉眼可读了。Pg提供了两个内置的逻辑解码器一个是pgoutput 另外一个是test_decoding, pgoutput需要编写代码使用流式逻辑复制协议来使用,test_decoding插件我们可以直接使用sql命令执行。如下采用test_decoding来演示获取wal日志中的增量数据。
1. 提前准备好数据库,并修改数据库配置wal_level为logical(如果要使用逻辑复制,必须做此变更)
2. 接着我们准备好需要的slot, 关于slot以及其他术语我们后面介绍
创建slot
查看数据库当前的lsn
查看当前创建好的slot
// 先查询一次增量数据,发现为空
// 对现有数据库中的表做一次变更,然后执行查询
在讲逻辑复制的时候我们提到了几个名词,订阅者,发布者, 复制标识等,接下来就让我们来认识下这几个术语。
术语 | 描述 |
---|---|
发布者 | 发布数据的服务 |
订阅者 | 订阅复制数据的服务 |
复制标识 | 原名repelication identify,u是在pdate和delete场景下定位老数据的标识,一般是主键或者唯一键 |
Slot | 一个标记, 用来记录客户端数据复制的进度信息,主要是lsn的信息 |
Publication | Publication是一个变更集, 也可以称之为复制集,内部包含了表的变更。也可以作为一个过滤器,提供了行级别力度的控制,可以定义哪些表被发布,也可以定义某个表中符合条件的行被发布。 |
对于发布者和订阅者
在pg的数据复制过程中,发布者和订阅者是个相对的概念, 发布数据的为发布者,订阅数据的为订阅者,这个发布订阅的模型是可以进行级联的,订阅者也可以发布数据供下游继续订阅。
对于slot的详细信息可以查阅下表
字段名 | 含义 |
---|---|
xmin | 该slot需要数据库保存的最旧的lsn,该lsn之后的事物不会被VACUUM删除 |
Catalog_xmin | 该slot需要数据库保留影响系统catalog的最旧的lsn |
Restart_lsn | 使用该slot的消费者可能需要的lsn, 不会被checkpoint删除,只有当此lsn落后current lsn超过max_slot_wal_keep_size. 如果该slot从未被reserved,那么该值为null。 |
Confirmed_flush_lsn | 表明消费者已经确认接受到数据对应的lsn, 比当前lsn更老的数据将不会再次接受。 |
Wal_status | 该slot需要的wal文件的可用性: 1. reserved:表明该slot需要的wal文件在max_wal_size范围内。 2. extende:表明超过了max_wal_size但是文件仍然被slot或者wal_keep_size保留。 3. unreserved:该slot不再保留需要的wal文件,其中的一些将会在下次的checkpoint被删除, 此状态可以变成reserved或者extended 4. lost:该slot所需的wal文件已经被删除,该slot也不再可用。 |
Safe_wal_size | 表明该slot所需的wal文件还可以被写入的字节数(意味着该slot没有处于lost的危险状态), lost的slot该值为null,max_slot_keep_size为-1的该值也为null。 |
Two_phase | 如果slot启动了对prepared 事物解码,则为true, 物理slot始终未false。 |
用途 | SQL命令 |
---|---|
查询pg版本 | select version(); |
创建slot | |
查询slot | SELECT * FROM pg_replication_slots; |
删除slot | select pg_drop_replication_slot(‘slot_name’); |
查询publication | select * from pg_publication; |
查询publication发布的表 | select * from pg_publication_tables; |
查询某个表的replication identify | SELECT CASE relreplident WHEN ‘d’ THEN ‘default’ WHEN ‘n’ THEN ‘nothing’ WHEN ‘f’ THEN ‘full’ WHEN ‘i’ THEN ‘index’ END AS replica_identity FROM pg_class WHERE oid = ‘mytablename’::regclass; |
查询数据库当前正在执行的操作 | select * from pg_stat_activity where state =‘active’; |
Pg的jdbc驱动中提供了一套API来做逻辑复制。只需要简单的几行代码就可以实现一个简单的数据服务的逻辑。