【深度】ArteryBase事务日志(4)– 事务日志误删怎么办

2022-03-29 00:00:00 数据 数据库 事务 日志 检查点

【深度】ArteryBase事务日志(4)– 事务日志误删怎么办

@(arterybase)

概述

在实际使用中,由于各种各样等原因,可能会将事务日志删掉,这种情况如何处理,是不是就整个库就完全不能用了?

例子1

  • 数据库启动后,连到数据库上,插入1千万条数据。然后关闭数据库

  1. pg_ctl start -w

  2. psql postgres sa -c "insert into test1 select generate_series(1, 10000000)"

  3. pg_ctl stop

  • 删除事务日志

  1. cd $PGDATA/pg_xlog

  2. rm 0000*

  • 重启数据库,无法启动

  1. pg_ctl start

  2. LOG:  database system was shut down at 2016-10-20 11:24:06 CST

  3. LOG:  invalid primary checkpoint record

  4. LOG:  invalid secondary checkpoint record

  5. PANIC:  could not locate a valid checkpoint record

  6. LOG:  startup process (PID 52721) was terminated by signal 6: Aborted

  7. LOG:  aborting startup due to startup process failure

  • 使用pg_controldata工具查看控制文件中的信息,得到后一个检查点的信息,如nextXID、nextOID。

  1. $ pg_controldata | grep "Latest checkpoint's Next"

  2. Latest checkpoint's NextXID:          0/1875

  3. Latest chekpoint's NextOID:          40975

  4. Latest checkpoint's NextMultiXactId:  1

  5. Latest checkpoint's NextMultiOffset:  

  • 下一步,我们根据以上信息使用pg_resetxlog将事务日志初始化

注意: 我们一定只把它用作后的方法,就是说只有因为这样的崩溃导致服务器无法启动的时候才使用。

  1. $ pg_resetxlog -o 40975 -x 1875 -f $PGDATA

  2. Transaction log reset

  • 启动数据库,可正常启动

  1. $ pg_ctl start -w

  2. waiting for server to start....LOG:  database system was shut down at 2016-10-20 11:28:14 CST

  3. LOG:  MultiXact member wraparound protections are now enabled

  4. LOG:  database system is ready to accept connections

  5. LOG:  autovacuum launcher started

  6. done

  7. server started

  • 访问及导出所有的表,均没有错误

  1. psql postgres sa -c "select count(*) from test1"

  2.  count  

  3. ---------

  4. 1000000

  5. (1 row)

  6. pg_dumpall -U sa >test.dmp

通过以上例子,说明在数据库正常关机情况下,删除事务日志文件,数据库是可以恢复正常的。 根据前文《【深度】ArteryBase事务日志(2)--从关机中恢复》一文中,正常关机情况下(pg_ctl stop,不加-m immediate参数)会创建关机检查点,事务日志和数据文件保持一致。重新启动时,无需重做即可恢复。

例子2

  • 数据库启动后,待psql连到数据库上后,杀掉数据库守护进程;

  1. $ ps -ef | grep postgres

  2. appuser  44913 25901   04:31 pts/2    00:00:00 psql postgres appuser

  3. appuser  52758     1   11:28 pts/    00:00:00 /home/appuser/workspace/arterybase/bin/postgres

  4. appuser  52760 52758   11:28 ?        00:00:00 postgres: checkpointer process                

  5. appuser  52761 52758   11:28 ?        00:00:00 postgres: writer process                      

  6. appuser  52762 52758   11:28 ?        00:00:00 postgres: wal writer process                  

  7. appuser  52763 52758   11:28 ?        00:00:00 postgres: autovacuum launcher process          

  8. appuser  52764 52758   11:28 ?        00:00:00 postgres: stats collector process              

  9. appuser  52834 52497   11:34 pts/    00:00:00 grep postgres

  10. $ kill -9 52758

  • 在psql窗口写sql插入一千万条数据后,杀掉psql对应的服务进程

  1. psql postgres sa

  2. #select pg_backend_pid();

  3. pg_backend_pid

  4. ----------------

  5.          52877

  6. (1 row)

  7. #insert into test1 select generate_series(1, 10000000);

  8. Kill -9 52877

  • 然后将pg_xlog下的事务日志都删除,然后启动数据库,数据库报错:

  1. $ pg_ctl start

  2. LOG:  database system was interrupted; last known up at 2016-10-20 13:36:28 CST

  3. LOG:  invalid primary checkpoint record

  4. LOG:  invalid secondary checkpoint record

  5. PANIC:  could not locate a valid checkpoint record

  6. LOG:  startup process (PID 53557) was terminated by signal 6: Aborted

  7. LOG:  aborting startup due to startup process failure

  • 跟踪源码:在读取xlog文件时,找不到文件,以下为报错堆栈:

  1. (gdb) backtrace

  2. #0  XLogFileRead (segno=3915, emode=13,    tli=1, source=2, notfoundOk=1 '\001') at xlog.c:3351

  3. #1  0x0000000000521342 in XLogFileReadAnyTLI (segno=3915, emode=13, source=2) at xlog.c:3436

  4. #2  0x000000000052fcc3 in WaitForWALToBecomeAvailable (RecPtr=65682808832, randAccess=1 '\001',    fetching_ckpt=1 '\001', tliRecPtr=65682920312) at xlog.c:11291

  5. #3  0x000000000052f560 in XLogPageRead (xlogreader=0xe7af18, targetPagePtr=65682800640,    reqLen=8192, targetRecPtr=65682920312, readBuf=0xe7b838 "", readTLI=0xe7b7c0) at xlog.c:10956

  6. #4  0x0000000000534a24 in ReadPageInternal (state=0xe7af18, pageptr=65682915328, reqLen=5008) at xlogreader.c:524

  7. #5  0x0000000000534264 in XLogReadRecord (state=0xe7af18, RecPtr=65682920312, errormsg=0x7fffffffd2f8) at xlogreader.c:243

  8. #6  0x00000000005221e1 in ReadRecord (xlogreader=0xe7af18, RecPtr=65682920312, emode=15, fetching_ckpt=1 '\001') at xlog.c:3922

  9. #7  0x0000000000529632 in ReadCheckpointRecord (xlogreader=0xe7af18, RecPtr=65682920312, whichChkpt=1, report=1 '\001')    at xlog.c:7660

  10. #8  0x000000000052717b in StartupXLOG () at xlog.c:6175

  11. #9  0x000000000076e394 in StartupProcessMain ()    at startup.c:215

  12. #10 0x000000000053af6f in AuxiliaryProcessMain (argc=2,    argv=0x7fffffffdf80) at bootstrap.c:418

  13. #11 0x000000000076d557 in StartChildProcess (type=StartupProcess) at postmaster.c:5203

  14. #12 0x00000000007683b3 in PostmasterMain (argc=1, argv=0xe50f90) at postmaster.c:1288

  15. #13 0x00000000006c018a in main (argc=1,    argv=0xe50f90) at main.c:228

由于无法读取到xlog文件,系统返回NULL,在xlog.c文件中的ReadCheckpointRecord方法,输出错误日志后退出。(处理过程中,后一个检查点对应的xlog未找到,又往前找前一个检查点的xlog,如果都未找到,才退出)

  1. record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true);

  2.        if (record != NULL)

  3.        {

  4.            ...

  5.        }

  6.        /* 从库模式 */

  7.        else if (StandbyMode)

  8.        {

  9.            ...

  10.        }

  11.        /* 未找到继续找前一个检查点 */

  12.        else

  13.        {

  14.            checkPointLoc = ControlFile->prevCheckPoint;

  15.            record = ReadCheckpointRecord(xlogreader, checkPointLoc, 2, true);

  16.            if (record != NULL)

  17.            {

  18.                ...

  19.            }

  20.            else

  21.                ereport(PANIC,

  22.                     (errmsg("could not locate a valid checkpoint record")));

  23.        }

  • 守护进程检测到startup process非正常退出,输出无法启动数据库,退出。

  1. while ((pid = waitpid(-1, &exitstatus, WNOHANG)) > )

  2. {

  3.    if (pid == StartupPID)

  4.    {

  5.        ......

  6.        /* 状态不为0,退出 */

  7.        if (pmState == PM_STARTUP && !EXIT_STATUS_0(exitstatus))

  8.        {

  9.            LogChildExit(LOG, _("startup process"),

  10.                         pid, exitstatus);

  11.            ereport(LOG,

  12.            (errmsg("aborting startup due to startup process failure")));

  13.            ExitPostmaster(1);

  14.        }

  15.        ......

  • 使用pg_controldata工具查看控制文件$PGDATA/global/pg_control中的信息,得到后一个检查点的信息,如nextXID、nextOID。

  1. $ pg_controldata | grep "Latest checkpoint's Next"

  2. Latest checkpoint's NextXID:          0/1864

  3. Latest checkpoint's NextOID:          40971

  4. Latest checkpoint's NextMultiXactId:  1

  5. Latest checkpoint's NextMultiOffset:  

  • 下一步,我们将系统从后一个检查点接起来,忽略在检查点之间产生的信息

  • 使用pg_resetxlog恢复

注意: 我们一定只把它用作后的方法,就是说只有因为这样的崩溃导致服务器无法启动的时候才使用。

  1. $ pg_resetxlog -o 40971 -x 1864 -f $PGDATA

  2. Transaction log reset

  • 启动数据库

  1. $ pg_ctl start -w

  2. waiting for server to start....LOG:  database system was shut down at 2016-10-19 14:04:37 CST

  3. LOG:  MultiXact member wraparound protections are now enabled

  4. LOG:  database system is ready to accept connections

  5. LOG:  autovacuum launcher started

  6. done

  7. server started

  • 数据库虽然已经启动,但启动后一段时间,查看后台日志,频繁报错:

  1. CONTEXT:  writing block 625696 of relation base/13241/40970

  2. ERROR:  xlog flush request E/DA06ED78 is not satisfied --- flushed only to E/DA014238

  3. DETAIL:  Multiple failures --- write error might be permanent.

  • 根据日志查看是哪个表出了问题,是被杀掉的进程对应的表test1出问题了。

  1. select relname from pg_class where relfilenode = 40970;

  2. relname

  3. ---------

  4. test1

  5. (1 row)

  • 此时,其他操作均无法进行,重启数据库,访问该表部分数据ok,访问全部数据报错,报错后系统无法使用。

  1. select * from test1 limit 10;

  2. ---ok

  3. select count(*) from test1;

  4. --报错

  5. LOG:  request to flush past end of generated WAL; request E/DB0D7368, currpos E/DB0AD130

  6. CONTEXT:  writing block 626882 of relation base/13241/40970

  7. FATAL:  xlog flush request E/DB0D7368 is not satisfied --- flushed only to E/DB0AD130

  • 重启后将该表的数据导出。

  1. pg_dump -d postgres -U sa -t test1 >test1.dmp

导出过程中也报上述错误,但该数据确实可以导出。

  • 清空test1表,并导入备份数据。

  1. psql postgres sa -c "truncate table test1"

  2. psql postgres sa -f test1.dmp

成功导入,但数据不完整。因为在杀进程时,部分脏页写回磁盘,部分未写,又没有事务日志来redo。因此,应该从业务上校正这些数据。

注意: 以上报错情形只是可能出现的一种情形,如果脏页完全未写入磁盘,也有可能数据完全未插入。总之,可能出现各种奇怪的数据。

后续处理

虽然进行了恢复,但数据完整性得不到保证。应该将实例A作为保留实例,新建一个数据库实例B,并将实例A中的数据导入到实例B,并进行数据校正,校正后方可使用。

总结

事务日志系列文章4篇,详细介绍了事务日志原理、事务日志文件为什么不能通过直接删除文件的方式删除、事务日志膨胀原因解决办法以及事务日志误删的处理方法,希望能够了解事务日志的运行机理,避免因为不了解导致严重问题,当出现严重问题时,能够迅速想到解决办法,提高数据库的可用性。

来自:https://mp.weixin.qq.com/s/nwV0gSANAysRCKif3mx0nQ

相关文章