初识 undo类型日志系统

日志系统是保证数据库管理系统正确执行事务的基本机制。根据作用的不同,日志系统分为undo和redo两种,本文对undo类型日志的原理进行简单模拟说明。

1 UNDO日志要求

日志记录了数据修改之前的旧值;

数据刷盘之前,把日志刷盘;(一致性)

数据刷盘之后,把日志COMMIT刷盘。(持久性)

2 UNDO日志缺陷

UNDO日志提供了足够的信息可以保证事务的一致性和持久性。但是,为了保持一致性,采取的是被动保守的策略,即:用旧值覆盖不能确保成功的事务。未成功的事务不能重新执行,只能恢复到事务之前的一致状态。

2 模拟代码

只是模拟了数据写入的过程,没有模拟数据恢复过程,待以后有时间补充。

/* UNDO 类型日志基本流程模拟 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <err.h> /* 日志文件,基本格式: * T1_START * A=100 * B=100 * T1_COMMIT */ #define LOG_FILE "test.log" /* 数据文件,基本格式: 每行一个键值对,长度固定为1024,右侧用空格填充。 * A=100 (padding) * B=100 (padding) */ #define DATA_FILE "test.data" #define LINE_MAX 1024 /* 键值对 */ typedef struct KV KV; struct KV { char* key; char* value; }; /* 从硬盘读取名为k的数据 */ static int fread_kv(char* k, char* v, FILE* fp) { rewind(fp); char line[LINE_MAX+1]={0}; int lineNo = 0; while(fread(line, 1, LINE_MAX, fp)==LINE_MAX){ int i=0; while(k[i] && line[i]!='='){ if(k[i] != line[i]){ break; } i++; } if(line[i] == '='){ strcpy(v, &line[i+1]); return lineNo; 1,1 Top } lineNo ++; } return -1; } /* 把数据刷入硬盘 */ static void fwrite_kv( char* k, char* v, FILE* fp) { int lineNo = -1; char newLine[LINE_MAX]; sprintf(newLine, "%s=%s", k, v); int offset = 0; if( (lineNo=fread_kv(k,v,fp))<0) /* insert */ { offset = fseek(fp, 0, SEEK_END); }else{ /* update */ offset = fseek(fp, LINE_MAX * lineNo, 0); } fprintf(fp, "%-1023s\n", newLine); fflush(fp); } int main(int argc, char** argv) { FILE* fpLog = fopen(LOG_FILE, "r+"); FILE* fpData = fopen(DATA_FILE, "r+"); if(!(fpLog && fpData)){ perror(NULL); } /* 开始一个事务 */ char log[1024] = "T1 START\n"; /* 在内存中执行事务操作 */ char v[LINE_MAX]; fread_kv("A", v, fpData); int A = atoi(v); fread_kv("B", v, fpData); int B = atoi(v); char logA[1024]; sprintf(logA, "T1:A=%d\n", A); /*在日志中记录旧值*/ strcat(log, logA); A -= 50; char logB[1024]; sprintf(logB, "T1:B=%d\n", B); /*在日志中记录旧值*/ strcat(log, logB); B += 50; /************** 如果此时发生故障,日志和数据均尚未写出到硬盘上, 事务丢失,但保持数据库一致性.*************/ /* 数据刷盘之前,先把日志刷入硬盘 */ fputs(log, fpLog); fflush(fpLog); /************* 如果此时发生故障,日志旧值已经被写出到硬盘上,数据尚未写入,恢复时需要把旧值恢复.(随然数据未刷盘,但并不可知)********/ /* 把数据新值刷入硬盘 */ sprintf(v, "%d", A); fwrite_kv("A", v, fpData); /************* 如果此时发生故障,日志旧值已经被写出到硬盘上,数据尚未写入,恢复时需要把旧值恢复.********/ abort(); /* 模拟故障 */ sprintf(v, "%d", B); fwrite_kv("B", v, fpData); /************* 如果此时发生故障,日志旧值已经被写出到硬盘上,数据已经写入硬盘,但是COMMIT日志未写出,也需要恢复旧值.(虽然数据已完整刷盘,但并不可知)********/ /* 数据刷盘之后,把提交日志刷入硬盘*/ fputs("T1 COMMIT\n", fpLog); fflush(fpLog); /************* 如果此时发生故障,日志COMMIT已刷盘,能够确保数据也已经刷盘成功。无需恢复********/ fclose(fpLog); fclose(fpData); return 0; }

代码执行之前:
数据文件内容如下:

A=100 B=100

上述代码执行后:
数据文件内容如下:

A=50 B=100

日志文件内容如下:

T1 START T1:A=100 T1:B=100

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/a317943f28d17045bd495e282d121669.html