Welcome 微信登录

首页 / 数据库 / MySQL / 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 ", 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 ";/* 在内存中执行事务操作 */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 ", A); /*在日志中记录旧值*/strcat(log, logA);A -= 50;char logB[1024];sprintf(logB, "T1:B=%d ", 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 ", fpLog);fflush(fpLog);/************* 如果此时发生故障,日志COMMIT已刷盘,能够确保数据也已经刷盘成功。无需恢复********/fclose(fpLog);fclose(fpData);return 0;}代码执行之前:
数据文件内容如下:A=100B=100上述代码执行后:
数据文件内容如下:A=50B=100日志文件内容如下:T1 STARTT1:A=100T1:B=100根据日志文件,由于没有找到T1 COMMIT,所以断定事务T1未能成功,数据可能处于不一致状态,需要数据恢复。进而根据日志文件,可以得到事务T1执行之前的数据旧值A=100,B=100,恢复也就很容易了,只要把A,B的值都更新为其对应的旧值就可以了。恢复之后的数据文件:A=100B=100更多Oracle相关信息见Oracle 专题页面 http://www.linuxidc.com/topicnews.aspx?tid=12本文永久更新链接地址