写在前面
备份通过是否停机分为热备和冷备,可以分为逻辑备份和物理备份,热备又分为全量备份和增量备份。
恢复可以分为完全恢复、指定时间点恢复(PITR)。完全恢复是指恢复到数据库损坏的那个时间点,指定之间点恢复
逻辑备份是备份某个时间点的数据,通过MVCC保证数据的一致性,恢复的时候只能恢复到备份的时间点,不可以应用日志进行前滚,通常处理时间相对物理备份会慢,MogDB通过gs_dump或者gs_dumpall来实现逻辑备份,通过gs_restore实现数据恢复。
物理备份是备份物理文件和归档日志,因此恢复的时候可以先还原物理文件,然后在应用归档日志,可以恢复到任意时间点,如果redo日志没有丢失,可以恢复到数据库宕机前,处理时间相对逻辑备份会高效的多。
物理备份和逻辑备份应用场景在现实生活中都比较多,相对而言描述以下使用场景:
物理备份:主要是运维人员为了保证数据库损坏后数据少丢失,数据库体量大的尽量使用物理备份,整库的备份和恢复;
逻辑备份:开发人员应用较多,数据库体量不大,只备份恢复几个表几个schema可以考虑使用逻辑备份恢复。
此次笔记的目的是测试物理备份和恢复。
备份
前提条件
备份命令中如果指定--xlog-method=fetch会在备份最后的阶段备份wal日志,避免在备份过程中wal预写日志文件被轮转覆盖,需要设置wal_keep_segments参数大一些,这样就可以保留更多的wal预写日志文件,保证在备份期间不会覆盖重用原有的wal文件。也可以开启归档日志,这样在覆盖重用之前,被覆盖的文件会被归档。
[omm@pkt_mogdb1 gs_guc]$ gs_guc reload -N all -I all -c "archive_mode=on" |
备份命令中如果指定--xlog-method=stream时,会在备份过程中对wal日志进行备份,是通过wal日志流传输控制,如果是一主N从的物理架构,需要保证有空闲的线程用来备份时传输wal日志到备份文件,因此建议max_wal_senders参数的数量至少为N+1。
归档和参数的设置主要请参考《日志管理》的笔记。
备份数据库
gs_basebackup -D /opt/mogdb/backup -h 10.80.9.249 -p 26000 |
没有指定xlog-method,默认是stream。
备份成功以后打印出如下日志。
模拟数据文件丢失
查看备份路径下的文件内容:
查看源数据库data目录下的内容
可以发现备份就是把源文件下的内容备份到了另外一个目录,因此恢复的时候直接复制文件到data目录即可,先删除数据库文件模拟数据丢失
数据文件都是放在data目录下的base目录下面,删除base目录及内容
[omm@pkt_mogdb1 data]$ pwd /opt/mogdb/data/data [omm@pkt_mogdb1 data]$ rm -rf base/ |
查询数据库
select count(*) from db_mysql.dump_tables_mysql dtm left join db_mysql.dump_tables_mysql dtm1 on dtm.tablename =dtm1.tablename; |
提示错误:
SQL 错误 [11457] [3D000]: [10.80.9.150:56205/10.80.9.249:26000] FATAL: database "db_mogdb" does not exist 详细:The database subdirectory "base/16388" is missing. |
恢复数据库
- 停止数据库
[omm@pkt_mogdb1 data]$ gs_om -t stop |
2. 拷贝备份文件中的目录到数据库data目录
[omm@pkt_mogdb1 backup]$ cp -r base/ /opt/mogdb/data/data/ |
3. 启动数据库
[omm@pkt_mogdb1 data]$ gs_om -t start |
select count(*) from db_mysql.dump_tables_mysql dtm left join db_mysql.dump_tables_mysql dtm1 on dtm.tablename =dtm1.tablename; |
能够正常查询,数据已经恢复
PITR恢复
通过测试发现,上面的测试只是恢复到了备份的时间点,并没有进行前滚。因此不能恢复到出故障的时间点,依旧会丢失数据,通过PITR的方式可以恢复到物理备份数据之后的某一时间点。如果存在备机的环境,备机需要进行全量build与主全量同步,因为主库进行了PITR恢复,导致主从数据已经不一致,注意:一定要开启归档日志,并且指定归档日志路径。
测试步骤:
- 全量备份数据库
gs_basebackup -D /opt/mogdb/backup -h 10.80.9.249 -p 26000 |
[omm@pkt_mogdb1 ~]$ gsql -U zkh -W Zkh12345678 -d db_mogdb db_mogdb=>create table test_1 (id varchar(2),name varchar(50),saler decimal(10,2),dept_no varchar(2)); db_mogdb=> insert into test_1 values ('2','zs',6000,'10'); db_mogdb=>insert into test_1 values ('3','ls',7000,'10'); db_mogdb=>insert into test_1 values ('4','wemz',8000,'10'); db_mogdb=>insert into test_1 values ('5','lucy',9000,'10'); db_mogdb=>insert into test_1 values ('6','lili',10000,'10'); db_mogdb=>insert into test_1 values ('7','ww',50000,'10'); db_mogdb=>insert into test_1 values ('8','tq',10000,'10'); db_mogdb=> alter table test_1 add primary key (id); |
表test_1是备份后创建的,备份文件中是不存在的
手动切换几次日志
select pg_switch_xlog(); select pg_switch_xlog(); select pg_switch_xlog(); |
记录一下当前时间2022-08-17 09:27:17
执行误操作,把所有的人工资都修改成了10000,如果当时发现可以使用闪回功能来恢复数据,此处只是为了测试,把数据恢复到update之前。
update test_1 set saler=10000: |
gs_om -t stop |
手动备份一下目标数据库data目录,以防出现意外情况
[omm@pkt_mogdb1 data]$ tar -zcvf data.tar.gz data/ |
替换目标数据库的data目录
cp -r * /opt/mogdb/data/data/ |
[omm@pkt_mogdb1 pg_xlog]$ pwd /opt/mogdb/data/data/pg_xlog [omm@pkt_mogdb1 pg_xlog]$ rm -rf * |
cp /opt/mogdb/data/archivelog/* /opt/mogdb/data/data/pg_xlog/ |
[omm@pkt_mogdb1 data]$ cat recovery.conf #restore_command 指定一个shell命令从归档日志路径中把归档日志复制到pg_xlog中由于我已经手动复制可以不进行配置此项 #restore_command = 'cp /opt/mogdb/data/archivelog/* /opt/mogdb/data/data/pg_xlog/ ' #archive_cleanup_command 指定一个shell命令,每次重启数据库都会清理备库已经不需要的配置文件,我这里不进行清理 #archive_cleanup_command = 'pg_archivecleanup /mnt/server/archivedir %r' #recovery_target_time 恢复到指定时间点的时间戳 recovery_target_time='2022-08-17 09:27:17' #recovery_target_inclusive 参数表明到达恢复时间点后是否还继续,这里不在继续恢复 recovery_target_inclusive=false |
[omm@pkt_mogdb1 data]$ gs_om -t start |
数据库成功,因为主库已经进行了PITR恢复,所以主备数据是不一致的,备库需要重新rebuild,
登录主库查看数据是否恢复到我们想要的数据
可以看到数据库已经恢复到我们想要的时间节点。
8. 重建从库
登录从库,切换到omm用户
[omm@pkt_mogdb2 ~]$ gs_ctl build -b full |
重新查看主从状态
主从状态已经正常。
9. 若已经恢复到预期状态,通过pg_xlog_replay_resume()指令使主节点对外提供服务。
Call pg_xlog_replay_resume() |